@@ -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<TransportItemsBetweenContainers.TransportItemTarget> 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<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.
|
||||||
|
|
||||||
|
```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<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)`).
|
||||||
|
|
||||||
|
```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<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$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
|
||||||
|
```
|
||||||
@@ -1,15 +1,8 @@
|
|||||||
package com.dynamitos.mixin.client;
|
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.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 {
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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<CropType> CROP_TYPES = new ArrayList<>();
|
||||||
|
|
||||||
|
public record CropType(ItemStack seedStack, Predicate<BlockState> 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<CropType> getCropTypes() {
|
||||||
|
return List.copyOf(CROP_TYPES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Predicate<BlockState> getFarmlandPredicate() {
|
||||||
|
return state -> state.is(Blocks.FARMLAND);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Predicate<BlockState> getDestinationPredicate() {
|
||||||
|
return state -> state.is(Blocks.FARMLAND);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Predicate<BlockState> 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<ItemStack> getCropSeedStacks() {
|
||||||
|
return CROP_TYPES.stream()
|
||||||
|
.map(c -> c.seedStack.copy())
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<MoveItemsTask.InteractionCallback> 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<Predicate<MoveItemsTask.Storage>> 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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<Predicate<BlockState>> callbackInfo) {
|
||||||
|
Predicate<BlockState> original = callbackInfo.getReturnValue();
|
||||||
|
Predicate<BlockState> farmlandAware = state ->
|
||||||
|
state.is(Blocks.FARMLAND) || (original != null && original.test(state));
|
||||||
|
callbackInfo.setReturnValue(farmlandAware);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Predicate<BlockState> copperfarmers$createFarmlandPredicate() {
|
||||||
|
return state ->
|
||||||
|
state.is(Blocks.FARMLAND) ||
|
||||||
|
CopperFarmingRegistry.getDestinationPredicate().test(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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<PathAwareEntity> {
|
|
||||||
private MoveItemsTaskAccessor thisAccessor = (MoveItemsTaskAccessor) this;
|
|
||||||
|
|
||||||
public MoveItemsTaskMixin(Map<MemoryModuleType<?>, 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<Optional<MoveItemsTask.Storage>> 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<Boolean> 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<Boolean> 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<PathAwareEntity, MoveItemsTask.Storage> pickupItemCallback,
|
|
||||||
BiConsumer<PathAwareEntity, MoveItemsTask.Storage> pickupNoItemCallback,
|
|
||||||
BiConsumer<PathAwareEntity, MoveItemsTask.Storage> placeItemCallback,
|
|
||||||
BiConsumer<PathAwareEntity, MoveItemsTask.Storage> 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<PathAwareEntity, MoveItemsTask.Storage> createSetInteractionStateCallback2(
|
|
||||||
MoveItemsTask.InteractionState state) {
|
|
||||||
return (entity, inventory) -> thisAccessor.copperfarmers$setInteractionState(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<TransportItemsBetweenContainers.TransportItemTarget> copperfarmers$getTransportTarget(
|
||||||
|
ServerLevel world, PathfinderMob entity);
|
||||||
|
}
|
||||||
@@ -3,12 +3,12 @@
|
|||||||
"package": "com.dynamitos.mixin",
|
"package": "com.dynamitos.mixin",
|
||||||
"compatibilityLevel": "JAVA_25",
|
"compatibilityLevel": "JAVA_25",
|
||||||
"mixins": [
|
"mixins": [
|
||||||
"CopperGolemBrainMixin",
|
"CopperGolemBehaviorHookMixin",
|
||||||
"MoveItemsTaskMixin",
|
"FarmlandPredicateMixin",
|
||||||
"MoveItemsTaskAccessor"
|
"TransportItemsBasedOnResearchAccessor"
|
||||||
],
|
],
|
||||||
"injectors": {
|
"injectors": {
|
||||||
"defaultRequire": 1
|
"defaultRequire": true
|
||||||
},
|
},
|
||||||
"overwrites": {
|
"overwrites": {
|
||||||
"requireAnnotations": true
|
"requireAnnotations": true
|
||||||
|
|||||||
Reference in New Issue
Block a user