From 912f59d9eac8becbed67cf8335595230ab0e6fd2 Mon Sep 17 00:00:00 2001 From: Dynamitos Date: Sat, 25 Oct 2025 22:47:29 +0200 Subject: [PATCH] Implement farming copper golems --- .../mixin/CopperGolemBrainMixin.java | 49 +++++ .../com/dynamitos/mixin/ExampleMixin.java | 15 -- .../mixin/MoveItemsTaskAccessor.java | 78 ++++++++ .../dynamitos/mixin/MoveItemsTaskMixin.java | 188 ++++++++++++++++++ src/main/resources/copper-farmers.mixins.json | 4 +- 5 files changed, 318 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/dynamitos/mixin/CopperGolemBrainMixin.java delete mode 100644 src/main/java/com/dynamitos/mixin/ExampleMixin.java create mode 100644 src/main/java/com/dynamitos/mixin/MoveItemsTaskAccessor.java create mode 100644 src/main/java/com/dynamitos/mixin/MoveItemsTaskMixin.java diff --git a/src/main/java/com/dynamitos/mixin/CopperGolemBrainMixin.java b/src/main/java/com/dynamitos/mixin/CopperGolemBrainMixin.java new file mode 100644 index 0000000..5847176 --- /dev/null +++ b/src/main/java/com/dynamitos/mixin/CopperGolemBrainMixin.java @@ -0,0 +1,49 @@ +package com.dynamitos.mixin; + +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 net.minecraft.block.Blocks; +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(); + } + } + }); + } +} diff --git a/src/main/java/com/dynamitos/mixin/ExampleMixin.java b/src/main/java/com/dynamitos/mixin/ExampleMixin.java deleted file mode 100644 index f2f2b34..0000000 --- a/src/main/java/com/dynamitos/mixin/ExampleMixin.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.dynamitos.mixin; - -import net.minecraft.server.MinecraftServer; -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(MinecraftServer.class) -public class ExampleMixin { - @Inject(at = @At("HEAD"), method = "loadWorld") - private void init(CallbackInfo info) { - // This code is injected into the start of MinecraftServer.loadWorld()V - } -} \ No newline at end of file diff --git a/src/main/java/com/dynamitos/mixin/MoveItemsTaskAccessor.java b/src/main/java/com/dynamitos/mixin/MoveItemsTaskAccessor.java new file mode 100644 index 0000000..b7762bc --- /dev/null +++ b/src/main/java/com/dynamitos/mixin/MoveItemsTaskAccessor.java @@ -0,0 +1,78 @@ +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 new file mode 100644 index 0000000..30b7fe1 --- /dev/null +++ b/src/main/java/com/dynamitos/mixin/MoveItemsTaskMixin.java @@ -0,0 +1,188 @@ +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 { + 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.info("Wrapped findStorage called"); + + Box box = ((MoveItemsTaskAccessor) this).copperfarmers$getSearchBoundingBox(entity); + if (!entity.getEquippedStack(EquipmentSlot.MAINHAND).isOf(Items.WHEAT_SEEDS)) { + return; + } + for (int x = (int) box.minX; x <= box.maxX; x++) { + for (int y = (int) box.minY; y <= box.maxY; y++) { + for (int z = (int) box.minZ; z <= box.maxZ; z++) { + 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.info("Found farmland at " + pos); + 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.info("Wrapped testContainer called"); + if(state.isOf(Blocks.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.info("Wrapped tickInteracting called"); + MoveItemsTaskAccessor thisAccessor = (MoveItemsTaskAccessor)this; + 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.info("Interaction ticks: " + thisAccessor.copperfarmers$getInteractionTicks()); + if (thisAccessor.copperfarmers$getInteractionTicks() >= 60) { + Copperfarmers.LOGGER.info("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.info("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) + ); + ((MoveItemsTaskAccessor)this).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.info("Wrapped isUnchanged called"); + if(storage.state().isOf(Blocks.FARMLAND)) { + callbackInfo.setReturnValue(true); + } + } + + private void placeStack2(PathAwareEntity entity, MoveItemsTask.Storage storage) { + Copperfarmers.LOGGER.info("Wrapped placeStack called"); + MoveItemsTaskAccessor thisAccessor = (MoveItemsTaskAccessor)this; + 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.info("Placed wheat seeds at " + pos.up()); + 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) { + MoveItemsTaskAccessor thisAccessor = (MoveItemsTaskAccessor)this; + 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.info("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) -> ((MoveItemsTaskAccessor)this).copperfarmers$setInteractionState(state); + } +} diff --git a/src/main/resources/copper-farmers.mixins.json b/src/main/resources/copper-farmers.mixins.json index 34b0750..2231456 100644 --- a/src/main/resources/copper-farmers.mixins.json +++ b/src/main/resources/copper-farmers.mixins.json @@ -3,7 +3,9 @@ "package": "com.dynamitos.mixin", "compatibilityLevel": "JAVA_21", "mixins": [ - "ExampleMixin" + "CopperGolemBrainMixin", + "MoveItemsTaskMixin", + "MoveItemsTaskAccessor" ], "injectors": { "defaultRequire": 1