javadata-structuresminecraftjava-21minecraft-fabric

Best data structure for storing 2048 minecraft ItemTags?


Context, I'm working on a Minecraft mod (1.21, so Java 21), the purpose of which is to:

  1. Use the max_stack_size data component to drive the apparent default max-stack-size of items in an straight-forward to configure way. To make items appear to default to max stack size N, simpley add the item to the max_stack_size_N.json, and Item tag logic'll take it the rest of the way.
  2. Tear the hard-coded 99 cap on max-stack-size to pieces and replace it with something more usable.

I've accomplished these two goals for N=2ˣ by indvidually defining, assigning a listener, and checking every tag for every power of two.


public class ModTags{
public static final TagKey<Item> IS_STACK_SIZE_1 = createTag("stack_size_1" );
public static final TagKey<Item> IS_STACK_SIZE_2 = createTag("stack_size_2" );  
public static final TagKey<Item> IS_STACK_SIZE_2 = createTag("stack_size_4" );  
[...]
public static final TagKey<Item> IS_STACK_SIZE_2048 = createTag("stack_size_2048");

[...]
addReloadListener("stack_size_1"   );
addReloadListener("stack_size_2"   );
addReloadListener("stack_size_4"   );
[...]
addReloadListener("stack_size_2048");
}}
@Mixin(ItemStack.class)
public abstract class ItemStack_SizeMixin implements ComponentHolder, FabricItemStack {
[...]
@Inject(method="getMaxCount", at = @At("HEAD"))
    private void updateMaxStackSizeWithTag(CallbackInfoReturnable<Integer> cir){
        ItemStack thisAsStack = (ItemStack)(Object) this;
        
        if      ( thisAsStack.isIn(ModTags.Items.IS_STACK_SIZE_2048)) ChangeStackSize(thisAsStack, 2048 );
        else if ( thisAsStack.isIn(ModTags.Items.IS_STACK_SIZE_1024)) ChangeStackSize(thisAsStack, 1024 );
[...]
}
}

A strategy that I knew wouldn't scale up when I picked it, but the naivety of which would make making the rest of the mod's logic easy to code, check. I'd hoped that in the time it'd take me to fix everything else up satisfactorially, I'd build up the know-how to replace it with a better system. Well, that time has come, far as I can tell.

And I definitely haven't so: my question,

What's the best way to systematically define, store, and check 2048 minecraft Item Tags? For the sake of the people who really want max_stack_sizes of 372?

I already tried implementing it as an ArrayList, and that was consistant causing a stackOverflows. And beyond that I just don't have the Java knowledge to have an idea.


Solution

  • I think the first thing you should consider is: "Do I really need people to be able to choose a stack size of 1837?". What would be the player's benefit of choosing such an arbitrary number? Just because you can doesn't mean you should.

    Players are used to stacks of 1, 16 and 64 so in my naive way of thinking I'd say: Stick to powers of two, that's more than enough. Not only that, but it makes your life easier and also the player's life by giving them less of a burden to choose. (I'm going out on a limb and say that players don't care about different stack sizes at all. They probably choose the largest possible size anyway...)

    That being said, when dealing with repeated creation and reading of objects, it is a good idea to use loops and maybe a map where you can easily store objects of the same type.

    That way, you'll also remove the need to create a constant for every kind of stack size.

    I'm not up to date on Minecraft modding, but I think most the code is comprised of static variables and methods. Also, I'm not 100% sure how thisAsStack.isIn works but I'll try to give you a starting point on how to generalise your code and make it scalable. So in the end I'd suggest the following:

    public class ModTags {
        /**
         * Allows for stack sizes up to 2^11 = 2048
         */
        private static final Integer MAX_POWER_OF_TWO = 11;
        public static final Map<Integer, TagKey<Item>> STACK_SIZES;
    
        static {
            Map<Integer, Object> tmpMap = new HashMap<>();
            for(int i = 0; i <= MAX_POWER_OF_TWO; i++) {
                int stackSize = 1 << i;
                String tagName = "stack_size_" + i;
                tmpMap.put(stackSize, createTag(tagName));
                addReloadListener(tagName);
            }
            STACK_SIZES = Collections.unmodifiableMap(tmpMap);
        }
        ...
    }
    

    and your utility class:

    ...
    private void updateMaxStackSizeWithTag(CallbackInfoReturnable<Integer> cir){
        ItemStack thisAsStack = (ItemStack)(Object) this;
        ModTags.STACK_SIZES.entrySet().stream()
            .filter(entry -> thisAsStack.isIn(entry.getValue()))
            .findFirst()
            .map(Map.Entry::getKey)
            .ifPresentOrElse(stackSize -> ChangeStackSize(thisAsStack, stackSize), () -> /* Handle not found here */);
    }
                
    

    With this you've got a generic way to add stack sizes with powers of two and handle all the registration and updating in a few lines. No creating hundreds of similar constants anymore.

    Again, since I'm not 100% sure how everything in your code is working, I hope I could give you at least a basis to work with.