@@ -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
|
||||
```
|
||||
Reference in New Issue
Block a user