Communityコーディング&開発github.com

vvs1off/fabric-mixins

Fabric Mod における Mixin の作成、レビュー、修正を行います。Fabric API イベントと Mixin の選択、@Inject、@Redirect、@ModifyVariable、より安全な MixinExtras インジェクター(@WrapOperation、@ModifyExpressionValue、@WrapWithCondition、@Local)、アクセサー/インボーカー、クラス改変(アクセスワイドナー)、クロスモッド互換性、mixin.debug.export によるデバッグをサポートします。バニラの動作を変更する必要がある場合、プライベートフィールドにアクセスする場合、または Mixin の適用に失敗した場合に使用します。

fabric-mixins とは?

このスキルは、Fabric Mod 開発において、Mixin を効果的かつ安全に扱うための包括的なガイドです。Mixin は Minecraft のバイトコードをロード時に書き換える強力ですが壊れやすい技術であり、このスキルは、まず Fabric API のイベントやバニラの拡張ポイント、データ駆動型コンテンツなど、より安定した代替手段を優先して検討するように促します。 具体的には、アクセサーやインボーカー、アクセスワイドナー、そして最終手段として各種インジェクターを、MixinExtras ライブラリ(Fabric Loader 0.15+ に同梱)の `@ModifyExpressionValue` や `@WrapOperation` などの安全なインジェクターを優先して使用することを推奨しています。 各 Mixin ターゲットの確認方法、Mixin 設定ファイルのベストプラクティス(必須設定、クライアント/サーバーの分離)、名前衝突を避けるための命名規則、デバッグ方法(`mixin.debug.export`)についても詳細な指示を提供します。Mixin の失敗の原因特定や、クロスモッド互換性の問題を解決するための実践的なアドバイスが含まれており、複数の Mod が競合する環境でも安定して動作する Mixin を作成するための知識を提供します。Mixin に関するあらゆるタスクにおいて、開発者の強力なリファレンスとなることを目指しています。

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

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Fabricミキシン

ミキシンは、ロード時にMinecraftのバイトコードをパッチします。強力ですが繊細です。すべてのミキシンは、特定のシグネチャを持つ特定のメソッドが次のバージョンでも存在し、他のMODが互換性を壊すように変更しないことを約束します。可能な限り少なく、可能な限り狭く記述してください。

決定順序 – ミキシンを書く前にこれらを試す

  1. Fabric APIのイベントまたはレジストリ。 まずFabric APIを調べてください。ライフサイクルイベント (ServerLifecycleEvents, ClientTickEvents)、プレイヤーイベント (UseBlockCallback, AttackEntityCallback, PlayerBlockBreakEvents)、HUD (HudElementRegistry)、画面 (ScreenEvents)、 loot (LootTableEvents)、コマンド (CommandRegistrationCallback)、ネットワーキング、アイテムツールチップ、リソースリロードリスナーなどがあります。 https://github.com/FabricMC/fabric-api (各モジュールの api パッケージ) とFabricドキュメントを参照してください。イベントはバージョン間で安定しており、他のMODと無料で合成できます。
  2. バニラの拡張ポイント。 データ駆動型のコンテンツ (タグ、ルートテーブル、レシピ、データコンポーネント、レジストリ) は、多くの場合、コードパッチがまったく必要ありません。
  3. アクセサー/インボーカーミキシン。 プライベートフィールドの読み取り/書き込みや、プライベートメソッドの呼び出しのみが必要な場合。
  4. クラスツイーカー (アクセスワイドナー)。 finalクラスをサブクラス化したり、finalメソッドをオーバーライドしたり、非パブリッククラスを参照したりする必要がある場合。
  5. インジェクターミキシン。 ここで初めて使います。@Redirect/@Overwrite よりも MixinExtras インジェクターを優先してください。

ステップ1〜4が機能しなかった理由を (ミキシンの上にあるコメントに) 記述してください。レビュアーが尋ねます。

インジェクターを書く前に: ターゲットを確認する

名前とシグネチャはMinecraftのバージョンごとに変わります。ターゲットを記憶から書かないでください。

./gradlew genSources        # その後、IDEでクラスを開くか、生成されたソースを検索します
  • 26.1以降 (難読化解除): Mojang名をソースに表示されている通りに正確にターゲットにします。
  • 26.1より前: プロジェクトのマッピング (YarnまたはMojang) の名前をターゲットにします。Loomのミキシンアノテーションプロセッサはビルド時にそれらをintermediaryに再マッピングします。不一致があると、コンパイル時に「Cannot remap」警告として表示されます。その警告をエラーとして扱ってください。
  • メソッド記述子は、バイトコードビューまたはツール (MCDev IntelliJプラグイン、mcsrc.dev) からコピーしてください。手動で入力しないでください。記述子リファレンス: https://docs.fabricmc.net/develop/mixins/bytecode

Mixin設定と規約

{
  "required": true,
  "package": "com.example.examplemod.mixin",
  "compatibilityLevel": "JAVA_25",
  "mixins": ["LivingEntityMixin", "accessor.LivingEntityAccessor"],
  "client": [],
  "server": [],
  "injectors": { "defaultRequire": 1 },
  "overwrites": { "requireAnnotations": true }
}
  • "defaultRequire": 1 は、何もマッチしないミキシンを、何もせずに静かに動作する代わりに大きく失敗させます。そのままにしておいてください。
  • クライアント専用ターゲット (net.minecraft.client.*) は、クライアント設定 (src/client/resources にあり、fabric.mod.json で "environment": "client" と宣言されています) に配置します。
  • ミキシンクラスは、通常のコードから参照されることは決してありません (アクセサーインターフェースは、キャストを介して除く)。
  • 追加するすべてのメンバーには、他のMODとの衝突を避けるために、mod-idの接頭辞を付けてください: examplemod$onTick, @Unique private int examplemod$counter;。
  • 追加するヘルパーフィールド/メソッドには @Unique をマークします。
  • ハンドラーメソッドは private です (ターゲットが非staticの場合は非static)。

以下の例のターゲットはMojang名を使用し、各インジェクターの形状を示しています。メソッド名と記述子はバージョン間で変化します – 再利用する前に、生成されたソースでそれぞれを再確認してください。

@Inject – ある時点でコードを実行し、必要に応じてキャンセルする

@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 はオーバーロードされています (getJumpPower() と getJumpPower(float)): 記述子を指定します。
    @Inject(method = "getJumpPower()F", at = @At("RETURN"), cancellable = true)
    private void examplemod$boostJump(CallbackInfoReturnable<Float> cir) {
        cir.setReturnValue(cir.getReturnValueF() * 1.5f);
    }
}
  • void ターゲットの場合は CallbackInfo、それ以外の場合は CallbackInfoReturnable<T> (ボックス化された型) を使用します。
  • 実際にキャンセルする場合にのみ cancellable = true を宣言してください。HEAD でキャンセルすると、バニラのメソッドと、その後にある他のすべてのMODのインジェクションがスキップされます – これは @Inject ができる中で最も互換性の低いことです。値を変更することを優先してください。
  • ターゲットのパラメータを読み取るには、CallbackInfo の前に、ハンドラーのシグネチャに順番に追加します: (float amount, CallbackInfo ci)。
  • 一般的な @At の値: HEAD, RETURN (すべてのreturn), TAIL (最後のreturnのみ), INVOKE (呼び出しの前。後に続けるには shift = At.Shift.AFTER を追加), FIELD, NEW。
  • ordinal は必要な場合にのみ使用してください。Mojangが別のマッチング呼び出しを追加するとすぐに壊れます。

MixinExtras – @Redirect とほとんどの @ModifyVariable の代わりにこれらを使用する

MixinExtrasは Fabric Loader 0.15+ に同梱されているため、追加の依存関係は必要ありません。インポートは com.llamalad7.mixinextras の下にあります。ドキュメント: https://github.com/LlamaLad7/MixinExtras/wiki

@ModifyExpressionValue – 呼び出し/フィールド読み取りが返すものを変更する

@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;
}

複数のMODが同じ式に @ModifyExpressionValue をチェーンできます。各MODは前のMODの結果を受け取ります。同じ呼び出しに対する @Redirect は競合してクラッシュします。

@WrapOperation – 呼び出しをラップし、元の呼び出しを実行するかどうかを選択する

@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);   // チェーンを維持する
}
  • パラメータ: レシーバー (非static呼び出しの場合)、次に呼び出しの引数、次に Operation<R> (Rはボックス化された戻り値の型。void の場合は Void)。
  • 呼び出しをスキップすることが機能でない限り、常に original.call(...) を呼び出してください。 呼び出さないと、同じ呼び出しに対する他のすべてのMODのラッパーが静かに無効になります。

@WrapWithCondition – void呼び出しを条件付きでスキップする

@WrapWithCondition(
    method = "render",
    at = @At(value = "INVOKE", target = "Lsome/Target;drawThing(...)V")
)
private boolean examplemod$hideThing(Target instance /*, call args... */) {
    return !ExampleConfig.get().hideThing;   // false = 呼び出しをスキップ
}

@Local – @ModifyVariable を使わずにローカル変数を読み取る

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

26.1以降では、ゲームにパラメータ名とローカル変数名が含まれているため、@Local(name = ...) はそこで信頼できます。26.1より前 (難読化されたjarにローカル変数名がない) では、タイプごとに @Local(ordinal = n) を優先し、mixin.debug.export で確認してください。

その他の便利な MixinExtras ツール

@ModifyReturnValue (メソッドの戻り値を RETURN で変更、他のMODとチェーン)、@ModifyReceiver、@WrapMethod (メソッド全体をラップ)、@Share (同じメソッド内の2つのインジェクター間で値を渡す)、@Cancellable (@WrapOperation 内からキャンセル)。

@Redirect と @ModifyVariable – 他に適切なものがない場合のみ

  • @Redirect は呼び出しを置き換えます。1つのMODだけが特定の呼び出しをリダイレクトできます。2つ目のMODの @Redirect (または、リダイレクトされた呼び出しの上に適用された @WrapOperation が特定の順序で) は、ハードクラッシュで失敗します。代わりに @WrapOperation を使用してください。
  • @ModifyVariable はローカル変数をタイプとordinalでターゲットにしますが、これはバージョン間で変わります。値を生成する式に対して @ModifyExpressionValue を使用するか、読み取るために @Local を使用してください。
  • @Overwrite はメソッド全体を置き換えます。それに触れる他のすべてのMODと互換性がありません。公開MODでは使用しないでください。

アクセサーとインボーカー

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();
}

// 使用法
int delay = ((LivingEntityAccessor) entity).examplemod$getNoJumpDelay();
  • インスタンスアクセサーメソッドはmod-idの接頭辞を取得します。staticメソッド (static T getX() { throw new AssertionError(); }) はそれを必要としません。
  • セッターの @Mutable は、ターゲットフィールドから final を削除します。
  • コンストラクターインボーカー: @Invoker("<init>") static Foo examplemod$new(...)。
  • アクセサーは他のMODのクラスでも機能します。クラスツイーカーはバニラでのみ機能します。

クラスツイーカー (旧アクセスワイドナー)

final クラスをサブクラス化したり、final メソッドをオーバーライドしたり、非パブリッククラスを参照したりするために必要です。Loader 0.18+ と Loom 1.12+ が必要です。古いセットアップでは、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;

両方の場所で配線し、Gradleをリフレッシュして genSources を再実行します:

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

./gradlew validateAccessWidener を実行します – 無効なエントリは、他に静かに無視されます。あなたのMODに依存するMODが変更を認識する必要がある場合にのみ transitive- ディレクティブを使用してください。ヘッダーの詳細、v2機能 (インターフェースインジェクション、Enum拡張): https://docs.fabricmc.net/develop/class-tweakers/

クロスMOD互換性チェックリスト

  • 機能する最も狭いポイントでインジェクトしてください。確信がない限り、広く使用されているメソッド (tick, render, hurt, travel) の HEAD でキャンセルしないでください。
  • 置き換えるインジェクター (@Redirect, @Overwrite) よりもチェーンするインジェクター (@ModifyExpressionValue, @ModifyReturnValue, @WrapOperation) を優先してください。
  • 追加するすべての状態に @Unique + mod-id プレフィックスを使用してください。
  • @At("RETURN") が機能する場合は、ordinal と @At("TAIL") を避けてください。
  • @Mixin(value = X.class, priority = ...) は、既知の競合を解決するためにのみ使用し、文書化してください。
  • 別のMODをターゲットにするオプションのミキシン: それらを別の設定に入れ、ミキシンプラグイン (IMixinConfigPlugin#shouldApplyMixin が FabricLoader.getInstance().isModLoaded("othermod") をチェック) でゲートするか、そのインジェクターで "require": 0 を使用してください。
  • 既知のヘビーパッチャー (Sodium, Iris, Lithium, ImmediatelyFast) は、レンダラーとゲームロジックの大部分を置き換えます。レンダリングやエンティティのティックにインジェクトする場合は、それらでテストしてください。

ミキシンのデバッグ

開発実行設定にJVM引数を追加します:

loom {
    runs {
        configureEach {
            vmArg "-Dmixin.debug.export=true"     // 変換されたクラスを run/.mixin.out/ に書き込む
            vmArg "-Dmixin.debug.verbose=true"
            vmArg "-Dmixin.dumpTargetOnFailure=true"
        }
    }
}
  • run/.mixin.out/class/... には変換されたクラスが含まれています – それを逆コンパイルして、インジェクターが何を生成したか、他のMODのインジェクターがどの順序で実行されたかを正確に確認できます。
  • -Dmixin.checks.interfaces=true は、ミキシンがインターフェースメソッドを実装していることを検証します。
  • デコードエラー (ターゲットが見つからない、InvalidInjectionException、競合するリダイレクト): fabric-crash-debugging スキルを参照してください。

よくある間違い

  • Fabric APIにすでにイベントがあるものに対してミキシンを書く (HUD、tick、ブロック使用、ツールチップ、コマンド、ルートテーブル)。
  • メソッド名や記述子を記憶や別のバージョン/マッピングセットから推測する。最初に生成されたソースを常に確認してください。
  • Mojang名のプロジェクトでYarn名 (MinecraftClient, PlayerEntity) を使用する、またはその逆。
  • @WrapOperation/@ModifyExpressionValue で十分なところで @Redirect を使用する。公開MODで @Overwrite を使用する。
  • 動作を「変更する」デフォルトの方法として cancellable = true + ci.cancel() を HEAD で使用する。
  • @WrapOperation で original.call(...) を忘れる。
  • CallbackInfoReturnable のジェネリクスが間違っている (プリミティブの代わりにボックス化)、または必要なときにターゲットのパラメータを ci の前に追加し忘れる。
  • ミキシンクラスが設定にリストされていない、間違ったセクション (mixins 対 client) にリストされている、または設定の package とは異なるパッケージにある。
  • 共通設定のクライアントターゲット: 起動時に専用サーバーがクラッシュします。
  • 通常のコードからミキシンクラスを直接参照する (new LivingEntityMixin()) 、またはミキシンパッケージ内に非ミキシンヘルパークラスを配置する。
  • 別のMODのミキシンと衝突する、@Unique ではなく、接頭辞のないフィールド。
  • 失敗しているミキシンを「修正」するために defaultRequire をオフにする – これはバグを隠し、何もせずに静かに動作するMODを出荷します。
  • クラスツイーカーが build.gradle / fabric.mod.json の一方だけで宣言されている、または検証されていない。

Individual skills in this repo

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

関連スキル