# Copper Farmers Migration Plan for Minecraft 26.2 ## Overview Porting the copper-farmers mod from 1.21.x Yarn mappings to Minecraft 26.2 where Yarn classes have been completely removed. The key insight is that both `MoveItemsTask` and `CopperGolemBrain` classes no longer exist in 26.2 - they've been replaced by entirely new class names and architecture. ## What Changed in Minecraft 26.2 ### Class Removals and Replacements - `MoveItemsTask` (entity.ai.brain.task) → **REMOVED** → replaced by `TransportItemsBetweenContainers` (ai.behavior) - `CopperGolemBrain` (entity.passive) → **REMOVED** → replaced by `CopperGolemAi` (animal.golem) - `CopperGolemEntity` → `CopperGolem` (same package: animal.golem) ### Path Package Changes | Old Path | New Path | |---|---| | `net.minecraft.entity.*` | `net.minecraft.world.entity.*` | | `net.minecraft.entity.passive.*` | `net.minecraft.world.entity.animal.golem.*` | | `net.minecraft.block.*` | `net.minecraft.world.level.block.*` | | `net.minecraft.inventory.Inventory` | `net.minecraft.Container` (root pkg) | | `net.minecraft.server.world.ServerWorld` | `net.minecraft.server.level.ServerLevel` | | `net.minecraft.sound.SoundEvent` | `net.minecraft.sounds.SoundEvent` | | `net.minecraft.util.math.Box/Vec3d` | `net.minecraft.world.phys.AABB/Vec3` | | `net.minecraft.block.entity.*` | `net.minecraft.world.level.block.entity.*` | ### Inner Class Changes (on new classes) - `MoveItemsTask.Storage` → `TransportItemTarget` nested in TBC - `.inventory()` renamed to `.container()` - Still has `pos()`, `blockEntity()`, `state()` fields - `MoveItemsTask.InteractionState` → `ContainerInteractionState` (TBC nested) - Same enum values: PICKUP_ITEM, PICKUP_NO_ITEM, PLACE_ITEM, PLACE_NO_ITEM ## Implementation Plan ### Files to Create/Modify 1. **TransportItemsBetweenContainersAccessor.java** (NEW - replaces MoveItemsTaskAccessor.java) - Mixin target: `TransportItemsBetweenContainers` - @Invoker methods for TBC's static helper methods: `pickupItemFromContainer`, `addItemsToContainer`, `isPickingUpItems` - Uses `Container` instead of `Inventory` 2. **FarmlandPredicateMixin.java** (NEW - replaces MoveItemsTaskMixin.java core logic) - Mixin target: `CopperGolemAi` - @WrapOperation on TBC constructor to add farmland support to source/destination predicates - Makes farmland valid for copper golems to interact with 3. **CopperGolemBehaviorHookMixin.java** (NEW - replaces CopperGolemBrainMixin.java) - Mixin target: `CopperGolemAi.getTargetReachedInteractions()` - @ModifyReturnValue to customize how farmland and chests are handled as storage - Handles chest open/close through Container interface on BlockEntity - Custom interaction callback for planting/harvesting 4. **CropRegistry.java** (NEW) - Central registry for what crops copper golems can plant - Default: wheat seeds, potatoes, carrots - API for other mods to register their own crop types as farmland-compatible 5. **copper-farmers.mixins.json** (MODIFY) - Update all class references to new package paths ### Key Technical Points for Farmland Support The golem's behavior controller uses a `TransportItemsBetweenContainers` (TBC) that has: - `sourceBlockType` predicate (what it can extract items FROM) - `destinationBlockType` predicate (what it can insert items INTO) - Both checked via `isWantedBlock(state)` during target search The fix is to intercept TBC construction in CopperGolemAi and inject farmland into both predicates. The interaction callback is then modified to handle the two distinct scenarios: 1. **Container target**: container != null → do item transfer (existing TCB behavior) 2. **Farmland target**: container == null, state == FARMLAND → place seeds / harvest grown crops ## Execution Order 1. Write TransportItemsBetweenContainersAccessor.java 2. Write CropRegistry.java (base registry with wheat/potato/carrot support) 3. Write FarmlandPredicateMixin.java (intercept TBC construction) 4. Write CopperGolemBehaviorHookMixin.java (interaction callbacks + chest detection) 5. Update copper-farmers.mixins.json 6. Verify compilation ## Complete File Contents for Each File ### 1. TransportItemsBetweenContainersAccessor.java (NEW - replaces MoveItemsTaskAccessor.java) Package: `com.dynamitos.mixin` Target: `@Mixin(TransportItemsBasedOnResearch)` ```java package com.dynamitos.mixin; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Invoker; import net.minecraft.Container; // was Inventory import net.minecraft.world.entity.PathfinderMob; // was PathAwareEntity import net.minecraft.world.entity.ai.behavior.TransportItemsBetweenContainers; import net.minecraft.item.ItemStack; @Mixin(TransportItemsBetweenContainers.class) public interface TransportItemsBasedOnResearchAccessor { @Invoker("pickupItemFromContainer") ItemStack copperfarmers$pickupItem(Container inventory); @Invoker("addItemsToContainer") ItemStack copperfarmers$placeItem(PathfinderMob entity, Container inventory); @Invoker("isPickingUpItems") boolean copperfarmers$isPickingUpItems(PathfinderMob entity); @Invoker("matchesGettingItemsRequirement") boolean copperfarmers$matchesGettingItemsRequirement(Container inventory); @Invoker("hasItemMatchingHandItem") boolean copperfarmers$hasItemMatchingHandItem(PathfinderMob entity, Container inventory); @Invoker("getTransportTarget") java.util.Optional copperfarmers$getTransportTarget( net.minecraft.server.level.ServerLevel world, PathfinderMob entity); } ``` ### 2. CropRegistry.java (NEW) Package: `com.dynamitos` ```java package com.dynamitos; import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.item.Items; public class CopperFarmingRegistry { private static final List CROP_TYPES = new ArrayList<>(); public record CropType(Block block, Predicate statePredicate) {} public static void registerCrop(CropType cropType) { CROP_TYPES.add(cropType); Copperfarmers.LOGGER.info("Registered crop type: " + cropType.block()); } /** Returns true if the block is farmland and the stack would be a compatible seed/crop */ public static Predicate getFarmlandPredicate() { return state -> { if (!state.isOf(Blocks.FARMLAND)) { return false; } // Also register other farmable blocks registered by this mod and other mods for (var crop : CROP_TYPES) { if (state.isOf(crop.block())) { return true; } } return false; }; } static { // Register default crops: wheat, potatoes, carrots registerCrop(new CropType(Blocks.WHEAT, null)); registerCrop(new PotatoType(), null); // potato block/planted variant registerCrop(new CarrotType(), null); // carrot block/planted variant } /** Get predicate for farmland destination (what goes on top of farmland) */ public static Predicate getDestinationPredicate() { State -> state.isOf(Blocks.FARMLAND) && !CROP_TYPES.stream().anyMatch(crop -> crop.statePredicate().test(state)); } /** Get predicate for source (what contains items the golem can extract) */ public static Predicate getSourcePredicate() { State -> state.isOf(Blocks.FARMLAND); // farmland is both source and destination } /** Check if a BlockState represents planted crops that are ready to harvest */ public static boolean isCropsReadyToHarvest(BlockState state) { if (!state.isOf(Blocks.FARMLAND)) { return false; } // Check the block above farmland for fully-grown crops // TODO: This needs to match the grow state of wheat/potato/carrot blocks } } ``` ### 3. FarmlandPredicateMixin.java (NEW - replaces MoveItemsTaskMixin.java) Package: `com.dynamitos.mixin` Target: Mixin onto `CopperGolemAi` Key approach: Use @WrapOperation to intercept TBC construction and replace source/destination predicates with farmland-compatible ones. Target the constructor parameters where they are passed. ```java package com.dynamitos.mixin; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.extra.injector.wrapoperation.Operation; import org.spongepowered.asm.mixin.extra.injector.wrapoperation.WrapOperation; import com.dynamitos.CropRegistry; import net.minecraft.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import java.util.function.Predicate; @Mixin(CopperGolemAi.class) public abstract class FarmlandPredicateMixin { /** Wrap the predicate lambdas in CopperGolemAi lambda$static$0 and lambda$static$1 to add farmland support */ @WrapOperation(method = "initCoreActivity", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockState;isOf(Lnet/minecraft/world/level/block/;)Z")) private static boolean copperfarmers$wrapBlockCheck(BlockState $this, Block block, Operation original) { if (block == Blocks.FARMLAND) { return true; // Always accept farmland regardless of what the original predicate checked } return original.call($this, block); } /** Alternative: Use @ModifyVariable on getTargetReachedInteractions to replace interaction map */ private static Predicate copperfarmers$createFarmlandPredicate() { return state -> state.isOf(Blocks.FARMLAND) || CopperFarmingRegistry.getDestinationPredicate().test(state); // Check registered crops } } ``` ### 4. CopperGolemBehaviorHookMixin.java (NEW - replaces CopperGolemBrainMixin.java) Package: `com.dynamitos.mixin` Target: Mixin onto `CopperGolemAi.getTargetReachedInteractions()` Callback now is `TriConsumer` (was previously: `(PathAwareEntity, Storage, int)`). ```java package com.dynamitos.mixin; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.inition.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.extra.injector.ModifyReturnValue; import com.dynamitos.Copperfarmers; import net.minecraft.block.Blocks; import net.minecraft.sounds.SoundEvents; // was net.minecraft.sound.SoundEvent -> Sounds.SoundEvents @Mixin(CopperGolemAi.class) public abstract class CopperGolemBehaviorHookMixin { @ModifyReturnValue(method = "getTargetReachedInteractions") private static Map copperfarmers$modifyTargetReachedInter(Map original) { return state -> { switch (state) { case PLACE_ITEM: // Farmland check - plant seeds if farmland block above is air // Chest check - place item into chest inventory break; case PICKUP_ITEM: // Harvest crops from farmland // Extract item from container break; } return original.get(state); // Fall back to default for unhandled cases }; } /** Handle the actual planting logic (was in MoveItemsTask.placeStack2) */ private static void handleFarmlandPlanting( PathfinderMob golem, TransportItemTarget target, int interactionTicks) { BlockPos pos = target.pos(); World world = golem.getEntityWorld(); // was ServerWorld // Check farmland: state().isOf(FARMLAND) && world.isAir(pos.up()) // Similar logic to original placeStack2 method } /** Handle chest open/close detection (was in moveItemsTaskMixin.tickInteracting) */ private static boolean isContainerOpen(TransportItemTarget target, PathfinderMob golem) { BlockEntity be = target.blockEntity(); if (be instanceof ChestBlockEntity chest) { return !chest.getEntitiesWithContainerOpen().isEmpty(); // was inventory.onViewingUsers() } return false; } } ``` ### 5. copper-farmers.mixins.json (UPDATE) Replace these class references: - `"CopperGolemBrainMixin"` → `"FarmlandPredicateMixin,"CopperGolemBehaviorHookMixin"` (split into two, or combine) - `"MoveItemsTaskMixin"` → removed, replaced with above - `"MoveItemsTaskAccessor"` → `"TransportItemsBasedOnResearchAccessor"` (or just update class name) ### 6. Move Items Between Containers.java (update package paths on imports): **Full list of changes in every source file:** #### MoveItemsTaskAccessor.java → TransportItemsBetweenContainersAccessor - `import net.minecraft.block.BlockState` → `import net.minecraft.world.level.block.state.BlockState;` - `import net.minecraft.entity.ai.brain.task.MoveItemsTask` → `import net.minecraft.world.entity.ai.behavior.TransportItemsBetweenContainers;` - `import net.minecraft.entity.mob.PathAwareEntity` → `import net.minecraft.world.entity.PathfinderMob;` - `import net.minecraft.inventory.Inventory` → `import net.minecraft.Container;` (root package!) - `@Mixin(MoveItemsTask.class)` → `@Mixin(TransportItemsBasedOnResearch.class)` - Inner class refs: - `MoveItemsTask.InteractionState` → `TBC$ContainerInteractionState` or just import the nested type - Method names change as noted above #### CopperGolemBrainMixin.java → behavior hook mixin - `import net.minecraft.block.Blocks` → `net.minecraft.world.level.block.Blocks;` - `import net.minecraft.block.entity.ChestBlockEntity` → `net.minecraft.world.level.block.entity.ChestBlockEntity` - `import net.minecraft.entity.passive.CopperGolemBrain` → **gone** - replaced by CopperGolemAi - `import net.minecraft.entity.passive.CopperGolemEntity` → `net.minecraft.world.entity.animal.golem.CopperGolem;` - `import net.minecraft.entity.passive.CopperGolemState` → `net.minecraft.world.entity.animal.golem.CopperGolemState` (still exists!) - `@Mixin(CopperGolemBrain.class)` → `@Mixin(CopperGolemAi.class)` - Methods: - `createInteractionCallback(...)` → `getTargetReachedInteractions()` (method renamed) - Callback type change: `(PathAwareEntity, Storage, int) → (PathfinderMob, TransportItemTarget, Integer)` #### Key Code Pattern Updates: **Old pattern used by MoveItemsTaskAccessor:** ```java MoveItemsTaskAccessor.copperfarmers$canPickUpItem(entity); // @Invoker on private method entity.getEquippedStack(EquipmentSlot.MAINHAND).isOf(Items.WHEAT_SEEDS); new Storage(pos, null, null, blockState); ``` **New pattern on top of 26.2:** ```java TransportItemsBetweenContainersAccessor.copperfarmers$isPickingUpItems(entity); // Inventory → Container: inventory is now called container() on target target.container(); // Not .inventory() // Storage record was TransportItemTarget: target; // Same record field access: pos(), container(), blockEntity(), state() ``` **Chest detection pattern change:** ```java // old (copperfarmersBrainMixin): inventory.onOpen(copperGolemEntity); // Inventory.onOpen inventory.getViewingUsers(); // Inventory.getViewingUsers() // new: BlockEntity instead of Inventory chestBlockEntity.triggerEvent(ChestBlockEntity.EVENT_SET_OPEN_COUNT, chestBlockEntity.getOpenCount()); chestBlockEntity.getEntitiesWithContainerOpenersCounter().startOpen(chestGolem); // Or similar chestBlockEntity.stopOpen(chestGolem); chestBlockEntity.getEntitiesWithContainerOpen(); // Check who's viewing the container ```