> For the complete documentation index, see [llms.txt](https://docs.quickclient.cc/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.quickclient.cc/extras/mixins.md).

# Миксины и хуки

Как встроиться в код игры через Sponge Mixin и связать миксин со скриптом через хуки.

Когда готового API не хватает, скрипт может встроиться в код игры через миксины. Это обычные Sponge Mixin, как в любом Fabric-моде, они лежат в папке `mixin/` внутри jar. Из миксина доступно всё: классы игры, классы Quick, сеть. Песочница на миксины не распространяется.

У миксинов есть ограничение: они встраиваются один раз, при запуске Minecraft. Если вы поменяли миксин, перезапустите игру. Код из `java/` при этом, как и раньше, перезагружается на лету.

```
VanillaFly.jar
  java/VanillaFly.java
  mixin/scriptmixin/vanillafly/LocalPlayerMixin.java
```

```java
// mixin/scriptmixin/vanillafly/LocalPlayerMixin.java
package scriptmixin.vanillafly;

import hex.script.api.Hook;
import hex.script.api.Hooks;
import net.minecraft.client.player.LocalPlayer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Mixin(LocalPlayer.class)
public class LocalPlayerMixin {

    @Inject(method = "isShiftKeyDown", at = @At("HEAD"), cancellable = true)
    private void vanillafly$shift(CallbackInfoReturnable<Boolean> info) {
        Hook hook = Hooks.fire("vanillafly:shift");
        if (hook.hasResult()) {
            info.setReturnValue((Boolean) hook.result());
        }
    }
}
```

```java
// java/VanillaFly.java
public class VanillaFly extends Script {

    private final CheckBox hideSneak = checkBox("Скрывать присед", true);

    @Override
    public void activate() {
        hook("vanillafly:shift", hook -> {
            if (hideSneak.value()) hook.result(false);
        });
    }
}
```

## Как это работает

* Миксин встраивается в начало `isShiftKeyDown` и запрашивает у хука `vanillafly:shift` значение для возврата.
* Скрипт в `activate()` подписывается на этот хук и, если галочка стоит, отвечает `false`.
* Если ответ есть, миксин подменяет возвращаемое значение. Если ответа нет, игра работает как обычно.

## Правила

| Правило                                         | Почему                                                                                                 |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| пакет `scriptmixin.<имя jar в нижнем регистре>` | по пакету Quick определяет, какому скрипту принадлежит миксин; с другим пакетом миксин не встраивается |
| в `mixin/` только классы с `@Mixin`             | остальные классы Quick отбрасывает и пишет ошибку в консоль                                            |
| классы из `java/` из миксина не видны           | они в другом загрузчике и перезагружаются на лету, связь возможна только через хуки                    |
| в хук передавайте числа, строки и типы API      | обработчик скрипта работает в песочнице и не видит классы игры                                         |
| имена хуков начинайте с имени скрипта           | имена общие для всех скриптов, так они не пересекутся с чужими                                         |

Имена классов игры берутся из маппингов Mojang, как в примере: `net.minecraft.client.player.LocalPlayer`, `isShiftKeyDown`. Java 21, Minecraft 26.2.

## Хуки

Хуки связывают миксин и скрипт. Миксин вызывает `Hooks.fire(name, args...)` и получает обратно `Hook`, в который обработчики скрипта записали ответ. Скрипт подписывается через `hook(name, handler)` в `activate()`. Пока скрипт выключен, его обработчики не вызываются, и `fire` возвращает пустой `Hook.NONE`.

### Hooks, для миксина

| Метод                 | Тип       | Описание                                                                                         |
| --------------------- | --------- | ------------------------------------------------------------------------------------------------ |
| `fire(name, args...)` | `Hook`    | вызывает обработчики в том же потоке, где сработал миксин; без слушателей возвращает `Hook.NONE` |
| `listening(name)`     | `boolean` | есть ли слушатели; это стоит проверить до того, как собирать дорогие аргументы                   |

### Hook, для обеих сторон

| Метод               | Тип       | Описание                                               |
| ------------------- | --------- | ------------------------------------------------------ |
| `name()`            | `String`  | имя хука                                               |
| `size()`            | `int`     | количество аргументов                                  |
| `arg(index)`        | `Object`  | аргумент в том виде, в котором его передал миксин      |
| `arg(index, value)` | `void`    | заменяет аргумент, миксин прочитает уже новое значение |
| `cancel()`          | `void`    | просит миксин отменить исходный метод                  |
| `cancelled()`       | `boolean` | запрашивал ли кто-нибудь отмену                        |
| `result(value)`     | `void`    | предлагает возвращаемое значение                       |
| `result()`          | `Object`  | предложенное значение                                  |
| `hasResult()`       | `boolean` | предложил ли обработчик значение, даже если это `null` |

Сам `Hook` ничего не отменяет, решение всегда принимает миксин. Отмена и подмена возвращаемого значения или аргумента делаются через `CallbackInfo`, как в примере.

Про потоки: `fire` вызывает обработчики в потоке миксина, и это не обязательно главный поток игры. Это может быть сетевой поток, поток рендера чанков или звука. Из такого обработчика нельзя работать с миром и рендером. Сохраните значение в поле и обработайте его в `onTick()`. Если обработчик выбросит исключение, скрипт выключится, как и в любом другом колбэке.

## Перезапуск

При каждой перезагрузке скриптов Quick сравнивает `mixin/` в jar с тем, что было встроено при старте игры. Если есть разница, в списке появится «Нужен перезапуск», а в консоли предупреждение. Скрипт продолжает работать, но со старыми миксинами. Миксины удалённого jar тоже остаются до перезапуска.

Изменённые миксины компилируются сразу, поэтому ошибки видны ещё до перезапуска. Если что-то сломалось при старте, в консоли будет строка «миксины: …».

## В IDE

Миксины компилируются против классов игры, Mixin и `script-api.jar`. Чтобы IDE показывала подсказки, нужен Fabric-проект под Minecraft 26.2 с маппингами Mojang. Подключите `mixin/` вторым sources root и добавьте `script-api.jar` в зависимости. Компилировать самостоятельно ничего не нужно, в jar кладутся исходники.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.quickclient.cc/extras/mixins.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
