Milking the Peacemaker Cow

Peacemaker Cows serve as the main food source for the Butterfly invasion of your Minecraft world. They can be “milked” to gather Peacemaker Honey, and this honey can be used by players to pacify the alien invaders. Of course, killing them is also an option if you want to deter the invaders.

Milking Honey


I’ve implemented a new method on the Peacemaker Cows that allows players to use a Glass Bottle on them in order to gain a Peacemaker Honey Bottle. As explained in an earlier article, Peacemaker Butterflies will follow you around when you hold these, and will not attack you. They can also be used to permanently pacify a Peacemaker Butterfly by feeding it.

The code to achieve this is very simple. I just added an override for mobInteract() that replaces a Glass Bottle with a Peacemaker Honey Bottle. This method is based on Minecraft’s own code for milking actual cows:

    /**
     * Allow players to "milk" Peacemaker Honey from the Cow.
     * @param player The player interacting with the Cow.
     * @param interactionHand The hand being used for the interaction.
     * @return The result of the interaction.
     */
    @NotNull
    @Override
    public InteractionResult mobInteract(Player player,
                                         @NotNull InteractionHand interactionHand) {
        ItemStack itemInHand = player.getItemInHand(interactionHand);
        if (!itemInHand.is(Items.GLASS_BOTTLE)) {
            return super.mobInteract(player, interactionHand);
        }

        Level level = level();
        if (level.isClientSide()) {
            player.playSound(SoundEvents.COW_MILK, 1.0F, 1.0F);
            return InteractionResult.SUCCESS;
        }

        Item peacemakerHoney = ItemRegistry.PEACEMAKER_HONEY_BOTTLE.get();
        ItemStack result = new ItemStack(peacemakerHoney);

        itemInHand.shrink(1);
        if (itemInHand.isEmpty()) {
            player.setItemInHand(interactionHand, result);
        } else if (!player.getInventory().add(result)) {
            player.drop(result, false);
        }

        level.gameEvent(player, GameEvent.FLUID_PICKUP, blockPosition());
        return InteractionResult.CONSUME;
    }

With this interaction we now have emphasis that these huge creatures are the food source for the Butterfly invasion, which will also provide a hint on how to reduce the number of Peacemaker Butterflies spawning into the world later.

Refactoring the Registries


Another thing I worked on this week were the registries. I tried to be too clever when I implemented the registries, trying to avoid polluting the global namespace with constants. The idea was cleaner code, but it actually had the opposite effect.

In my implementation I had to ensure everything that used items, entities, blocks, or anything that required registering had a reference to it. This meant passing registries into other registries and having to ensure initialisation orders were correct. As an example this is the original code in the main mod class’s constructor:

        // Initialize registries with explicit dependency ordering
        BannerPatternRegistry bannerPatternRegistry = new BannerPatternRegistry(modEventBus);
        BlockEntityTypeRegistry blockEntityTypeRegistry = new BlockEntityTypeRegistry(modEventBus);
        BlockRegistry blockRegistry = new BlockRegistry(modEventBus);
        CreativeTabRegistry creativeTabRegistry = new CreativeTabRegistry(modEventBus);
        DecoratedPotPatternsRegistry decoratedPotPatternsRegistry = new DecoratedPotPatternsRegistry(modEventBus);
        EntityTypeRegistry entityTypeRegistry = new EntityTypeRegistry(modEventBus);
        ItemRegistry itemRegistry = new ItemRegistry(modEventBus);
        LootModifierRegistry lootModifierRegistry = new LootModifierRegistry(modEventBus);
        MenuTypeRegistry menuTypeRegistry = new MenuTypeRegistry(modEventBus);
        PoiTypeRegistry poiTypesRegistry = new PoiTypeRegistry(modEventBus);
        TagRegistry tagRegistry = new TagRegistry();

        bannerPatternRegistry.initialise();
        blockEntityTypeRegistry.initialise(blockRegistry, menuTypeRegistry);
        blockRegistry.initialise(blockEntityTypeRegistry, itemRegistry, menuTypeRegistry);
        creativeTabRegistry.initialise(itemRegistry);
        decoratedPotPatternsRegistry.initialise();
        entityTypeRegistry.initialise(blockRegistry, itemRegistry);
        itemRegistry.initialise(blockRegistry, entityTypeRegistry, tagRegistry);
        lootModifierRegistry.initialise(itemRegistry);
        menuTypeRegistry.initialise();
        poiTypesRegistry.initialise(blockRegistry);
        villagerProfessionRegistry.initialise(poiTypesRegistry);

Notice how I have to first construct all the registries, then have to ensure each registry gets a reference to anything it needs in their initialise() methods. This is now replaced with a single method that doesn’t need to worry about initialisation order or dependencies:

    /**
     * Register the mod-specific registries.
     * @param modEventBus The mod's event bus.
     */
    private void registerRegistries(IEventBus modEventBus) {
        BannerPatternRegistry.BANNER_PATTERNS.register(modEventBus);
        BlockEntityTypeRegistry.BLOCK_ENTITY_TYPES.register(modEventBus);
        BlockRegistry.BLOCKS.register(modEventBus);
        ButterflyEntityTypeRegistry.BUTTERFLY_ENTITY_TYPES.register(modEventBus);
        CreativeTabRegistry.CREATIVE_TABS.register(modEventBus);
        DecoratedPotPatternsRegistry.DECORATED_POT_PATTERNS.register(modEventBus);
        EntityTypeRegistry.ENTITY_TYPES.register(modEventBus);
        ItemRegistry.ITEMS.register(modEventBus);
        LootModifierRegistry.LOOT_MODIFIERS.register(modEventBus);
        MenuTypeRegistry.MENU_TYPES.register(modEventBus);
        PeacemakerEntityTypeRegistry.PEACEMAKER_ENTITY_TYPES.register(modEventBus);
        PoiTypeRegistry.POI_TYPES.register(modEventBus);
        SpawnEggRegistry.SPAWN_EGGS.register(modEventBus);
        VillagerProfessionRegistry.VILLAGER_PROFESSIONS.register(modEventBus);
    }

This is achieved by using the methods recommended in the Forge documentation, methods I shouldn’t have ignored because I thought I knew better. Essentially each Registry class holds a list of constants that are statically initialised. As a simple example, here is my new BlockEntityTypeRegistry class:

/**
 * Registers block entity types.
 */
public class BlockEntityTypeRegistry {

    // An instance of a deferred registry we use to register items.
    public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITY_TYPES;

    // The block entities.
    public static final RegistryObject<BlockEntityType<ButterflyFeederEntity>> BUTTERFLY_FEEDER;

    static {
        BLOCK_ENTITY_TYPES = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, ButterfliesMod.MOD_ID);

        //noinspection DataFlowIssue
        BUTTERFLY_FEEDER = BLOCK_ENTITY_TYPES.register("butterfly_feeder",
                () -> BlockEntityType.Builder.of(ButterflyFeederEntity::new,
                        BlockRegistry.BUTTERFLY_FEEDER.get()).build(null));
    }

    /**
     * Prevent construction.
     */
    private BlockEntityTypeRegistry() {}
}

Now I can access the Butterfly Feeder block entity from anywhere by just using BlockEntityTypeRegistry.BUTTERFLY_FEEDER. I don’t need to add accessors to the class, I don’t need to pass the registry class through other registries down to the constructors of in-game objects. I can just access it anywhere.

This has led to major cleanup in the code since I can now delete all the old accessors, I can make construction of objects a lot simpler, and I’ve removed one or two questionable lines of code I added to force my “cleverer” implementation to work. While this work doesn’t add any new features, it does lead to much more maintainable code and easier work for myself in the future.

Better Peacemaker AI


The AI for Peacemaker mobs wasn’t implemented well. This problem stemmed from the way I was using registries, leading to me having to write code that works around the fact that registries can’t be accessed when goals are supposed to be set up. I ended up adding an extra step, postRegisterGoals() that would register them again later using an external class I named PeacemakerGoalRegistrar. It was clunky, but it worked.

Another problem was that I was using a tag to detect whether or not an entity was a Peacemaker entity. Though data-driven, this approach meant there was an extra file to modify every time I added a new entity.

To fix this I added a new interface with a static method to help check the entity’s type:

/**
 * Interface to indicate an entity is infected with a Peacemaker Butterfly.
 */
public interface PeacemakerEntity {

    /**
     * Checks whether the entity is a Peacemaker Butterfly.
     * @param entity The entity to check.
     * @return True if the entity is a Peacemaker Butterfly.
     */
    static boolean isNotPeacemaker(LivingEntity entity) {
        return !(entity instanceof PeacemakerEntity);
    }
}

It checks if the entity is NOT a Peacemaker entity since that’s what the target goals check for. This meant I could remove the Peacemaker Entity Tag, which means one less resource to keep track of. Instead, I just ensure all Peacemaker entities implement this interface.

With this interface I was also able to move the code from PeacemakerGoalRegistrar into a default method here and delete the original clunky class:

    default void registerPathfinderGoals(PathfinderMob entity) {

        //  Tempt goals
        entity.goalSelector.addGoal(1,
                new TemptGoal(
                        entity,
                        1.25D,
                        Ingredient.of(ItemRegistry.PEACEMAKER_HONEY_BOTTLE.get()),
                        false));

        GoalSelector targetSelector = entity.targetSelector;
        targetSelector.removeAllGoals((x) -> true);
        targetSelector.addGoal(1, (new HurtByTargetGoal(entity, Raider.class))
                .setAlertOthers()
                .setAlertOthers(PeacemakerButterfly.class)
                .setAlertOthers(PeacemakerEvoker.class)
                .setAlertOthers(PeacemakerIllusioner.class)
                .setAlertOthers(PeacemakerPillager.class)
                .setAlertOthers(PeacemakerVindicator.class)
                .setAlertOthers(PeacemakerWitch.class));
        targetSelector.addGoal(2, (new NearestAttackableTargetGoal<>(entity, Player.class, true))
                .setUnseenMemoryTicks(300));
        targetSelector.addGoal(3, (new NearestAttackableTargetGoal<>(entity, AbstractVillager.class, false,
                PeacemakerEntity::isNotPeacemaker)).setUnseenMemoryTicks(300));
        targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(entity, IronGolem.class, false));
    }

You can see in this version I’m accessing the ItemRegistry globally instead of relying on an object being passed down. This is what was preventing this kind of solution in the first place. With this solution I’m now able to add these Peacemaker goals to any class by overriding the registerGoals() method and not having to worry about whether or not I have a reference to the registry yet:

    /**
     * Register Peacemaker-specific goals.
     */
    @Override
    protected void registerGoals() {
        super.registerGoals();
        registerPathfinderGoals(this);
    }

What originally involved modifying the constructor, adding new data members, and having to register goals at a later stage can now all be achieved with only 5 lines of code.

Next Steps


I’m going to carry on working on the Peacemaker Cows. Right now they have a very basic implementation and don’t look great in game. They have no animations, no sounds, and don’t react to things like getting slashed with a sword. They also don’t look amazing – I still have some work to do on their model and textures.

I’m also planning to have a newly generated structure for the Peacemaker Cow to live inside. These will be Butterfly bases, and will act as a Peacemaker Butterfly Spawner as long as the Cow is still alive.

As for refactoring, I also want to look at the way I’ve implemented event handlers. I’ve avoided using @SubscribeEvent following the same logic of avoiding globals, but in hindsight it feels like the Forge-recommended approach is the better one. This doesn’t really cause any major issues, however, so I may hold off on that refactor for a while.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.