vvs1off/fabric-mixins

Escreva, revise ou corrija Mixins em um mod Fabric - escolhendo entre um evento Fabric API e um mixin, @Inject, @Redirect, @ModifyVariable, os injetores mais seguros do MixinExtras (@WrapOperation, @ModifyExpressionValue, @WrapWithCondition, @Local), accessors/invokers, class tweakers (access wideners), compatibilidade entre mods e depuração com mixin.debug.export. Use quando uma tarefa precisar alterar o comportamento vanilla, ler campos privados ou quando um mixin falhar ao aplicar.

O que é fabric-mixins?

Esta habilidade fornece um guia completo e prático para escrever, revisar e depurar Mixins em mods Fabric para Minecraft. Ela explica quando usar um mixin em vez de um evento Fabric API, uma extensão vanilla, um accessor/invoker ou um class tweaker (access widener), seguindo uma ordem de decisão clara para minimizar o uso de mixins, que são poderosos, mas frágeis. A habilidade cobre detalhadamente cada tipo de injetor, desde os básicos como @Inject até os mais modernos e seguros fornecidos pelo MixinExtras (como @ModifyExpressionValue, @WrapOperation e @WrapWithCondition), que permitem encadeamento entre mods e evitam conflitos. Também aborda accessors, invokers, class tweakers, uma checklist de compatibilidade entre mods e técnicas de depuração com mixin.debug.export. É útil para qualquer desenvolvedor Fabric que precise alterar o comportamento vanilla, acessar membros privados ou corrigir a aplicação de mixins, funcionando em todas as versões recentes do Fabric Loader e Loom.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/vvs1off/fabric-skills/tree/main/skills/fabric-mixins

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Mixins Fabric

Mixins aplicam patches no bytecode do Minecraft durante o carregamento. Eles são poderosos e frágeis: cada mixin é uma promessa de que um método específico, com uma forma específica, ainda existe na próxima versão e que nenhum outro mod o altera de forma incompatível. Escreva o mínimo possível, da forma mais restrita possível.

Ordem de decisão - tente estas opções antes de escrever um mixin

  1. Evento ou registro Fabric API. Pesquise a Fabric API primeiro: eventos de ciclo de vida (ServerLifecycleEvents, ClientTickEvents), eventos de jogador (UseBlockCallback, AttackEntityCallback, PlayerBlockBreakEvents), HUD (HudElementRegistry), telas (ScreenEvents), loot (LootTableEvents), comandos (CommandRegistrationCallback), rede, dicas de item, listeners de recarregamento de recursos e muitos mais. Navegue em https://github.com/FabricMC/fabric-api (pacote api de cada módulo) e na documentação do Fabric. Um evento é estável entre versões e compõe-se com outros mods gratuitamente.
  2. Pontos de extensão vanilla. Conteúdo orientado a dados (tags, tabelas de loot, receitas, componentes de dados, registries) geralmente não precisa de nenhum patch de código.
  3. Accessor / Invoker mixin - quando você só precisa ler/escrever um campo privado ou chamar um método privado.
  4. Class tweaker (access widener) - quando você precisa criar uma subclasse de uma classe final, sobrescrever um método final ou nomear uma classe privada/de pacote privado.
  5. Injector mixin - só agora. Prefira injetores MixinExtras ao invés de @Redirect/@Overwrite.

Anote (em um comentário acima do mixin) por que os passos 1-4 não funcionaram. Os revisores perguntarão.

Antes de escrever qualquer injetor: confirme o alvo

Nomes e assinaturas mudam a cada versão do Minecraft. Nunca escreva um alvo de memória.

./gradlew genSources        # então abra a classe no IDE ou pesquise as fontes geradas
  • 26.1+ (não ofuscado): mire os nomes Mojang exatamente como aparecem nas fontes.
  • Pré-26.1: mire os nomes do mapeamento do seu projeto (Yarn ou Mojang). O processador de anotações de mixin do Loom os remapeia para intermediary durante a compilação; uma incompatibilidade aparece como um aviso "Cannot remap" durante a compilação - trate esse aviso como um erro.
  • Copie descritores de método da visualização de bytecode ou de uma ferramenta (plugin MCDev IntelliJ, mcsrc.dev), não manualmente. Referência de descritores: https://docs.fabricmc.net/develop/mixins/bytecode

Config de mixin e convenções

{
  "required": true,
  "package": "com.example.examplemod.mixin",
  "compatibilityLevel": "JAVA_25",
  "mixins": ["LivingEntityMixin", "accessor.LivingEntityAccessor"],
  "client": [],
  "server": [],
  "injectors": { "defaultRequire": 1 },
  "overwrites": { "requireAnnotations": true }
}
  • "defaultRequire": 1 faz com que um mixin que não corresponde a nada falhe ruidosamente em vez de silenciosamente não fazer nada. Mantenha-o.
  • Alvos somente cliente (net.minecraft.client.*) vão na config do cliente (em src/client/resources, declarados com "environment": "client" em fabric.mod.json).
  • Classes mixin nunca são referenciadas a partir de código normal (exceto interfaces accessor, através de um cast).
  • Prefixe cada membro adicionado com seu mod id para evitar colisões com outros mods: examplemod$onTick, @Unique private int examplemod$counter;.
  • Marque campos/métodos auxiliares que você adicionar com @Unique.
  • Métodos handler são private (não estáticos se o alvo não for estático).

Os alvos nos exemplos abaixo usam nomes Mojang e mostram a forma de cada injetor. Nomes de métodos e descritores variam entre versões - verifique cada um deles em suas fontes geradas antes de reutilizá-lo.

@Inject - executa código em um ponto, opcionalmente cancela

@Mixin(LivingEntity.class)
public abstract class LivingEntityMixin {
    @Inject(method = "jumpFromGround", at = @At("HEAD"), cancellable = true)
    private void examplemod$cancelJump(CallbackInfo ci) {
        if (ExampleConfig.get().disableJump) {
            ci.cancel();
        }
    }

    // getJumpPower está sobrecarregado (getJumpPower() e getJumpPower(float)): forneça o descritor.
    @Inject(method = "getJumpPower()F", at = @At("RETURN"), cancellable = true)
    private void examplemod$boostJump(CallbackInfoReturnable<Float> cir) {
        cir.setReturnValue(cir.getReturnValueF() * 1.5f);
    }
}
  • CallbackInfo para alvos void, CallbackInfoReturnable<T> caso contrário (tipo encapsulado).
  • Declare cancellable = true apenas quando você realmente cancelar. Cancelar em HEAD pula o método vanilla e toda injeção de outros mods após a sua - é a coisa menos compatível que um @Inject pode fazer. Prefira modificar um valor.
  • Para ler os parâmetros do alvo, adicione-os à assinatura do handler antes do CallbackInfo, em ordem: (float amount, CallbackInfo ci).
  • Valores comuns de @At: HEAD, RETURN (todo retorno), TAIL (apenas o último retorno), INVOKE (antes de uma chamada; adicione shift = At.Shift.AFTER para depois), FIELD, NEW.
  • Use ordinal apenas quando necessário; ele quebra assim que a Mojang adiciona outra chamada correspondente.

MixinExtras - use estes em vez de @Redirect e da maioria dos @ModifyVariable

O MixinExtras está incluído no Fabric Loader (0.15+), portanto nenhuma dependência extra é necessária. As importações estão sob com.llamalad7.mixinextras. Documentação: https://github.com/LlamaLad7/MixinExtras/wiki

@ModifyExpressionValue - altera o que uma chamada/leitura de campo retorna

@ModifyExpressionValue(
    method = "travelInWater",
    at = @At(value = "INVOKE",
             target = "Lnet/minecraft/world/entity/LivingEntity;getSpeed()F")
)
private float examplemod$fasterInWater(float original) {
    return original * 1.2f;
}

Vários mods podem encadear @ModifyExpressionValue na mesma expressão; cada um recebe o resultado do mod anterior. Um @Redirect na mesma chamada entraria em conflito e quebraria.

@WrapOperation - envolve uma chamada; chama a original (ou não)

@WrapOperation(
    method = "hurtServer",
    at = @At(value = "INVOKE",
             target = "Lnet/minecraft/world/entity/LivingEntity;actuallyHurt(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;F)V")
)
private void examplemod$logDamage(LivingEntity self, ServerLevel level, DamageSource source,
                                  float amount, Operation<Void> original) {
    ExampleMod.LOGGER.debug("{} took {} damage", self, amount);
    original.call(self, level, source, amount);   // mantenha a cadeia intacta
}
  • Parâmetros: o receiver (para chamadas não estáticas), depois os argumentos da chamada e depois Operation<R> onde R é o tipo de retorno encapsulado (Void para void).
  • Sempre chame original.call(...) a menos que pular a chamada seja a funcionalidade. Não chamá-lo desabilita silenciosamente todos os outros wrappers de mods na mesma chamada.

@WrapWithCondition - condicionalmente pula uma chamada void

@WrapWithCondition(
    method = "render",
    at = @At(value = "INVOKE", target = "Lsome/Target;drawThing(...)V")
)
private boolean examplemod$hideThing(Target instance /*, argumentos da chamada... */) {
    return !ExampleConfig.get().hideThing;   // false = pular a chamada
}

@Local - lê uma variável local sem @ModifyVariable

@Inject(method = "tick", at = @At(value = "INVOKE", target = "..."))
private void examplemod$readLocal(CallbackInfo ci, @Local(name = "pos") BlockPos pos) { ... }

No 26.1+ o jogo fornece nomes de parâmetros e locais, então @Local(name = ...) é confiável lá. Pré-26.1 (sem nomes locais em jars ofuscados) prefira @Local(ordinal = n) por tipo e verifique com mixin.debug.export.

Outras ferramentas úteis do MixinExtras

@ModifyReturnValue (altera o valor de retorno de um método em RETURN, encadeia com outros mods), @ModifyReceiver, @WrapMethod (envolve um método inteiro), @Share (passa um valor entre dois injetores no mesmo método), @Cancellable (cancela de dentro de um @WrapOperation).

@Redirect e @ModifyVariable - apenas quando nada mais se encaixa

  • @Redirect substitui uma chamada. Apenas um mod pode redirecionar uma determinada chamada; um segundo @Redirect de outro mod (ou um @WrapOperation aplicado sobre uma chamada redirecionada em algumas ordens) falha com uma quebra grave. Use @WrapOperation em vez disso.
  • @ModifyVariable mira um local por tipo e ordinal, que muda entre versões. Use @ModifyExpressionValue na expressão que produz o valor ou @Local para lê-lo.
  • @Overwrite substitui o método inteiro: incompatível com qualquer outro mod que o toque. Não use em um mod público.

Accessors e invokers

package com.example.examplemod.mixin.accessor;

@Mixin(LivingEntity.class)
public interface LivingEntityAccessor {
    @Accessor("noJumpDelay")
    int examplemod$getNoJumpDelay();

    @Accessor("noJumpDelay")
    void examplemod$setNoJumpDelay(int value);

    @Invoker("getJumpPower")
    float examplemod$invokeGetJumpPower();
}

// uso
int delay = ((LivingEntityAccessor) entity).examplemod$getNoJumpDelay();
  • Métodos accessor de instância recebem o prefixo do mod id; os estáticos (static T getX() { throw new AssertionError(); }) não precisam.
  • @Mutable em um setter remove final do campo alvo.
  • Invoker de construtor: @Invoker("<init>") static Foo examplemod$new(...).
  • Accessors funcionam também em classes de outros mods; class tweakers só funcionam no vanilla.

Class tweakers (antigamente access wideners)

Necessários para criar uma subclasse de uma classe final, sobrescrever um método final ou referenciar uma classe não pública. Exige Loader 0.18+ e Loom 1.12+; configurações mais antigas usam a mesma sintaxe sob o nome "access widener" com um cabeçalho accessWidener v2 named.

src/main/resources/examplemod.classtweaker:

classTweaker v1 official
accessible  class   net/minecraft/world/inventory/MenuType$MenuSupplier
accessible  method  net/minecraft/network/chat/TextColor  formatValue  ()Ljava/lang/String;
accessible  field   net/minecraft/world/damagesource/DamageSources  damageTypes  Lnet/minecraft/core/Registry;

Conecte-o em ambos os lugares, depois atualize o Gradle e reexecute genSources:

loom { accessWidenerPath = file("src/main/resources/examplemod.classtweaker") }
"accessWidener": "examplemod.classtweaker"

Execute ./gradlew validateAccessWidener - entradas inválidas são silenciosamente ignoradas. Use diretivas transitive- apenas quando mods que dependem do seu precisarem ver a alteração. Detalhes do cabeçalho, recursos v2 (injeção de interface, extensão de enum): https://docs.fabricmc.net/develop/class-tweakers/

Checklist de compatibilidade entre mods

  • Injete no ponto mais restrito que funciona; nunca cancele em HEAD de um método amplamente usado (tick, render, hurt, travel) a menos que tenha certeza.
  • Prefira injetores que encadeiam (@ModifyExpressionValue, @ModifyReturnValue, @WrapOperation) em vez de injetores que substituem (@Redirect, @Overwrite).
  • Use @Unique + prefixo do mod id para todo estado adicionado.
  • Evite ordinal e @At("TAIL") quando @At("RETURN") funcionar.
  • Use @Mixin(value = X.class, priority = ...) apenas para resolver um conflito conhecido; documente-o.
  • Mixins opcionais direcionados a outro mod: coloque-os em uma config separada e proteja-os com um plugin de mixin (IMixinConfigPlugin#shouldApplyMixin verificando FabricLoader.getInstance().isModLoaded("othermod")), ou use "require": 0 nesse injetor.
  • Patchers pesados conhecidos (Sodium, Iris, Lithium, ImmediatelyFast) substituem grandes partes do renderizador e da lógica do jogo. Se você injetar em renderização ou tick de entidades, teste com eles.

Depurando mixins

Adicione argumentos JVM às configurações de execução de desenvolvimento:

loom {
    runs {
        configureEach {
            vmArg "-Dmixin.debug.export=true"     // escreve classes transformadas em run/.mixin.out/
            vmArg "-Dmixin.debug.verbose=true"
            vmArg "-Dmixin.dumpTargetOnFailure=true"
        }
    }
}
  • run/.mixin.out/class/... contém as classes transformadas - descompile-as para ver exatamente o que seu injetor produziu e em qual ordem os injetores de outros mods foram executados.
  • -Dmixin.checks.interfaces=true verifica se os mixins implementam métodos de interface.
  • Erro de decodificação (alvo não encontrado, InvalidInjectionException, redirecionamentos conflitantes): veja a habilidade fabric-crash-debugging.

Erros comuns

  • Escrever um mixin para algo que a Fabric API já tem um evento (HUD, ticks, uso de bloco, dicas, comandos, tabelas de loot).
  • Adivinhar um nome de método ou descritor de memória ou de uma versão/mapeamento diferente. Sempre verifique as fontes geradas primeiro.
  • Usar nomes Yarn (MinecraftClient, PlayerEntity) em um projeto com nomes Mojang ou vice-versa.
  • @Redirect onde @WrapOperation/@ModifyExpressionValue serviriam; @Overwrite em um mod público.
  • cancellable = true + ci.cancel() em HEAD como a maneira padrão de "alterar" comportamento.
  • Esquecer original.call(...) em @WrapOperation.
  • CallbackInfoReturnable genérico errado (primitivo em vez de encapsulado) ou parâmetros do alvo faltando antes de ci quando você precisar deles.
  • Classe mixin não listada na config, listada na seção errada (mixins vs client) ou em um pacote diferente do package da config.
  • Alvos cliente na config comum: trava servidores dedicados na inicialização.
  • Referenciar uma classe mixin diretamente de código normal (new LivingEntityMixin()) ou colocar classes auxiliares não mixin dentro do pacote de mixin.
  • Campos não @Unique, sem prefixo que colidem com o mixin de outro mod.
  • Desligar defaultRequire para "corrigir" um mixin com falha - isso esconde o bug e envia um mod que silenciosamente não faz nada.
  • Class tweaker declarado em apenas um dos build.gradle / fabric.mod.json, ou não validado.

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Habilidades Relacionadas