16 KiB
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 byTransportItemsBetweenContainers(ai.behavior)CopperGolemBrain(entity.passive) → REMOVED → replaced byCopperGolemAi(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→TransportItemTargetnested 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
-
TransportItemsBetweenContainersAccessor.java (NEW - replaces MoveItemsTaskAccessor.java)
- Mixin target:
TransportItemsBetweenContainers - @Invoker methods for TBC's static helper methods:
pickupItemFromContainer,addItemsToContainer,isPickingUpItems - Uses
Containerinstead ofInventory
- Mixin target:
-
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
- Mixin target:
-
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
- Mixin target:
-
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
-
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:
sourceBlockTypepredicate (what it can extract items FROM)destinationBlockTypepredicate (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:
- Container target: container != null → do item transfer (existing TCB behavior)
- Farmland target: container == null, state == FARMLAND → place seeds / harvest grown crops
Execution Order
- Write TransportItemsBetweenContainersAccessor.java
- Write CropRegistry.java (base registry with wheat/potato/carrot support)
- Write FarmlandPredicateMixin.java (intercept TBC construction)
- Write CopperGolemBehaviorHookMixin.java (interaction callbacks + chest detection)
- Update copper-farmers.mixins.json
- Verify compilation
Complete File Contents for Each File
1. TransportItemsBetweenContainersAccessor.java (NEW - replaces MoveItemsTaskAccessor.java)
Package: com.dynamitos.mixin
Target: @Mixin(TransportItemsBasedOnResearch)
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<TransportItemsBetweenContainers.TransportItemTarget> copperfarmers$getTransportTarget(
net.minecraft.server.level.ServerLevel world, PathfinderMob entity);
}
2. CropRegistry.java (NEW)
Package: com.dynamitos
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<CropType> CROP_TYPES = new ArrayList<>();
public record CropType(Block block, Predicate<BlockState> 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<BlockState> 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<BlockState> 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<BlockState> 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.
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<Boolean> 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<BlockState> 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<PathfinderMob, TransportItemTarget, Integer> (was previously: (PathAwareEntity, Storage, int)).
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<TransportItemsBetweenContainers.ContainerInteractionState, TransportItemsBasedOnResearch$OnTargetReachedInteraction>
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$ContainerInteractionStateor 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.ChestBlockEntityimport net.minecraft.entity.passive.CopperGolemBrain→ gone - replaced by CopperGolemAiimport 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:
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:
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:
// 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