diff --git a/.opencode/plans/minecraft-26-2-migration.md b/.opencode/plans/minecraft-26-2-migration.md new file mode 100644 index 0000000..8aff397 --- /dev/null +++ b/.opencode/plans/minecraft-26-2-migration.md @@ -0,0 +1,356 @@ +# 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 +``` diff --git a/src/client/java/com/dynamitos/mixin/client/ExampleClientMixin.java b/src/client/java/com/dynamitos/mixin/client/ExampleClientMixin.java index d844cc7..b2446e4 100644 --- a/src/client/java/com/dynamitos/mixin/client/ExampleClientMixin.java +++ b/src/client/java/com/dynamitos/mixin/client/ExampleClientMixin.java @@ -1,15 +1,8 @@ package com.dynamitos.mixin.client; -import net.minecraft.client.MinecraftClient; +import net.minecraft.client.Minecraft; 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.CallbackInfo; -@Mixin(MinecraftClient.class) +@Mixin(Minecraft.class) public class ExampleClientMixin { - @Inject(at = @At("HEAD"), method = "run") - private void init(CallbackInfo info) { - // This code is injected into the start of MinecraftClient.run()V - } -} \ No newline at end of file +} diff --git a/src/main/java/com/dynamitos/CopperFarmingRegistry.java b/src/main/java/com/dynamitos/CopperFarmingRegistry.java new file mode 100644 index 0000000..ab3e7d9 --- /dev/null +++ b/src/main/java/com/dynamitos/CopperFarmingRegistry.java @@ -0,0 +1,71 @@ +package com.dynamitos; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; + +public class CopperFarmingRegistry { + private static final List CROP_TYPES = new ArrayList<>(); + + public record CropType(ItemStack seedStack, Predicate predicate) { + public String name() { + return BuiltInRegistries.ITEM.getKey(seedStack.getItem()).toString(); + } + } + + public static void registerCrop(CropType cropType) { + CROP_TYPES.add(cropType); + Copperfarmers.LOGGER.info("Registered crop type: " + cropType.name()); + } + + public static List getCropTypes() { + return List.copyOf(CROP_TYPES); + } + + public static Predicate getFarmlandPredicate() { + return state -> state.is(Blocks.FARMLAND); + } + + public static Predicate getDestinationPredicate() { + return state -> state.is(Blocks.FARMLAND); + } + + public static Predicate getSourcePredicate() { + return state -> state.is(Blocks.FARMLAND); + } + + public static boolean isCropsReadyToHarvest(BlockState state) { + if (!state.is(Blocks.FARMLAND)) { + return false; + } + return true; + } + + static { + CropType wheatCrop = new CropType(new ItemStack(Items.WHEAT_SEEDS), s -> true); + CropType potatoCrop = new CropType(new ItemStack(Items.POTATO), s -> true); + CropType carrotCrop = new CropType(new ItemStack(Items.CARROT), s -> true); + + registerCrop(wheatCrop); + registerCrop(potatoCrop); + registerCrop(carrotCrop); + } + + public static boolean isRegisteredCrop(Item item) { + return CROP_TYPES.stream().anyMatch(c -> c.seedStack.getItem() == item); + } + + public static List getCropSeedStacks() { + return CROP_TYPES.stream() + .map(c -> c.seedStack.copy()) + .toList(); + } +} diff --git a/src/main/java/com/dynamitos/mixin/CopperGolemBehaviorHookMixin.java b/src/main/java/com/dynamitos/mixin/CopperGolemBehaviorHookMixin.java new file mode 100644 index 0000000..c63b740 --- /dev/null +++ b/src/main/java/com/dynamitos/mixin/CopperGolemBehaviorHookMixin.java @@ -0,0 +1,59 @@ +package com.dynamitos.mixin; + +import java.util.function.BiConsumer; + +import org.spongepowered.asm.mixin.Mixin; + +import com.dynamitos.Copperfarmers; +import com.dynamitos.CopperFarmingRegistry; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.Container; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.animal.golem.CopperGolem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; + +@Mixin(targets = "net/minecraft/world/entity/animal/golem/CopperGolemAi") +public abstract class CopperGolemBehaviorHookMixin { + + public static void copperfarmers$handleFarmlandPlanting( + CopperGolem golem, ServerLevel world, BlockPos pos, BlockState state) { + + BlockPos plantedAt = pos.above(); + if (state.is(Blocks.FARMLAND) && canPlantHere(world, plantedAt)) { + ItemStack antennaStack = golem.getItemBySlot(CopperGolem.EQUIPMENT_SLOT_ANTENNA); + if (!antennaStack.isEmpty() && antennaStack.is(Items.WHEAT_SEEDS)) { + world.setBlock(pos, Blocks.FARMLAND.defaultBlockState(), 3); + antennaStack.shrink(1); + Copperfarmers.LOGGER.debug("Planted wheat at " + pos.above()); + } + } + } + + public static void copperfarmers$handleFarmlandHarvesting( + CopperGolem golem, ServerLevel world, BlockPos pos, BlockState state) { + + BlockPos plantedAt = pos.above(); + BlockState above = world.getBlockState(plantedAt); + if (above.is(Blocks.CARROTS)) { + world.destroyBlock(plantedAt, true); + ItemStack harvested = new ItemStack(Items.CARROT); + ItemStack antennaSlot = golem.getItemBySlot(CopperGolem.EQUIPMENT_SLOT_ANTENNA); + if (antennaSlot.isEmpty()) { + golem.setItemSlot(CopperGolem.EQUIPMENT_SLOT_ANTENNA, harvested.copy()); + } else if (antennaSlot.is(harvested.getItem())) { + antennaSlot.grow(harvested.getCount()); + } + Copperfarmers.LOGGER.debug("Harvested crops at " + pos); + } + } + + public static boolean canPlantHere(ServerLevel world, BlockPos plantedAt) { + BlockState above = world.getBlockState(plantedAt); + return above.isAir() || true; + } +} diff --git a/src/main/java/com/dynamitos/mixin/CopperGolemBrainMixin.java b/src/main/java/com/dynamitos/mixin/CopperGolemBrainMixin.java deleted file mode 100644 index 0e481ba..0000000 --- a/src/main/java/com/dynamitos/mixin/CopperGolemBrainMixin.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.dynamitos.mixin; - -import java.util.function.Predicate; - -import org.jetbrains.annotations.Nullable; -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 com.dynamitos.Copperfarmers; - -import net.minecraft.block.Blocks; -import net.minecraft.block.entity.ChestBlockEntity; -import net.minecraft.entity.ai.brain.task.MoveItemsTask; -import net.minecraft.entity.passive.CopperGolemBrain; -import net.minecraft.entity.passive.CopperGolemEntity; -import net.minecraft.entity.passive.CopperGolemState; -import net.minecraft.inventory.Inventory; -import net.minecraft.sound.SoundEvent; - -@Mixin(CopperGolemBrain.class) -public abstract class CopperGolemBrainMixin { - @Inject(method = "createInteractionCallback", at = @At("HEAD"), cancellable = true) - private static void createInteractionCallback(CopperGolemState state, @Nullable SoundEvent soundEvent, - CallbackInfoReturnable callbackInfo) { - callbackInfo.setReturnValue((pathAwareEntity, storage, interactionTicks) -> { - if (pathAwareEntity instanceof CopperGolemEntity copperGolemEntity) { - Inventory inventory = storage.inventory(); - if (interactionTicks == 1) { - if (!storage.state().isOf(Blocks.FARMLAND)) { - inventory.onOpen(copperGolemEntity); - } - copperGolemEntity.setTargetContainerPos(storage.pos()); - copperGolemEntity.setState(state); - } - - if (interactionTicks == 9 && soundEvent != null) { - copperGolemEntity.playSoundIfNotSilent(soundEvent); - } - - if (interactionTicks == 60) { - if (!storage.state().isOf(Blocks.FARMLAND)) { - if (inventory.getViewingUsers().contains(pathAwareEntity)) { - inventory.onClose(copperGolemEntity); - } - } - - copperGolemEntity.resetTargetContainerPos(); - } - } - }); - } - - @Inject(method = "createStoragePredicate", at = @At("HEAD"), cancellable = true) - private static void createStoragePredicate(CallbackInfoReturnable> callbackInfo) { - callbackInfo.setReturnValue(storage -> { - if (storage.state().isOf(Blocks.FARMLAND)) { - Copperfarmers.LOGGER.debug("Storage predicate returning false for farmland at " + storage.pos()); - return false; - } - return storage.blockEntity() instanceof ChestBlockEntity chestBlockEntity ? !chestBlockEntity.getViewingUsers().isEmpty() : false; - }); - } -} diff --git a/src/main/java/com/dynamitos/mixin/FarmlandPredicateMixin.java b/src/main/java/com/dynamitos/mixin/FarmlandPredicateMixin.java new file mode 100644 index 0000000..69886df --- /dev/null +++ b/src/main/java/com/dynamitos/mixin/FarmlandPredicateMixin.java @@ -0,0 +1,32 @@ +package com.dynamitos.mixin; + +import java.util.function.Predicate; + +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 com.dynamitos.CopperFarmingRegistry; + +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; + +@Mixin(targets = "net/minecraft/world/entity/animal/golem/CopperGolemActivitySourcePredicate") +public abstract class FarmlandPredicateMixin { + + @Inject(method = "createStorageSourcePred", at = @At("HEAD"), cancellable = true) + private static void copperfarmers$modifyStorageSourcePred( + CallbackInfoReturnable> callbackInfo) { + Predicate original = callbackInfo.getReturnValue(); + Predicate farmlandAware = state -> + state.is(Blocks.FARMLAND) || (original != null && original.test(state)); + callbackInfo.setReturnValue(farmlandAware); + } + + public static Predicate copperfarmers$createFarmlandPredicate() { + return state -> + state.is(Blocks.FARMLAND) || + CopperFarmingRegistry.getDestinationPredicate().test(state); + } +} diff --git a/src/main/java/com/dynamitos/mixin/MoveItemsTaskAccessor.java b/src/main/java/com/dynamitos/mixin/MoveItemsTaskAccessor.java deleted file mode 100644 index b7762bc..0000000 --- a/src/main/java/com/dynamitos/mixin/MoveItemsTaskAccessor.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.dynamitos.mixin; - -import java.util.function.BiConsumer; -import java.util.function.Predicate; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; -import org.spongepowered.asm.mixin.gen.Invoker; - -import net.minecraft.block.BlockState; -import net.minecraft.entity.ai.brain.task.MoveItemsTask; -import net.minecraft.entity.mob.PathAwareEntity; -import net.minecraft.inventory.Inventory; -import net.minecraft.item.ItemStack; -import net.minecraft.util.math.Box; -import net.minecraft.util.math.Vec3d; -import net.minecraft.world.World; - -@Mixin(MoveItemsTask.class) -public interface MoveItemsTaskAccessor { - @Accessor("interactionState") - void copperfarmers$setInteractionState(MoveItemsTask.InteractionState state); - - @Accessor("interactionTicks") - int copperfarmers$getInteractionTicks(); - - @Accessor("interactionTicks") - void copperfarmers$setInteractionTicks(int ticks); - - @Invoker("getSearchBoundingBox") - Box copperfarmers$getSearchBoundingBox(PathAwareEntity entity); - - @Invoker("atCenterY") - Vec3d copperfarmers$atCenterY(PathAwareEntity entity); - - @Invoker("isWithinRange") - boolean copperfarmers$isWithinRange(double range, MoveItemsTask.Storage storage, World world, PathAwareEntity entity, Vec3d pos); - - @Invoker("transitionToTravelling") - void copperfarmers$transitionToTravelling(PathAwareEntity entity); - - @Invoker("setLookTarget") - void copperfarmers$setLookTarget(MoveItemsTask.Storage storage, PathAwareEntity entity); - - @Invoker("extractStack") - static ItemStack copperfarmers$extractStack(Inventory inventory) { - return null; - } - - @Invoker("resetVisitedPositions") - void copperfarmers$resetVisitedPositions(PathAwareEntity entity); - - @Invoker("insertStack") - static ItemStack copperfarmers$insertStack(PathAwareEntity entity, Inventory inventory) { - return null; - } - - @Invoker("invalidateTargetStorage") - void copperfarmers$invalidateTargetStorage(PathAwareEntity entity); - - @Invoker("canPickUpItem") - static boolean copperfarmers$canPickUpItem(PathAwareEntity entity) { - return false; - } - - @Invoker("hasItem") - static boolean copperfarmers$hasItem(Inventory inventory) { - return false; - } - - @Invoker("canInsert") - static boolean copperfarmers$canInsert(PathAwareEntity entity, Inventory inventory) { - return false; - } - - @Invoker("setNavigationState") - void copperfarmers$setNavigationState(MoveItemsTask.NavigationState state); -} diff --git a/src/main/java/com/dynamitos/mixin/MoveItemsTaskMixin.java b/src/main/java/com/dynamitos/mixin/MoveItemsTaskMixin.java deleted file mode 100644 index 01baea2..0000000 --- a/src/main/java/com/dynamitos/mixin/MoveItemsTaskMixin.java +++ /dev/null @@ -1,206 +0,0 @@ -package com.dynamitos.mixin; - -import java.util.Map; -import java.util.Optional; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Predicate; - -import org.spongepowered.asm.mixin.Debug; -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.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import com.dynamitos.Copperfarmers; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; - -import net.minecraft.block.BlockState; -import net.minecraft.block.Blocks; -import net.minecraft.entity.EquipmentSlot; -import net.minecraft.entity.ai.brain.MemoryModuleState; -import net.minecraft.entity.ai.brain.MemoryModuleType; -import net.minecraft.entity.ai.brain.task.MoveItemsTask; -import net.minecraft.entity.ai.brain.task.MultiTickTask; -import net.minecraft.entity.mob.PathAwareEntity; -import net.minecraft.item.ItemStack; -import net.minecraft.item.Items; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Box; -import net.minecraft.world.World; - -@Mixin(MoveItemsTask.class) -public abstract class MoveItemsTaskMixin extends MultiTickTask { - private MoveItemsTaskAccessor thisAccessor = (MoveItemsTaskAccessor) this; - - public MoveItemsTaskMixin(Map, MemoryModuleState> requiredMemoryState) { - super(requiredMemoryState); - // TODO Auto-generated constructor stub - } - - @Inject(method = "findStorage", at = @At("HEAD"), cancellable = true) - private void findStorage(ServerWorld world, PathAwareEntity entity, - CallbackInfoReturnable> callbackInfo) { - Copperfarmers.LOGGER.debug("Wrapped findStorage called"); - - Box box = ((MoveItemsTaskAccessor) this).copperfarmers$getSearchBoundingBox(entity); - if (!entity.getEquippedStack(EquipmentSlot.MAINHAND).isOf(Items.WHEAT_SEEDS)) { - return; - } - for (int z = (int) box.minZ; z <= box.maxZ; z++) { - for (int y = (int) box.minY; y <= box.maxY; y++) { - for (int x = (int) box.minX; x <= box.maxX; x++) { - var pos = new BlockPos(x, y, z); - var blockState = world.getBlockState(pos); - // if block is farmland - if (blockState.isOf(Blocks.FARMLAND) && world.isAir(pos.up())) { - Copperfarmers.LOGGER.debug("Found farmland at " + pos); - if (Math.random() < 0.5) { - callbackInfo - .setReturnValue( - Optional.of(new MoveItemsTask.Storage(pos, null, null, blockState))); - return; - } - } - } - } - } - } - - @Inject(method = "testContainer", at = @At("HEAD"), cancellable = true) - private void testContainer(PathAwareEntity entity, BlockState state, CallbackInfoReturnable callbackInfo) { - Copperfarmers.LOGGER.debug("Wrapped testContainer called"); - if (!MoveItemsTaskAccessor.copperfarmers$canPickUpItem(entity) && state.isOf(Blocks.FARMLAND)) { - Copperfarmers.LOGGER.debug("testContainer returning true for farmland"); - callbackInfo.setReturnValue(true); - } - } - - @Inject(method = "tickInteracting", at = @At("HEAD"), cancellable = true) - private void tickInteracting(MoveItemsTask.Storage storage, World world, PathAwareEntity entity, - CallbackInfo callbackInfo) { - Copperfarmers.LOGGER.debug("Wrapped tickInteracting called"); - if (!thisAccessor.copperfarmers$isWithinRange(2.0, storage, world, entity, - thisAccessor.copperfarmers$atCenterY(entity))) { - thisAccessor.copperfarmers$transitionToTravelling(entity); - } else { - thisAccessor.copperfarmers$setInteractionTicks(thisAccessor.copperfarmers$getInteractionTicks() + 1); - thisAccessor.copperfarmers$setLookTarget(storage, entity); - Copperfarmers.LOGGER.debug("Interaction ticks: " + thisAccessor.copperfarmers$getInteractionTicks()); - if (thisAccessor.copperfarmers$getInteractionTicks() >= 60) { - Copperfarmers.LOGGER.debug("Interacting with storage at " + storage.pos()); - this.selectInteractionState2( - entity, - storage, - this::takeStack2, - (entityxx, inventory) -> thisAccessor.copperfarmers$invalidateTargetStorage(entity), - this::placeStack2, - (entityxx, inventory) -> thisAccessor.copperfarmers$invalidateTargetStorage(entity)); - thisAccessor.copperfarmers$transitionToTravelling(entity); - } - } - callbackInfo.cancel(); - } - - @Inject(method = "transitionToInteracting", at = @At("HEAD"), cancellable = true) - private void transitionToInteracting(MoveItemsTask.Storage storage, PathAwareEntity entity, - CallbackInfo callbackInfo) { - Copperfarmers.LOGGER.debug("Wrapped transitionToInteracting called"); - this.selectInteractionState2( - entity, - storage, - this.createSetInteractionStateCallback2(MoveItemsTask.InteractionState.PICKUP_ITEM), - this.createSetInteractionStateCallback2(MoveItemsTask.InteractionState.PICKUP_NO_ITEM), - this.createSetInteractionStateCallback2(MoveItemsTask.InteractionState.PLACE_ITEM), - this.createSetInteractionStateCallback2(MoveItemsTask.InteractionState.PLACE_NO_ITEM)); - thisAccessor.copperfarmers$setNavigationState(MoveItemsTask.NavigationState.INTERACTING); - callbackInfo.cancel(); - } - - @Inject(method = "isUnchanged", at = @At("HEAD"), cancellable = true) - private void isUnchanged(World world, MoveItemsTask.Storage storage, CallbackInfoReturnable callbackInfo) { - Copperfarmers.LOGGER.debug("Wrapped isUnchanged called"); - if (storage.state().isOf(Blocks.FARMLAND) && world.isAir(storage.pos().up())) { - Copperfarmers.LOGGER.debug("isUnchanged returning true for farmland at " + storage.pos()); - callbackInfo.setReturnValue(true); - return; - } - Copperfarmers.LOGGER.debug("Storage state: " + storage.state() + " isAir: " + world.isAir(storage.pos().up())); - if (storage.blockEntity() == null) { - Copperfarmers.LOGGER.debug("No blockentity " + storage.pos()); - callbackInfo.setReturnValue(false); - return; - } - } - - private void placeStack2(PathAwareEntity entity, MoveItemsTask.Storage storage) { - Copperfarmers.LOGGER.debug("Wrapped placeStack called"); - BlockPos pos = storage.pos(); - ServerWorld world = (ServerWorld) entity.getEntityWorld(); - if (world.getBlockState(pos).isOf(Blocks.FARMLAND) && world.isAir(pos.up()) - && entity.getEquippedStack(EquipmentSlot.MAINHAND).isOf(Items.WHEAT_SEEDS)) { - world.setBlockState(pos.up(), Blocks.WHEAT.getDefaultState()); - entity.getEquippedStack(EquipmentSlot.MAINHAND).decrement(1); - if (entity.getEquippedStack(EquipmentSlot.MAINHAND).isEmpty()) { - thisAccessor.copperfarmers$resetVisitedPositions(entity); - } else { - thisAccessor.copperfarmers$invalidateTargetStorage(entity); - } - Copperfarmers.LOGGER.debug("Placed wheat seeds at " + pos.up()); - return; - } - if (storage.inventory() == null) { - Copperfarmers.LOGGER.debug("No inventory to place items into at " + pos); - return; - } - ItemStack itemStack = MoveItemsTaskAccessor.copperfarmers$insertStack(entity, storage.inventory()); - storage.inventory().markDirty(); - entity.equipStack(EquipmentSlot.MAINHAND, itemStack); - if (itemStack.isEmpty()) { - thisAccessor.copperfarmers$resetVisitedPositions(entity); - } else { - thisAccessor.copperfarmers$invalidateTargetStorage(entity); - } - } - - private void takeStack2(PathAwareEntity entity, MoveItemsTask.Storage storage) { - entity.equipStack(EquipmentSlot.MAINHAND, - MoveItemsTaskAccessor.copperfarmers$extractStack(storage.inventory())); - entity.setDropGuaranteed(EquipmentSlot.MAINHAND); - storage.inventory().markDirty(); - thisAccessor.copperfarmers$resetVisitedPositions(entity); - } - - private void selectInteractionState2( - PathAwareEntity entity, - MoveItemsTask.Storage storage, - BiConsumer pickupItemCallback, - BiConsumer pickupNoItemCallback, - BiConsumer placeItemCallback, - BiConsumer placeNoItemCallback) { - Copperfarmers.LOGGER.debug("Wrapped selectInteractionState called"); - if (storage == null && entity.getEquippedStack(EquipmentSlot.MAINHAND).isOf(Items.WHEAT_SEEDS)) { - placeItemCallback.accept(entity, storage); - } - if (MoveItemsTaskAccessor.copperfarmers$canPickUpItem(entity)) { - if (MoveItemsTaskAccessor.copperfarmers$hasItem(storage.inventory())) { - pickupItemCallback.accept(entity, storage); - } else { - pickupNoItemCallback.accept(entity, storage); - } - } else if (storage.state().isOf(Blocks.FARMLAND) - || MoveItemsTaskAccessor.copperfarmers$canInsert(entity, storage.inventory())) { - placeItemCallback.accept(entity, storage); - } else { - placeNoItemCallback.accept(entity, storage); - } - } - - private BiConsumer createSetInteractionStateCallback2( - MoveItemsTask.InteractionState state) { - return (entity, inventory) -> thisAccessor.copperfarmers$setInteractionState(state); - } -} diff --git a/src/main/java/com/dynamitos/mixin/TransportItemsBasedOnResearchAccessor.java b/src/main/java/com/dynamitos/mixin/TransportItemsBasedOnResearchAccessor.java new file mode 100644 index 0000000..3977146 --- /dev/null +++ b/src/main/java/com/dynamitos/mixin/TransportItemsBasedOnResearchAccessor.java @@ -0,0 +1,105 @@ +package com.dynamitos.mixin; + +import java.util.Optional; +import java.util.Map; +import java.util.function.Predicate; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; + +import net.minecraft.world.Container; +import net.minecraft.world.entity.PathfinderMob; +import net.minecraft.world.entity.ai.behavior.TransportItemsBetweenContainers; +import net.minecraft.world.item.ItemStack; +import net.minecraft.core.BlockPos; +import net.minecraft.core.GlobalPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.phys.AABB; + +@Mixin(TransportItemsBetweenContainers.class) +public interface TransportItemsBasedOnResearchAccessor { + + /** Access instance field via private setter */ + @Invoker("setInteractionState") + void copperfarmers$setInteractionState(TransportItemsBetweenContainers.ContainerInteractionState state); + + @Invoker("setTransportingState") + void copperfarmers$setTransportingState(TransportItemsBetweenContainers.TransportItemState state); + + /** Access the target search area via private method */ + @Invoker("getTargetSearchArea") + AABB copperfarmers$getTargetSearchArea(PathfinderMob entity); + + /** Access the center Y position via private method */ + @Invoker("atCenterY") + net.minecraft.world.phys.Vec3 copperfarmers$atCenterY(PathfinderMob entity); + + /** Check if target is within distance via private method */ + @Invoker("isWithinTargetDistance") + boolean copperfarmers$isWithinTargetDistance(double distance, + TransportItemsBetweenContainers.TransportItemTarget target, + net.minecraft.world.level.Level world, PathfinderMob entity, net.minecraft.world.phys.Vec3 pos); + + /** Execute travel to target callback (protected method) */ + @Invoker("onTravelToTarget") + void copperfarmers$onTravelToTarget(TransportItemsBetweenContainers.TransportItemTarget target, + net.minecraft.world.level.Level world, PathfinderMob entity); + + /** Get center position for walking via private method */ + @Invoker("getCenterPos") + net.minecraft.world.phys.Vec3 copperfarmers$getCenterPos(PathfinderMob entity); + + /** Pick up items from container (private method) */ + @Invoker("pickUpItems") + void copperfarmers$pickUpItems(PathfinderMob entity, Container container); + + /** Put down item into container (private method) */ + @Invoker("putDownItem") + void copperfarmers$putDownItem(PathfinderMob entity, Container container); + + /** Static helper invoker for extracting items from containers */ + @Invoker("pickupItemFromContainer") + static ItemStack copperfarmers$extractStack(Container inventory) { + return ItemStack.EMPTY; + } + + @Invoker("getVisitedPositions") + void copperfarmers$resetVisitedPositions(PathfinderMob entity); + + /** Static helper invoker for inserting items */ + @Invoker("addItemsToContainer") + static ItemStack copperfarmers$insertStack(PathfinderMob entity, Container container) { + return ItemStack.EMPTY; + } + + @Invoker("invalidateTargetStorage") + void copperfarmers$invalidateTargetStorage(PathfinderMob entity); + + /** Check if the entity can pick up items (static method) */ + @Invoker("isPickingUpItems") + static boolean copperfarmers$canPickUpItem(PathfinderMob entity) { + return false; + } + + @Invoker("hasItem") + static boolean copperfarmers$hasItem(Container inventory) { + return false; + } + + /** Check if target matches the golem's hand item requirement */ + @Invoker("matchesLeavingItemsRequirement") + static boolean copperfarmers$canInsert(PathfinderMob entity, Container container) { + return false; + } + + @Invoker("hasItemMatchingHandItem") + static boolean copperfarmers$hasItemMatchingHandItem(PathfinderMob entity, Container container) { + return false; + } + + /** Get the transport target via private getTransportTarget method */ + @Invoker("getTransportTarget") + Optional copperfarmers$getTransportTarget( + ServerLevel world, PathfinderMob entity); +} diff --git a/src/main/resources/copper-farmers.mixins.json b/src/main/resources/copper-farmers.mixins.json index b732207..20c4ae9 100644 --- a/src/main/resources/copper-farmers.mixins.json +++ b/src/main/resources/copper-farmers.mixins.json @@ -3,12 +3,12 @@ "package": "com.dynamitos.mixin", "compatibilityLevel": "JAVA_25", "mixins": [ - "CopperGolemBrainMixin", - "MoveItemsTaskMixin", - "MoveItemsTaskAccessor" + "CopperGolemBehaviorHookMixin", + "FarmlandPredicateMixin", + "TransportItemsBasedOnResearchAccessor" ], "injectors": { - "defaultRequire": 1 + "defaultRequire": true }, "overwrites": { "requireAnnotations": true