Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@
import java.util.Optional;

public class CrusherBlockEntity extends BlockEntity implements ExtendedScreenHandlerFactory<BlockPos>, ImplementedInventory {
private final DefaultedList<ItemStack> inventory = DefaultedList.ofSize(3, ItemStack.EMPTY);
private final DefaultedList<ItemStack> inventory = DefaultedList.ofSize(4, ItemStack.EMPTY);

private static final int INPUT_SLOT = 0;
private static final int FUEL_SLOT = 1;
private static final int OUTPUT_SLOT = 2;
private static final int AUXILIARY_OUTPUT_SLOT = 3;

protected final PropertyDelegate propertyDelegate;
private int progress = 0;
Expand Down Expand Up @@ -179,27 +180,79 @@ private void updateMaxProgress(World world) {
.orElse(CrusherRecipe.DEFAULT_CRUSHING_TIME);
}

/**
* Determines whether the crusher can perform a craft with the current input.
*
* Checks for a matching CrusherRecipe and verifies that the recipe's primary output and optional auxiliary output can be inserted into the output and auxiliary output slots respectively.
*
* @return `true` if a matching recipe exists and both outputs can be inserted into their target slots, `false` otherwise.
*/
private boolean canCraft() {
Optional<RecipeEntry<CrusherRecipe>> recipe = getCurrentRecipe();
if (recipe.isEmpty()) return false;

ItemStack output = recipe.get().value().getResult(null);
ItemStack outputSlot = inventory.get(OUTPUT_SLOT);
return (outputSlot.isEmpty() || outputSlot.isOf(output.getItem()))
&& outputSlot.getCount() + output.getCount() <= outputSlot.getMaxCount();
CrusherRecipe crusherRecipe = recipe.get().value();
ItemStack output = crusherRecipe.getResult(null);
ItemStack auxiliary = crusherRecipe.auxiliaryOutput().orElse(ItemStack.EMPTY);

return canInsertIntoSlot(OUTPUT_SLOT, output) && canInsertIntoSlot(AUXILIARY_OUTPUT_SLOT, auxiliary);
}

/**
* Determine whether the given ItemStack can be placed into the specified inventory slot
* without violating item compatibility or stack size limits.
*
* @param slot index of the target slot in the block entity's inventory
* @param stack the ItemStack intended for insertion; an empty stack is considered insertable
* @return `true` if the slot can accept the stack (slot empty or same item/component and total count does not exceed the slot's max), `false` otherwise
*/
private boolean canInsertIntoSlot(int slot, ItemStack stack) {
if (stack.isEmpty()) return true;
ItemStack slotStack = inventory.get(slot);
int maxCount = slotStack.isEmpty() ? stack.getMaxCount() : slotStack.getMaxCount();
return (slotStack.isEmpty() || ItemStack.areItemsAndComponentsEqual(slotStack, stack))
&& slotStack.getCount() + stack.getCount() <= maxCount;
}

/**
* Executes the currently matched crusher recipe: adds the recipe's main output to the output slot,
* adds the optional auxiliary output to the auxiliary output slot if present, and consumes one input.
*
* If no matching recipe is available, no changes are made.
*/
private void craftItem() {
Optional<RecipeEntry<CrusherRecipe>> recipe = getCurrentRecipe();
if (recipe.isEmpty()) return;

CrusherRecipe crusherRecipe = recipe.get().value();

// Handle Main Output
insertOrIncrement(OUTPUT_SLOT, crusherRecipe.getResult(null).copy());

// Handle Auxiliary Output
crusherRecipe.auxiliaryOutput().ifPresent(stack -> {
insertOrIncrement(AUXILIARY_OUTPUT_SLOT, stack.copy());
});

inventory.get(INPUT_SLOT).decrement(1);
ItemStack outputSlot = inventory.get(OUTPUT_SLOT);
ItemStack result = recipe.get().value().output().copy();
if (outputSlot.isEmpty()) {
inventory.set(OUTPUT_SLOT, result);
}

/**
* Inserts the provided ItemStack into the specified inventory slot, merging with the existing stack if present.
*
* If `result` is empty this method has no effect. If the target slot is empty the `result` is placed there;
* otherwise the existing stack's count is increased by `result.getCount()`.
*
* @param slot the index of the target inventory slot
* @param result the ItemStack to insert or merge into the slot
*/
private void insertOrIncrement(int slot, ItemStack result) {
if (result.isEmpty()) return;
ItemStack slotStack = inventory.get(slot);
if (slotStack.isEmpty()) {
inventory.set(slot, result);
} else {
outputSlot.increment(result.getCount());
slotStack.increment(result.getCount());
}
}

Expand All @@ -208,11 +261,25 @@ private Optional<RecipeEntry<CrusherRecipe>> getCurrentRecipe() {

}

/**
* Provide the indices of inventory slots that are accessible from the specified side.
*
* @param side the block face from which access is attempted
* @return an array of slot indices; for {@link Direction#DOWN} returns {OUTPUT_SLOT, AUXILIARY_OUTPUT_SLOT}, otherwise returns {INPUT_SLOT, FUEL_SLOT}
*/
@Override
public int[] getAvailableSlots(Direction side) {
return side == Direction.DOWN ? new int[]{OUTPUT_SLOT} : new int[]{INPUT_SLOT, FUEL_SLOT};
return side == Direction.DOWN ? new int[]{OUTPUT_SLOT, AUXILIARY_OUTPUT_SLOT} : new int[]{INPUT_SLOT, FUEL_SLOT};
}

/**
* Determines whether the given ItemStack may be inserted into the specified inventory slot from the provided side.
*
* @param slot the target inventory slot index
* @param stack the ItemStack to insert
* @param side the side from which insertion is attempted; may be null for non-sided access
* @return `true` if insertion is allowed: fuel slot accepts items that provide fuel time, input slot accepts items that match a Crusher recipe; `false` otherwise.
*/
@Override
public boolean canInsert(int slot, ItemStack stack, @Nullable Direction side) {
if (slot == FUEL_SLOT) return getFuelTime(stack) > 0;
Expand All @@ -221,11 +288,24 @@ public boolean canInsert(int slot, ItemStack stack, @Nullable Direction side) {
return false;
}

/**
* Determines whether items may be extracted from the given slot from the specified side.
*
* @param slot the slot index being accessed
* @param stack the stack being extracted
* @param side the side of the block from which extraction is attempted
* @return `true` if the slot is the primary output slot or the auxiliary output slot, `false` otherwise
*/
@Override
public boolean canExtract(int slot, ItemStack stack, Direction side) {
return slot == OUTPUT_SLOT;
return slot == OUTPUT_SLOT || slot == AUXILIARY_OUTPUT_SLOT;
}

/**
* Create a network packet containing this block entity's update data for the client.
*
* @return the update packet for synchronizing this block entity to clients, or {@code null} if no update is required
*/
@Nullable
@Override
public Packet<ClientPlayPacketListener> toUpdatePacket() {
Expand All @@ -242,4 +322,4 @@ public void clear() {
inventory.clear();
markDirty();
}
}
}
60 changes: 49 additions & 11 deletions src/main/java/anya/pizza/houseki/recipe/CrusherRecipe.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package anya.pizza.houseki.recipe;

import java.util.Optional;

import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
Expand All @@ -15,9 +17,33 @@
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.world.World;

public record CrusherRecipe(Ingredient inputItem, ItemStack output, int crushingTime) implements Recipe<CrusherRecipeInput> {
public record CrusherRecipe(Ingredient inputItem, ItemStack output, int crushingTime, Optional<ItemStack> auxiliaryOutput) implements Recipe<CrusherRecipeInput> {
public static final int DEFAULT_CRUSHING_TIME = 200;

public CrusherRecipe {
if (auxiliaryOutput == null) {
auxiliaryOutput = Optional.empty();
}
}

// 2. Secondary Constructor (For DataGen/Old Recipes)
// This allows you to call: new CrusherRecipe(input, output, time)
/**
* Constructs a CrusherRecipe with the specified input, output, and crushing time, and with no auxiliary output.
*
* @param inputItem the ingredient consumed by the recipe
* @param output the primary result produced by the recipe
* @param crushingTime the time required to perform the crushing (in ticks)
*/
public CrusherRecipe(Ingredient inputItem, ItemStack output, int crushingTime) {
this(inputItem, output, crushingTime, Optional.empty());
}

/**
* Gets the recipe's ingredient list.
*
* @return a DefaultedList containing the single input ingredient for this recipe
*/
@Override
public DefaultedList<Ingredient> getIngredients() {
DefaultedList<Ingredient> list = DefaultedList.of();
Expand Down Expand Up @@ -62,17 +88,29 @@ public RecipeType<?> getType() {

public static class Serializer implements RecipeSerializer<CrusherRecipe> {
public static final MapCodec<CrusherRecipe> CODEC = RecordCodecBuilder.mapCodec(inst -> inst.group(
Ingredient.DISALLOW_EMPTY_CODEC.fieldOf("ingredient").forGetter(CrusherRecipe::inputItem),
ItemStack.CODEC.fieldOf("result").forGetter(CrusherRecipe::output),
Codec.INT.optionalFieldOf("crushingTime",DEFAULT_CRUSHING_TIME).forGetter(CrusherRecipe::crushingTime)
).apply(inst, CrusherRecipe::new));
Ingredient.DISALLOW_EMPTY_CODEC.fieldOf("ingredient").forGetter(CrusherRecipe::inputItem),
ItemStack.CODEC.fieldOf("result").forGetter(CrusherRecipe::output),
Codec.INT.optionalFieldOf("crushingTime", DEFAULT_CRUSHING_TIME).forGetter(CrusherRecipe::crushingTime),
// Optional auxiliary output is now the 4th parameter
ItemStack.CODEC.optionalFieldOf("auxiliary_result", ItemStack.EMPTY)
.xmap(Optional::of, opt -> opt.orElse(ItemStack.EMPTY))
.forGetter(CrusherRecipe::auxiliaryOutput)
).apply(inst, CrusherRecipe::new));

public static final PacketCodec<RegistryByteBuf, CrusherRecipe> STREAM_CODEC =
PacketCodec.tuple(
Ingredient.PACKET_CODEC, CrusherRecipe::inputItem,
ItemStack.PACKET_CODEC, CrusherRecipe::output,
PacketCodecs.INTEGER, CrusherRecipe::crushingTime,
CrusherRecipe::new);
PacketCodec.tuple(
Ingredient.PACKET_CODEC, CrusherRecipe::inputItem,
ItemStack.PACKET_CODEC, CrusherRecipe::output,
PacketCodecs.INTEGER, CrusherRecipe::crushingTime,
// Change this line to use OPTIONAL_PACKET_CODEC
PacketCodecs.optional(ItemStack.OPTIONAL_PACKET_CODEC), CrusherRecipe::auxiliaryOutput,
CrusherRecipe::new);

/**
* Returns the map-based codec for serializing and deserializing CrusherRecipe instances.
*
* @return the MapCodec that encodes and decodes CrusherRecipe objects
*/
@Override
public MapCodec<CrusherRecipe> codec() {
return CODEC;
Expand All @@ -84,4 +122,4 @@ public PacketCodec<RegistryByteBuf, CrusherRecipe> packetCodec() {
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,17 @@ public CrusherScreenHandler(int syncId, PlayerInventory inventory, BlockPos pos)
this(syncId, inventory, inventory.player.getWorld().getBlockEntity(pos), new ArrayPropertyDelegate(5));
}

/**
* Creates a Crusher screen handler, initializes the crusher and player inventories, and attaches the provided property delegate for GUI state syncing.
*
* @param syncId window sync id assigned by the client/server
* @param playerInventory the player's inventory to populate player slots and hotbar
* @param blockEntity the block entity whose inventory backs this handler; must be an Inventory of size 4 and is used as a CrusherBlockEntity
* @param arrayPropertyDelegate the PropertyDelegate used to synchronize progress, fuel, and related GUI properties
*/
public CrusherScreenHandler(int syncId, PlayerInventory playerInventory, BlockEntity blockEntity, PropertyDelegate arrayPropertyDelegate) {
super(ModScreenHandlers.CRUSHER_SCREEN_HANDLER, syncId);
checkSize((Inventory) blockEntity, 3);
checkSize((Inventory) blockEntity, 4);
this.inventory = (Inventory) blockEntity;
this.propertyDelegate = arrayPropertyDelegate;
this.blockEntity = (CrusherBlockEntity) blockEntity;
Expand All @@ -38,6 +46,12 @@ public boolean canInsert(ItemStack stack) {
return false; //Makes output slot read-only
}
});
this.addSlot(new Slot(inventory, 3, 130, 30) {
@Override
public boolean canInsert(ItemStack stack) {
return false; //Makes output slot read-only
}
});

addPlayerInventory(playerInventory);
addPlayerHotbar(playerInventory);
Expand Down Expand Up @@ -123,4 +137,4 @@ private void addPlayerHotbar(PlayerInventory playerInventory) {
public PropertyDelegate getPropertyDelegate() {
return propertyDelegate;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,9 @@
"result": {
"id": "houseki:crushed_bauxite"
},
"auxiliary_result": {
"id": "minecraft:iron_nugget",
"count": 1
},
"crushingTime": 250
}