User Tools

Site Tools


tutorial:enum_adding

This is an old revision of the document!


Adding to Enums

Introduction

To properly add to enums we have to edit some internal fields. There is no guarantee that this will work on all java versions. Due to this it's important to make sure adding to a enum is the correct thing to do. eg. if you need to make armor you shouldn't add to ArmorMaterials but instead make your own class (armor). You also need to careful if your target enum is used in a switch and stop it from creating problems (you should anyways make sure that all uses are covered). If this stops working for some reason don't be surprised. It might be a small thing or the whole concept could be changed. I will be using adding a axolotl variant as an example here, but the concept should work on any enum you can mixin into.

Creating a Container Class

The purpose of this is to contain all of your custom enum entries. The entries will be contained in static non final fields.

public class CustomAxolotlVariant {
    static {
        AxolotlEntity.Variant.values(); // Ensure class is loaded before the variant is accessed
    }
 
    public static AxolotlEntity.Variant PURPLE; // You can add as many of these fields as you like
}

Mixing Into the Target

If you're adding to enum I'm going to expect you know the basics of mixin.

Accessing the Constructor

We need an invoker for the constructor. The first two arguments should always be the internal name and id. After that we have the arguments of the visible constructor.

@SuppressWarnings("InvokerTarget")
@Invoker("<init>")
private static AxolotlEntity.Variant newVariant(String internalName, int internalId, int id, String name, boolean natural) {
    throw new AssertionError();
}

Accessing the Values Field

If we are dealing with a minecraft class then the field name will be in intermediary mappings. To find the name of the filed we have to look at bytecode. This can be done in IntelliJ by placing the cursor in the class and going to View → Show Bytecode. Scroll down until you see a line a bit like this:

private final static synthetic [Lnet/minecraft/entity/passive/AxolotlEntity$Variant; field_28350

That means that in our case the field name is field_28350. Next we shadow it as such:

@SuppressWarnings("ShadowTarget")
@Shadow
@Mutable
private static @FinalAxolotlEntity.Variant[] field_28350;

Injecting the Entries

We are going inject after the values field is assigned. To do this we need the correct target for the for the INVOKE injection point. We can't use MCDev since we are dealing with internals so we need to type it ourselves as such: <classname><fieldname>:[<classname> In our case it would be Lnet/minecraft/entity/passive/AxolotlEntity$Variant;field_28350:[Lnet/minecraft/entity/passive/AxolotlEntity$Variant; Now we can create the inject:

@SuppressWarnings("UnresolvedMixinReference")
@Inject(method = "<clinit>", at = @At(value = "FIELD", 
        opcode = Opcodes.PUTSTATIC, 
        target = "Lnet/minecraft/entity/passive/AxolotlEntity$Variant;field_28350:[Lnet/minecraft/entity/passive/AxolotlEntity$Variant;", 
        shift = At.Shift.AFTER))
private static void addCustomVariant(CallbackInfo ci) {
 
}

Now finally we can add our entries:

var variants = new ArrayList<>(Arrays.asList(field_28350));
var last = variants.get(variants.size() - 1);
 
// This means our code will still work if other mods or Mojang add more variants!
// Repeat this section if you need more than one entry. Just remember to have unique ordinals!
var purple = newVariant("PURPLE", last.ordinal() + 1, last.getId() + 1, "purple", true);
CustomAxolotlVariant.PURPLE = purple;
variants.add(purple);
 
field_28350 = variants.toArray(new AxolotlEntity.Variant[0]);

Accessing Your Entries

To access your enum entries simply get the field from your container class. For example

System.out.println(CustomAxolotlVariant.PURPLE);

This tutorial is based on this gist by lamalad.

tutorial/enum_adding.1626965749.txt.gz · Last modified: 2021/07/22 14:55 by mattidragon