Skip to content

Instantly share code, notes, and snippets.

@TelepathicGrunt
Last active April 1, 2024 14:49
Show Gist options
  • Star 27 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TelepathicGrunt/3784f8a8b317bac11039474012de5fb4 to your computer and use it in GitHub Desktop.
Save TelepathicGrunt/3784f8a8b317bac11039474012de5fb4 to your computer and use it in GitHub Desktop.

What are mixins?

Mixins are a way to inject code or change the minecraft source code directly and should be use with caution. If you can do a PR to Forge or use an event, try those first. If all else fails, then mixins may be a good option to look into. Here are some resources for mixins:

Now, I'll be honest, the official mixin doc is kind of terrible to learn from for most people. It's extremely dense and and really only helps people who already knows in-depth bytecode and stuff. But most modders aren't like that and just wants to do small stuff lol. But I'll give you the run-down here. There's a few kinds of mixins that you will encounter quite often.

 

Commonly used kinds of mixins

  • Inject - Commonly used but take care on implementation. Will take your code and literally add it to exactly where you want it. Inject mixins can also add a return to methods to make it exit early (which is called a cancel). Never add an unconditional cancel that always runs as otherwise, you prevent the rest of the vanilla/modded code from running which can cause incompatibility issues. Also, multiple injects can stack on the same line of code just fine. The common places that most inject mixins will target in a method are:
  • @At("HEAD") - Adds your code to the top of the method.
  • @At("TAIL") - Adds your code to the bottom of the method.
  • @At("RETURN") - Adds your code to before all returns in the method's original code. Even returns in the middle of the method unlike TAIL.
  • @At(value = "INVOKE", target = "<method>"), - Adds your code before the targeted method is called in the method your mixin applied to.
  • @At(value = "INVOKE_ASSIGN", target = "<method>"), - Adds your code after the targeted method is called in the method your mixin applied to.
  • ModifyVariable - (very safe) changes the result or value of a stored variable. Multiple modifyvariables can stack on the same variable which makes it safe too.

  • ModifyConstant - (potential for conflict) allows for you to change constants like the 5 in class.something(5); but these mixins cannot stack on the same constant or else it blows up. Best done only if an modifyvariable doesnt work and an inject would be too messy/worse in compatibility. If the constant is not touched by a lot of mods, it should be alright. Do check out MixinExtras's new mixins if the constant is heavily contested.

  • Redirect - (potentially dangerous) within the method your mixin is applying to, a redirect can make a call to another method inside be redirected to your mixin's code instead. This means the redirected method doesnt get called and could cause compat issues if another mod has code in that original method.

  • Overwrite - (Extremely Dangerous) completely overwrite the method and runs only your code. Overwrites can and will break a lot of mods and other mixins, including other overwrites or injects into the middle of the method. Overwrites are good for testing purposes to help know what needs to be changed but try to never use them in production.

  • Accessors - (safest) allows for you to get or set protected, package-private, or private fields in any class. (Note: Accessors that set a value to final marked fields need @Mutable to be put above @Accessor)

  • Invokers - (safest) allows for you to call any protected, package-private, or private method in any class.

  • MixinExtras dependency (Adds some powerful compatiable mixin options beyond what is listed above): https://github.com/LlamaLad7/MixinExtras

 

Setting up mixins on Forge

Recommended link about the full details of setting up:
https://github.com/SpongePowered/Mixin/wiki/Mixins-on-Minecraft-Forge

If you are on intellij, download Minecraft Developer Plugin to make creating mixins easier. Then setup your forge workspace to be able to run mixins. You can check out this example repo for how to do that:
https://github.com/Tfarcenim/ModdingSkeletonMixin

If you had updated to ForgeGradle 4 (it updates your Gradle to Gradle 5 as a result), then in your build.gradle file, you need to add annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' to the dependency block that has minecraft "net.minecraftforge:forge:${mc_version}-${forge_version}" in it. Do not add this to the buildscript's dependency block. You will know if you have to add that annotationProcessor line if running the game errors and says to add org.spongepowered:mixin:0.8.5:processor somewhere.

 

Making a mixin as an example!

Now lets make a new mixin as an example. One that adds a custom block to BushBlock's isValidGround's check. First, make a new mixin file in the mixin folder. Call it something like BushBlockMixin so you can remember what it does and what class it is modifying. Add the mixin name to your mod.mixins.json file and put it in the mixins section.

Now lets start writing the new mixin. Above the class name, add @Mixin(BushBlock.class) This tells the game that the mixin will be applying to the BushBlock class.

Now write the annotation for the mixin method. The at = @At("HEAD") means our mixin will apply to the very top of the method. Next, Cancellable = true means we can add a return to the original method so that we can return true if it is our block. @Inject(at = @At("HEAD"), method = "<target goes here>", cancellable = true) Now we need the target method we want to mixin to.

Go into BushBlock class, find isValidGround method, right click the method name, and click Copy Mixin Target Reference which the Minecraft Dev plugin gives us. Paste that into the method = "" part. in this case, it is the method signature of the method we want which looks like Lnet/minecraft/block/BushBlock;isValidGround(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)Z

Now we write the method for the mixin. The name of the method can be anything but do something that makes sense for what it is and put your mod's name at the front of it (this way if your mixin crashes, the mixin's name in the stacktrace will say your mod's name so users know which mod crashed). You can leave the params blank so that Minecraft Dev plugin highlights it in red and offers to autocomplete it for you. Should look something like this when autocomplete runs.

private void testmod_validGroundBlocks(BlockState blockState, IBlockReader world, BlockPos position, CallbackInfoReturnable<Boolean> cir) {
   if (blockState.matches(CUSTOMBLOCK)) {
      cir.setReturnValue(true);
   }
}

The first part of the params is the same parameters as the method we are targeting. The CallbackInfoReturnable param is how we add a return to exit and end the targeted method early. So we check if the block is your block and if so, cir.setReturnValue(true); will make the method return true right away. Do note, never make an inject mixin that always cancels. That would be like an Overwrite mixin except it breaks other mods silently. Always make sure your cancelling mixins are only canceling under certain conditions.

Now run the game and the mixin should apply! You can test by placing a breakpoint in the mixin and then try placing a sapling on your block. Enjoy and use mixin safely! On a final note, there is one more thing that may be useful to know. If you make a mixin that is more of an optional thing and is ok if it fails to be applied for whatever reason, put on require = 0 in its annotation as this will make it so that if the mixin fails to apply, it just logs it out instead of hard crashing.

 

 


 

Extra stuff/Best practices:

 

Naming your mixin methods and fields:

When you create a mixin method, it is always best practice to append your modid to the front so if it crashes, the crash report states your mod right away for users. For example, consider this mixin:

    // makes Comb Cutter increase drops from BeehiveBlocks.
    @Inject(method = "use(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/entity/player/PlayerEntity;Lnet/minecraft/util/Hand;Lnet/minecraft/util/math/BlockRayTraceResult;)Lnet/minecraft/util/ActionResultType;",
            at = @At(value = "INVOKE", target = "Lnet/minecraft/block/BeehiveBlock;dropHoneycomb(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;)V"))
    private void thebumblezone_combDropIncrease(BlockState state, World worldIn, BlockPos pos, PlayerEntity player, Hand handIn, BlockRayTraceResult hit, CallbackInfoReturnable<ActionResultType> cir) {
        ItemStack itemStack = player.getMainHandItem();
        int equipmentLevel = EnchantmentHelper.getEnchantmentLevel(BzEnchantments.COMB_CUTTER.get(), player);
        if (equipmentLevel > 0 && !itemStack.isEmpty()) {
            Block.popResource(worldIn, pos, new ItemStack(Items.HONEYCOMB, equipmentLevel * 3));
        }
    }

Lets say I screw up my enchantment and that BzEnchantments.COMB_CUTTER.get() returned null. This would cause getEnchantmentLevel to crash which in turn, will cause the entire mixin to crash. But when it does, the stacktrace will show only vanilla's classes EXCEPT for one line where it says our mixin's method name. Because I called my mixin's method thebumblezone_combDropIncrease, this appears in the stacktrace and now it becomes instantly clear that my mod is the broken one.

Now for fields, mixins can add fields to the target class. This can be done by making a new field and giving it the @Unique annotation and NOT the @Shadow annotation. Shadow is for accessing a field in the target class. Unique is for adding a new field. Generally, unique will make sure your new field's name will not conflict with other mod's field names.

    @Unique
    private BeeAI.CachedPathHolder cachedPathHolder;

In all, it is often best practice to always append your modid to any mixin method or new field you make (unless you use unique annotation on a new field being added). It makes it much easier to debug crashes and to also help prevent crashes/conflicts.

 

Accessing the target's fields and methods:

Lets say you mixin into AnimalEntity class and did an inject somewhere but you need access to the animalentity's inLove field in your inject to do stuff. This is where @Shadow annotation comes in. All you need to do is make the same field in your mixin and append the shadow annotation like this:

@Shadow
private int inLove;

If the field was marked final, remove the final modifier when you define it in your mixin and put on the @Final annotation above @Shadow. This removes the compilier error with final but still tells the mixin that the original field is marked final. If you want to reassign the value of a final marked field, put @Mutable above @Final to allow it to be reassignable BUT be careful with this as reassigning a field's value could break other mods or vanilla if they do not expect changes to the value. Please use mutable rarely and carefully if you must use it.

The @Shadow annotation also works for methods as well. If I mixin into CatEntity and wanted to call that class's setCatType method in my inject mixin, I mark the mixin class as abstract because we will need to make an abstract method. Next, I create a new method with the same access level, return type, method name, and parameters BUT I also set it to be abstract. Then I can call just setCatType(10) in my mixin to give the cat a witch hut skin. The mixin will automatically convert the call to this abstract method to the actual real setCatType method within the CatEntity class. Here's what a shadowed method looks like:

@Shadow
public abstract void setCatType(int catType);

But what if we want to call a protected/public method or field in the target class's parent class? @Shadow won't work because it only looks in the target class for the stuff and not the parent class. There's a trick we can use. Make the mixin extend the parent class as well, make a dummy constructor, and now you can call the parent class's methods or fields that are protected or public. Here's an example where I extended TameableEntity so I can call both the setPersistenceRequired() and blockPosition() method from within the inject mixin. I actually should've extended MobEntity instead as that's where those two methods are and CatEntity does inherit from MobEntity if you follow the extends of each class.

@Mixin(CatEntity.class)
public abstract class CatEntityMixin extends TameableEntity {

    protected CatEntityMixin(EntityType<? extends TameableEntity> entityType, World world) {
        super(entityType, world);
    }

    @Shadow
    public abstract void setCatType(int catType);

    /**
     * Allow witch hut cats to spawn in repurposed Structures's witch huts.
     * @author TelepathicGrunt
     */
    @Inject(method = "finalizeSpawn(Lnet/minecraft/world/IServerWorld;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/entity/SpawnReason;Lnet/minecraft/entity/ILivingEntityData;Lnet/minecraft/nbt/CompoundNBT;)Lnet/minecraft/entity/ILivingEntityData;",
            at = @At(value = "TAIL"))
    private void repurposedstructures_spawnWitchHutCats(IServerWorld world, DifficultyInstance difficulty, SpawnReason spawnReason, ILivingEntityData entityData, CompoundNBT entityTag, CallbackInfoReturnable<ILivingEntityData> cir){
        ServerWorld world2 = world.getLevel();
        BlockPos pos = blockPosition();

        for(Structure<?> structureFeature : RSStructureTagMap.REVERSED_TAGGED_STRUCTURES.get(RSStructureTagMap.STRUCTURE_TAGS.WITCH_HUTS)){
            if (world2.structureFeatureManager().getStructureAt(pos, true, structureFeature).isValid()) {
                setCatType(10);
                setPersistenceRequired();
                return;
            }
        }
    }
}

Now if the parent class's method or field is private, you will need to make an accessor/invoker mixin instead. Then in your inject mixin, do ((TheAccessorMixin)(Object)this).modid_theField or ((TheInvokerMixin)(Object)this).modid_doSomething() to be able to use the accessor/invoker on your inject mixin itself.

 

Seeing mixins in source code:

Lets say your mixin is not behaving how you want it or you have a crash where the problematic method is a mixin but you cannot find who it belongs to. There is a way to see where all mixins are applied to in the MC source code and which mod the mixin came from. First, go to your VM arguments and add -Dmixin.debug.export=true.

In the vanilla launcher, you can add VM arguments by going to Installations, click the dots ..., hit Edit, click MORE OPTIONS, and add -Dmixin.debug.export=true to the box without replacing the other arguments.

In your IDE, you can edit your run configs to add the VM arguments. In Intellij, click the down arrow next to the runs (might be called Minecraft Client for you), hit Edit Configurations..., and add -Dmixin.debug.export=true to the box right under the Java version box. DO NOT ADD IT TO THE Program Arguments BOX. The box above that one is the JVM Argument box you need to add to.

Once you got the VM argument in, launch the game and now look into the game directory and you should see a new folder called .mixin.out. If you used the vanilla launcher, the folder would be in the .minecraft folder. If you launched in your IDE, the .mixin.out folder would be in your runs folder. Open the .mixin.out folder and go into the class folder. Open these java files in your IDE (Intellij will happily show you the contents right away) and you can see all the injected mixins at the bottom of the files and where they are called in that class. Extremely helpful! Enjoy!

 

How to get the parent class instance from a non-static inner class:

Link: https://gist.github.com/TelepathicGrunt/59f5ae53cf2b336ddfa0a37032e5e5a3

 

 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment