> 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/en/extras/mixins.md).

# Mixins and hooks

How to hook into game code with Sponge Mixin and connect a mixin to your script with hooks.

When the ready-made API is not enough, a script can hook into game code with mixins. These are regular Sponge Mixins, as in any Fabric mod, and they live in the `mixin/` folder inside the jar. A mixin has access to everything: game classes, Quick classes, the network. The sandbox does not apply to mixins.

Mixins have one limitation: they are applied once, when Minecraft starts. If you change a mixin, restart the game. Code from `java/` still reloads on the fly as before.

```
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("Hide sneak", true);

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

## How it works

* The mixin injects at the start of `isShiftKeyDown` and asks the `vanillafly:shift` hook what to return.
* In `activate()` the script subscribes to this hook and answers `false` if the checkbox is ticked.
* If there is an answer, the mixin replaces the return value. If there is none, the game works as usual.

## Rules

| Rule                                           | Why                                                                                                           |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| package `scriptmixin.<jar name in lowercase>`  | Quick uses the package to tell which script owns the mixin; with a different package the mixin is not applied |
| only `@Mixin` classes in `mixin/`              | Quick drops everything else and writes an error to the console                                                |
| classes from `java/` are not visible to mixins | they live in another class loader and reload on the fly, the only link is through hooks                       |
| pass numbers, strings and API types to hooks   | the script handler runs in the sandbox and cannot see game classes                                            |
| start hook names with the script name          | names are shared by all scripts, this way they do not clash with others                                       |

Game class names come from Mojang mappings, as in the example: `net.minecraft.client.player.LocalPlayer`, `isShiftKeyDown`. Java 21, Minecraft 26.2.

## Hooks

Hooks connect a mixin and a script. The mixin calls `Hooks.fire(name, args...)` and gets back a `Hook` in which the script handlers wrote their answer. The script subscribes with `hook(name, handler)` in `activate()`. While the script is disabled, its handlers are not called and `fire` returns the empty `Hook.NONE`.

### Hooks, for the mixin

| Method                | Type      | Description                                                                                    |
| --------------------- | --------- | ---------------------------------------------------------------------------------------------- |
| `fire(name, args...)` | `Hook`    | calls handlers on the same thread where the mixin fired; without listeners returns `Hook.NONE` |
| `listening(name)`     | `boolean` | whether anyone is listening; check it before building expensive arguments                      |

### Hook, for both sides

| Method              | Type      | Description                                               |
| ------------------- | --------- | --------------------------------------------------------- |
| `name()`            | `String`  | hook name                                                 |
| `size()`            | `int`     | number of arguments                                       |
| `arg(index)`        | `Object`  | the argument as the mixin passed it                       |
| `arg(index, value)` | `void`    | replaces an argument, the mixin will read the new value   |
| `cancel()`          | `void`    | asks the mixin to cancel the original method              |
| `cancelled()`       | `boolean` | whether anyone asked for cancellation                     |
| `result(value)`     | `void`    | suggests a return value                                   |
| `result()`          | `Object`  | the suggested value                                       |
| `hasResult()`       | `boolean` | whether a handler suggested a value, even if it is `null` |

`Hook` itself does not cancel anything, the mixin always decides. Cancelling and replacing the return value or an argument is done through `CallbackInfo`, as in the example.

About threads: `fire` calls handlers on the mixin's thread, which is not necessarily the main game thread. It can be the network thread, the chunk render thread or the sound thread. Do not touch the world or rendering from such a handler. Store the value in a field and handle it in `onTick()`. If a handler throws an exception, the script is disabled, as in any other callback.

## Restart

On every script reload Quick compares `mixin/` in the jar with what it applied when the game started. If there is a difference, the list shows "Restart required" and the console shows a warning. The script keeps working, but with the old mixins. A deleted jar also keeps its mixins until a restart.

Changed mixins are compiled right away, so you see errors before restarting. If something broke at startup, the console will have a line starting with "mixins: …".

## In the IDE

Mixins are compiled against game classes, Mixin and `script-api.jar`. For code completion in the IDE you need a Fabric project for Minecraft 26.2 with Mojang mappings. Add `mixin/` as a second sources root and add `script-api.jar` to the dependencies. You do not need to compile anything yourself, the jar holds source files.


---

# 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/en/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.
