From 05ca78eb98e896ef76581b41205f1c82f85aaed3 Mon Sep 17 00:00:00 2001 From: Andy Wilson Date: Wed, 28 Jan 2026 13:57:55 +0000 Subject: [PATCH 1/7] initial files for furnace block --- .../java/com/tcm/MineTale/MineTaleClient.java | 6 +- src/main/java/com/tcm/MineTale/MineTale.java | 2 + .../block/workbenches/AbstractWorkbench.java | 65 ++++++- .../block/workbenches/CampfireWorkbench.java | 51 ++++- .../block/workbenches/FurnaceWorkbench.java | 72 +++++++ .../entity/AbstractWorkbenchEntity.java | 71 +++++++ .../entity/CampfireWorkbenchEntity.java | 17 +- .../entity/FurnaceWorkbenchEntity.java | 180 ++++++++++++++++++ .../menu/FurnaceWorkbenchMenu.java | 143 ++++++++++++++ .../screen/FurnaceWorkbenchScreen.java | 46 +++++ .../MineTale/registry/ModBlockEntities.java | 7 + .../com/tcm/MineTale/registry/ModBlocks.java | 10 + .../tcm/MineTale/registry/ModMenuTypes.java | 33 ++++ 13 files changed, 691 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/tcm/MineTale/block/workbenches/FurnaceWorkbench.java create mode 100644 src/main/java/com/tcm/MineTale/block/workbenches/entity/AbstractWorkbenchEntity.java create mode 100644 src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java create mode 100644 src/main/java/com/tcm/MineTale/block/workbenches/menu/FurnaceWorkbenchMenu.java create mode 100644 src/main/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java create mode 100644 src/main/java/com/tcm/MineTale/registry/ModMenuTypes.java diff --git a/src/client/java/com/tcm/MineTale/MineTaleClient.java b/src/client/java/com/tcm/MineTale/MineTaleClient.java index a8a1fdf..d347081 100644 --- a/src/client/java/com/tcm/MineTale/MineTaleClient.java +++ b/src/client/java/com/tcm/MineTale/MineTaleClient.java @@ -1,10 +1,14 @@ package com.tcm.MineTale; +import com.tcm.MineTale.block.workbenches.screen.FurnaceWorkbenchScreen; +import com.tcm.MineTale.registry.ModMenuTypes; + import net.fabricmc.api.ClientModInitializer; +import net.minecraft.client.gui.screens.MenuScreens; public class MineTaleClient implements ClientModInitializer { @Override public void onInitializeClient() { - // This entrypoint is suitable for setting up client-specific logic, such as rendering. + MenuScreens.register(ModMenuTypes.FURNACE_WORKBENCH_MENU, FurnaceWorkbenchScreen::new); } } \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/MineTale.java b/src/main/java/com/tcm/MineTale/MineTale.java index 8bcf4e3..61a2bf2 100644 --- a/src/main/java/com/tcm/MineTale/MineTale.java +++ b/src/main/java/com/tcm/MineTale/MineTale.java @@ -10,6 +10,7 @@ import com.tcm.MineTale.registry.ModEntities; import com.tcm.MineTale.registry.ModEntityDataSerializers; import com.tcm.MineTale.registry.ModItems; +import com.tcm.MineTale.registry.ModMenuTypes; public class MineTale implements ModInitializer { public static final String MOD_ID = "minetale"; @@ -23,6 +24,7 @@ public class MineTale implements ModInitializer { public void onInitialize() { ModBlocks.initialize(); ModBlockEntities.initialize(); + ModMenuTypes.initialize(); ModEntities.initialize(); ModItems.initialize(); diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/AbstractWorkbench.java b/src/main/java/com/tcm/MineTale/block/workbenches/AbstractWorkbench.java index a86396e..53c5534 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/AbstractWorkbench.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/AbstractWorkbench.java @@ -3,7 +3,10 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.util.RandomSource; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.MenuProvider; import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.Level; @@ -18,11 +21,15 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.*; +import net.minecraft.world.phys.BlockHitResult; + import org.jetbrains.annotations.Nullable; +import com.tcm.MineTale.block.workbenches.entity.AbstractWorkbenchEntity; + import java.util.function.Supplier; -public abstract class AbstractWorkbench extends BaseEntityBlock { +public abstract class AbstractWorkbench extends BaseEntityBlock { public static final EnumProperty FACING = HorizontalDirectionalBlock.FACING; public static final EnumProperty HALF = BlockStateProperties.DOUBLE_BLOCK_HALF; public static final EnumProperty TYPE = BlockStateProperties.CHEST_TYPE; @@ -131,4 +138,60 @@ private boolean isCompatiblePart(BlockState current, BlockState neighbor) { protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(FACING, HALF, TYPE); } + + @Nullable + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + // We only want a Block Entity at the 'primary' anchor point of the structure. + // For 1x1: HALF=LOWER, TYPE=SINGLE + // For 2x1: HALF=LOWER, TYPE=LEFT + // For 2x2: HALF=LOWER, TYPE=LEFT + + boolean isLower = state.getValue(HALF) == DoubleBlockHalf.LOWER; + ChestType type = state.getValue(TYPE); + + // If it's the RIGHT side of a wide block, or the UPPER half of a tall block, return null. + if (isLower && (type == ChestType.LEFT || type == ChestType.SINGLE)) { + return this.blockEntityType.get().create(pos, state); + } + + return null; + } + + @Override + protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) { + if (level.isClientSide()) { + return InteractionResult.SUCCESS; + } + + // 1. Find the Master Position (Bottom-Left) + BlockPos masterPos = getMasterPos(state, pos); + BlockEntity blockEntity = level.getBlockEntity(masterPos); + + // 2. Check if the Master block has the MenuProvider trait + if (blockEntity instanceof MenuProvider menuProvider) { + // 3. Open the Screen (this triggers the ScreenHandler/Menu) + player.openMenu(menuProvider); + return InteractionResult.CONSUME; + } + + return InteractionResult.PASS; + } + + public BlockPos getMasterPos(BlockState state, BlockPos pos) { + BlockPos master = pos; + Direction facing = state.getValue(FACING); + + // Move down if we are the upper half + if (state.getValue(HALF) == DoubleBlockHalf.UPPER) { + master = master.below(); + } + + // Move left if we are the right side (relative to facing) + if (state.getValue(TYPE) == ChestType.RIGHT) { + master = master.relative(facing.getCounterClockWise()); + } + + return master; + } } \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/CampfireWorkbench.java b/src/main/java/com/tcm/MineTale/block/workbenches/CampfireWorkbench.java index 2f42631..d13fff2 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/CampfireWorkbench.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/CampfireWorkbench.java @@ -5,21 +5,29 @@ import org.jetbrains.annotations.Nullable; import com.mojang.serialization.MapCodec; +import com.tcm.MineTale.block.workbenches.entity.CampfireWorkbenchEntity; import com.tcm.MineTale.registry.ModBlockEntities; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.RenderShape; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.ChestType; +import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; +import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; // ChestBlock -public class CampfireWorkbench extends AbstractWorkbench { +public class CampfireWorkbench extends AbstractWorkbench { public static final boolean IS_WIDE = false; public static final boolean IS_TALL = false; @@ -31,7 +39,7 @@ public CampfireWorkbench(Properties properties) { super(properties, () -> ModBlockEntities.CAMPFIRE_WORKBENCH_BE, IS_WIDE, IS_TALL); } - public CampfireWorkbench(Properties properties, Supplier> supplier) { + public CampfireWorkbench(Properties properties, Supplier> supplier) { // isWide = false, isTall = false (1x1 footprint) super(properties, supplier, IS_WIDE, IS_TALL); } @@ -48,18 +56,45 @@ public RenderShape getRenderShape(BlockState state) { return RenderShape.MODEL; } + private static final VoxelShape SHAPE = Block.box(0, 0, 0, 16, 7, 16); + + @Override + public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { + return SHAPE; + } + @Nullable @Override public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { - // For a 1x1, we always create the Block Entity at this position. - return this.blockEntityType.get().create(pos, state); + // Only spawn the entity at the "Master" position (LOWER + LEFT or LOWER + SINGLE) + if (state.getValue(HALF) == DoubleBlockHalf.LOWER && state.getValue(TYPE) != ChestType.RIGHT) { + return blockEntityType.get().create(pos, state); + } + return null; } - private static final VoxelShape SHAPE = Block.box(0, 0, 0, 16, 7, 16); - @Override - public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { - return SHAPE; + protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) { + if (level.isClientSide()) return InteractionResult.SUCCESS; + + // BlockPos masterPos = getMasterPos(state, pos); + // BlockEntity be = level.getBlockEntity(masterPos); + + // if (be instanceof AbstractWorkbenchEntity) { + // // Open UI or handle Recycling logic here + // // Example: if player is holding a tool, try to recycle it + // return InteractionResult.CONSUME; + // } + + return InteractionResult.PASS; + } + + public BlockPos getMasterPos(BlockState state, BlockPos pos) { + BlockPos master = pos; + Direction facing = state.getValue(FACING); + if (state.getValue(HALF) == DoubleBlockHalf.UPPER) master = master.below(); + if (state.getValue(TYPE) == ChestType.RIGHT) master = master.relative(facing.getCounterClockWise()); + return master; } } diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/FurnaceWorkbench.java b/src/main/java/com/tcm/MineTale/block/workbenches/FurnaceWorkbench.java new file mode 100644 index 0000000..db1b508 --- /dev/null +++ b/src/main/java/com/tcm/MineTale/block/workbenches/FurnaceWorkbench.java @@ -0,0 +1,72 @@ +package com.tcm.MineTale.block.workbenches; + +import java.util.function.Supplier; + +import org.jetbrains.annotations.Nullable; + +import com.mojang.serialization.MapCodec; +import com.tcm.MineTale.block.workbenches.entity.FurnaceWorkbenchEntity; +import com.tcm.MineTale.registry.ModBlockEntities; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +public class FurnaceWorkbench extends AbstractWorkbench { + // Setting these to true creates the 2x2 multi-block footprint + private static final boolean IS_WIDE = true; + private static final boolean IS_TALL = true; + + public static final MapCodec CODEC = simpleCodec((properties) -> + new FurnaceWorkbench(properties, () -> null)); + + /** + * Standard constructor for registration. + */ + public FurnaceWorkbench(Properties properties) { + super(properties, () -> ModBlockEntities.FURNACE_WORKBENCH_BE, IS_WIDE, IS_TALL); + } + + /** + * Flexible constructor allowing for specialized Block Entity Types. + */ + public FurnaceWorkbench(Properties properties, Supplier> supplier) { + super(properties, supplier, IS_WIDE, IS_TALL); + } + + @Override + public RenderShape getRenderShape(BlockState state) { + // Essential so that the 2x2 model is visible + return RenderShape.MODEL; + } + + @Nullable + @Override + public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { + // Only the Master block (Lower-Left) should tick to process smelting + // This helper ensures the logic only runs on the Server side for our specific BE + return createTickerHelper(type, ModBlockEntities.FURNACE_WORKBENCH_BE, (lvl, pos, st, be) -> { + if (be instanceof FurnaceWorkbenchEntity furnace) { + furnace.tick(lvl, pos, st); + } + }); + } + + @Nullable + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + // AbstractWorkbench logic ensures only the Master block gets the entity. + // We override it here to point specifically to our Furnace entity. + return super.newBlockEntity(pos, state); + } + + @Override + protected MapCodec codec() { + return CODEC; + } + +} diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/entity/AbstractWorkbenchEntity.java b/src/main/java/com/tcm/MineTale/block/workbenches/entity/AbstractWorkbenchEntity.java new file mode 100644 index 0000000..7b05f5d --- /dev/null +++ b/src/main/java/com/tcm/MineTale/block/workbenches/entity/AbstractWorkbenchEntity.java @@ -0,0 +1,71 @@ +package com.tcm.MineTale.block.workbenches.entity; + +import java.util.ArrayList; +import java.util.List; + +import org.jspecify.annotations.Nullable; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +public abstract class AbstractWorkbenchEntity extends BlockEntity implements MenuProvider { + protected int tier = 1; + protected double scanRadius = 5.0; + + public AbstractWorkbenchEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); + } + + // --- TIER SYSTEM --- + public int getTier() { return tier; } + public void setTier(int tier) { this.tier = tier; setChanged(); } + + // --- CHEST SCANNING --- + public List getNearbyInventories() { + List inventories = new ArrayList<>(); + BlockPos.betweenClosed( + worldPosition.offset((int)-scanRadius, -2, (int)-scanRadius), + worldPosition.offset((int)scanRadius, 2, (int)scanRadius) + ).forEach(pos -> { + BlockEntity be = level.getBlockEntity(pos); + if (be instanceof Container container) { + inventories.add(container); + } + }); + + // Prioritization: Sort by proximity to prevent "chest prioritization" issues + inventories.sort((a, b) -> { + double distA = ((BlockEntity)a).getBlockPos().distSqr(this.worldPosition); + double distB = ((BlockEntity)b).getBlockPos().distSqr(this.worldPosition); + return Double.compare(distA, distB); + }); + + return inventories; + } + + // --- RECYCLER LOGIC --- + public void attemptRecycle(ItemStack stack) { + // Logic to break down stack.getItem() and return components + // to nearby chests or drop them at worldPosition. + } + + @Nullable + @Override + public abstract AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player); + + @Override + public Component getDisplayName() { + return Component.translatable(this.getBlockState().getBlock().getDescriptionId()); + } + + +} diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/entity/CampfireWorkbenchEntity.java b/src/main/java/com/tcm/MineTale/block/workbenches/entity/CampfireWorkbenchEntity.java index 024935e..40dc1ee 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/entity/CampfireWorkbenchEntity.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/entity/CampfireWorkbenchEntity.java @@ -1,13 +1,26 @@ package com.tcm.MineTale.block.workbenches.entity; +import org.jspecify.annotations.Nullable; + import com.tcm.MineTale.registry.ModBlockEntities; import net.minecraft.core.BlockPos; -import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.level.block.state.BlockState; -public class CampfireWorkbenchEntity extends BlockEntity { +public class CampfireWorkbenchEntity extends AbstractWorkbenchEntity { public CampfireWorkbenchEntity(BlockPos blockPos, BlockState blockState) { super(ModBlockEntities.CAMPFIRE_WORKBENCH_BE, blockPos, blockState); + + this.scanRadius = 6.0; + this.tier = 1; + } + + @Override + public @Nullable AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'createMenu'"); } } diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java b/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java new file mode 100644 index 0000000..8e6b147 --- /dev/null +++ b/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java @@ -0,0 +1,180 @@ +package com.tcm.MineTale.block.workbenches.entity; + +import java.util.List; + +import org.jspecify.annotations.Nullable; + +import com.mojang.serialization.Codec; +import com.tcm.MineTale.block.workbenches.menu.FurnaceWorkbenchMenu; +import com.tcm.MineTale.registry.ModBlockEntities; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.Container; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerData; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; + +public class FurnaceWorkbenchEntity extends AbstractWorkbenchEntity { + // Inventory Mapping: + // 0 -> Fuel Slots + // 1, 2 -> Input Slot + // 3-6 -> Output Slots + private final SimpleContainer inventory = new SimpleContainer(7); + + private int cookTime; + private int cookTimeTotal = 200; + private int fuelTime; + + protected final ContainerData data = new ContainerData() { + @Override + public int get(int index) { + return switch (index) { + case 0 -> fuelTime; + case 1 -> 100; // Fuel total + case 2 -> cookTime; + case 3 -> cookTimeTotal; + default -> 0; + }; + } + + @Override + public void set(int index, int value) { + switch (index) { + case 0 -> fuelTime = value; + case 2 -> cookTime = value; + } + } + + @Override + public int getCount() { + return 4; + } + }; + + public FurnaceWorkbenchEntity(BlockPos pos, BlockState state) { + super(ModBlockEntities.FURNACE_WORKBENCH_BE, pos, state); + this.scanRadius = 8.0; + } + + public void tick(Level level, BlockPos pos, BlockState state) { + if (level.isClientSide()) return; + + boolean changed = false; + ItemStack input = inventory.getItem(0); + ItemStack fuel = inventory.getItem(1); + + // TRAIT: Streamline crafting by pulling from nearby chests if input is empty + if (input.isEmpty() && level.getGameTime() % 20 == 0) { + pullFromNearbyChests(); + } + + if (canSmelt(input)) { + // TRAIT: Upgrade System - Higher tier = faster smelting + // Tier 1: 200 ticks, Tier 2: 150 ticks, Tier 3: 100 ticks... + int speedBoost = (this.tier - 1) * 50; + int currentTotal = Math.max(20, this.cookTimeTotal - speedBoost); + + if (fuelTime > 0 || !fuel.isEmpty()) { + if (fuelTime <= 0 && consumeFuel(fuel)) { + changed = true; + } + + if (fuelTime > 0) { + fuelTime--; + cookTime++; + if (cookTime >= currentTotal) { + smeltItem(input); + cookTime = 0; + changed = true; + } + } + } + } else { + cookTime = 0; + } + + if (changed) setChanged(); + } + + private boolean canSmelt(ItemStack input) { + if (input.isEmpty()) return false; + // Logic: Check if it's an ore (Copper to Adamantite) or Logs for Charcoal + return isOre(input) || isWood(input); + } + + private boolean consumeFuel(ItemStack fuel) { + // TRAIT: Use fibres (string), sticks, or logs + if (fuel.is(Items.STICK) || fuel.is(Items.STRING) || isWood(fuel)) { + this.fuelTime = 100; // Assign burn time + fuel.shrink(1); + return true; + } + return false; + } + + private void smeltItem(ItemStack input) { + ItemStack result; + // TRAIT: Logs yield Charcoal + if (isWood(input)) { + result = new ItemStack(Items.CHARCOAL); + } else { + // Placeholder: Replace with your actual Ore-to-Ingot logic + result = new ItemStack(Items.COPPER_INGOT); + } + + ItemStack output = inventory.getItem(2); + if (output.isEmpty()) { + inventory.setItem(2, result.copy()); + } else if (ItemStack.isSameItem(output, result)) { + output.grow(result.getCount()); + } + input.shrink(1); + } + + private void pullFromNearbyChests() { + List nearby = this.getNearbyInventories(); + for (Container chest : nearby) { + for (int i = 0; i < chest.getContainerSize(); i++) { + ItemStack stack = chest.getItem(i); + if (isOre(stack) || isWood(stack)) { + inventory.setItem(0, stack.split(1)); + chest.setChanged(); + return; + } + } + } + } + + // --- Helpers --- + private boolean isOre(ItemStack stack) { return stack.is(Items.RAW_COPPER); /* Add more ores */ } + private boolean isWood(ItemStack stack) { return stack.getItem().toString().contains("log"); } + + @Override + protected void saveAdditional(ValueOutput valueOutput) { + super.saveAdditional(valueOutput); + // store() uses Codecs for type safety + valueOutput.store("WorkbenchTier", Codec.INT, this.tier); + valueOutput.store("ScanRadius", Codec.DOUBLE, this.scanRadius); + } + + @Override + protected void loadAdditional(ValueInput valueInput) { + super.loadAdditional(valueInput); + // read() returns an Optional + this.tier = valueInput.read("WorkbenchTier", Codec.INT).orElse(1); + this.scanRadius = valueInput.read("ScanRadius", Codec.DOUBLE).orElse(5.0); + } + + @Override + public @Nullable AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) { + return new FurnaceWorkbenchMenu(syncId, playerInventory, this.inventory, this.data); + } +} diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/menu/FurnaceWorkbenchMenu.java b/src/main/java/com/tcm/MineTale/block/workbenches/menu/FurnaceWorkbenchMenu.java new file mode 100644 index 0000000..9f08f9b --- /dev/null +++ b/src/main/java/com/tcm/MineTale/block/workbenches/menu/FurnaceWorkbenchMenu.java @@ -0,0 +1,143 @@ +package com.tcm.MineTale.block.workbenches.menu; + +import com.tcm.MineTale.registry.ModMenuTypes; + +import net.minecraft.world.Container; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerData; +import net.minecraft.world.inventory.FurnaceResultSlot; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; + +public class FurnaceWorkbenchMenu extends AbstractContainerMenu { + private final Container container; + private final ContainerData data; + + private final int containerSize = 7; + + // Slot Constants + private static final int FUEL_SLOT = 0; + private static final int INPUT_1 = 1; + private static final int INPUT_2 = 2; + private static final int OUTPUT_START = 3; + private static final int OUTPUT_END = 6; + + // This constructor is used by the Client + public FurnaceWorkbenchMenu(int syncId, Inventory playerInventory) { + this(syncId, playerInventory, new SimpleContainer(7), new SimpleContainerData(4)); + } + + // This constructor is used by the Server (via the BlockEntity) + public FurnaceWorkbenchMenu(int syncId, Inventory playerInventory, Container container, ContainerData data) { + super(ModMenuTypes.FURNACE_WORKBENCH_MENU, syncId); + checkContainerSize(container, containerSize); + this.container = container; + this.data = data; + + container.startOpen(playerInventory.player); + + // --- FURNACE SLOTS --- + + // 1. Fuel Slot (Center-ish bottom) + this.addSlot(new Slot(container, FUEL_SLOT, 80, 53) { + @Override + public boolean mayPlace(ItemStack stack) { + // Trait: Sticks, Fibres (String), and Logs + return stack.is(Items.STICK) || stack.is(Items.STRING) || stack.getItem().toString().contains("log"); + } + }); + + // 2. Two Input Slots (Stacked on the left) + this.addSlot(new Slot(container, INPUT_1, 44, 17)); + this.addSlot(new Slot(container, INPUT_2, 44, 35)); + + // 3. Four Output Slots (2x2 Grid on the right) + this.addSlot(new FurnaceResultSlot(playerInventory.player, container, 3, 116, 21)); + this.addSlot(new FurnaceResultSlot(playerInventory.player, container, 4, 134, 21)); + this.addSlot(new FurnaceResultSlot(playerInventory.player, container, 5, 116, 39)); + this.addSlot(new FurnaceResultSlot(playerInventory.player, container, 6, 134, 39)); + + // --- PLAYER INVENTORY --- + addPlayerInventory(playerInventory); + addPlayerHotbar(playerInventory); + + // Sync the cooking/fuel progress bars + this.addDataSlots(data); + } + + @Override + public ItemStack quickMoveStack(Player player, int index) { + ItemStack itemStack = ItemStack.EMPTY; + Slot slot = this.slots.get(index); + if (slot != null && slot.hasItem()) { + ItemStack itemStack2 = slot.getItem(); + itemStack = itemStack2.copy(); + + // From Furnace to Player + if (index < 7) { + if (!this.moveItemStackTo(itemStack2, 7, 43, true)) { + return ItemStack.EMPTY; + } + } + // From Player to Furnace + else { + // If it's fuel, try fuel slot + if (isFuel(itemStack2)) { + if (!this.moveItemStackTo(itemStack2, 0, 1, false)) return ItemStack.EMPTY; + } + // Otherwise, try inputs + else if (!this.moveItemStackTo(itemStack2, 1, 3, false)) { + return ItemStack.EMPTY; + } + } + + if (itemStack2.isEmpty()) { + slot.setByPlayer(ItemStack.EMPTY); + } else { + slot.setChanged(); + } + } + return itemStack; + } + + private boolean isFuel(ItemStack stack) { + return stack.is(Items.STICK) || stack.is(Items.STRING) || stack.getItem().toString().contains("log"); + } + + @Override + public boolean stillValid(Player player) { + return this.container.stillValid(player); + } + + private void addPlayerInventory(Inventory playerInventory) { + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 9; ++j) { + this.addSlot(new Slot(playerInventory, j + i * 9 + 9, 8 + j * 18, 84 + i * 18)); + } + } + } + + private void addPlayerHotbar(Inventory playerInventory) { + for (int i = 0; i < 9; ++i) { + this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 142)); + } + } + + // Methods for the Screen to use for rendering progress bars + public int getCookProgress() { + int i = this.data.get(2); // cookTime + int j = this.data.get(3); // cookTimeTotal + return j != 0 && i != 0 ? i * 24 / j : 0; + } + + public int getBurnProgress() { + int i = this.data.get(1); // fuelTimeTotal + if (i == 0) i = 200; + return this.data.get(0) * 13 / i; // fuelTime + } +} diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java b/src/main/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java new file mode 100644 index 0000000..0589705 --- /dev/null +++ b/src/main/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java @@ -0,0 +1,46 @@ +package com.tcm.MineTale.block.workbenches.screen; + +import com.tcm.MineTale.MineTale; +import com.tcm.MineTale.block.workbenches.menu.FurnaceWorkbenchMenu; + +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.resources.Identifier; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.network.chat.Component; + +// FurnaceBlockEntity +// AbstractFurnaceMenu +// FurnaceMenu +// FurnaceScreen +// AbstractFurnaceScreen + +public class FurnaceWorkbenchScreen extends AbstractContainerScreen { + private static final Identifier TEXTURE = + // Identifier.fromNamespaceAndPath(MineTale.MOD_ID, "textures/gui/container/furnace_workbench.png"); + Identifier.withDefaultNamespace("textures/gui/container/furnace.png"); + + public FurnaceWorkbenchScreen(FurnaceWorkbenchMenu menu, Inventory inventory, Component title) { + super(menu, inventory, title); + } + + @Override + protected void init() { + super.init(); + this.titleLabelX = (this.imageWidth - this.font.width(this.title)) / 2; + } + + protected void renderBg(GuiGraphics guiGraphics, float f, int i, int j) { + int k = this.leftPos; + int l = this.topPos; + guiGraphics.blit(RenderPipelines.GUI_TEXTURED, TEXTURE, k, l, 0.0F, 0.0F, this.imageWidth, this.imageHeight, 256, 256); + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + renderBackground(graphics, mouseX, mouseY, delta); + super.render(graphics, mouseX, mouseY, delta); + renderTooltip(graphics, mouseX, mouseY); + } +} \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/registry/ModBlockEntities.java b/src/main/java/com/tcm/MineTale/registry/ModBlockEntities.java index 8b4102f..9571b9f 100644 --- a/src/main/java/com/tcm/MineTale/registry/ModBlockEntities.java +++ b/src/main/java/com/tcm/MineTale/registry/ModBlockEntities.java @@ -2,6 +2,7 @@ import com.tcm.MineTale.MineTale; import com.tcm.MineTale.block.workbenches.entity.CampfireWorkbenchEntity; +import com.tcm.MineTale.block.workbenches.entity.FurnaceWorkbenchEntity; import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; import net.minecraft.core.Registry; @@ -19,6 +20,12 @@ public class ModBlockEntities { ModBlocks.CAMPFIRE_WORKBENCH_BLOCK ); + public static final BlockEntityType FURNACE_WORKBENCH_BE = register( + "furnace_workbench_be", + FurnaceWorkbenchEntity::new, + ModBlocks.FURNACE_WORKBENCH_BLOCK + ); + public static void initialize() { System.out.println("Registered Mod Entities for " + MineTale.MOD_ID); diff --git a/src/main/java/com/tcm/MineTale/registry/ModBlocks.java b/src/main/java/com/tcm/MineTale/registry/ModBlocks.java index f94d914..fa0293b 100644 --- a/src/main/java/com/tcm/MineTale/registry/ModBlocks.java +++ b/src/main/java/com/tcm/MineTale/registry/ModBlocks.java @@ -4,6 +4,7 @@ import com.tcm.MineTale.MineTale; import com.tcm.MineTale.block.workbenches.CampfireWorkbench; +import com.tcm.MineTale.block.workbenches.FurnaceWorkbench; import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents; import net.minecraft.core.Registry; @@ -26,11 +27,20 @@ public class ModBlocks { BlockBehaviour.Properties.of().sound(SoundType.WOOD), true ); + + public static final Block FURNACE_WORKBENCH_BLOCK = register( + "furnace_workbench_block", + FurnaceWorkbench::new, + BlockBehaviour.Properties.of().sound(SoundType.WOOD), + true + ); public static void initialize() { ItemGroupEvents.modifyEntriesEvent(CreativeModeTabs.FUNCTIONAL_BLOCKS).register(entries -> { entries.accept(CAMPFIRE_WORKBENCH_BLOCK); + entries.accept(FURNACE_WORKBENCH_BLOCK); }); + System.out.println("Registered Mod Blocks for " + MineTale.MOD_ID); } diff --git a/src/main/java/com/tcm/MineTale/registry/ModMenuTypes.java b/src/main/java/com/tcm/MineTale/registry/ModMenuTypes.java new file mode 100644 index 0000000..05d3b88 --- /dev/null +++ b/src/main/java/com/tcm/MineTale/registry/ModMenuTypes.java @@ -0,0 +1,33 @@ +package com.tcm.MineTale.registry; + +import com.tcm.MineTale.MineTale; +import com.tcm.MineTale.block.workbenches.menu.FurnaceWorkbenchMenu; + +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.Identifier; +import net.minecraft.world.flag.FeatureFlags; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MenuType; + +public class ModMenuTypes { + + public static final MenuType FURNACE_WORKBENCH_MENU = register( + "furnace_workbench_menu", + FurnaceWorkbenchMenu::new + ); + + public static void initialize() { + // Just used to trigger the static registration + System.out.println("Registered Mod Menus for " + MineTale.MOD_ID); + } + + private static MenuType register(String name, MenuType.MenuSupplier factory) { + Identifier id = Identifier.fromNamespaceAndPath(MineTale.MOD_ID, name); + return Registry.register( + BuiltInRegistries.MENU, + id, + new MenuType<>(factory, FeatureFlags.DEFAULT_FLAGS) + ); + } +} From ff1522141a102d22662bb5497a895929f39675bf Mon Sep 17 00:00:00 2001 From: Andy Wilson Date: Wed, 28 Jan 2026 14:05:21 +0000 Subject: [PATCH 2/7] fix: bump fabric version --- build.gradle | 2 +- gradle.properties | 14 +++++++------- .../workbenches/screen/FurnaceWorkbenchScreen.java | 1 - 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index 45f35cc..f135729 100644 --- a/build.gradle +++ b/build.gradle @@ -37,7 +37,7 @@ dependencies { modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" // Fabric API. This is technically optional, but you probably want it anyway. - modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}" } diff --git a/gradle.properties b/gradle.properties index c0ce71e..fa65e5b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # Done to increase the memory available to gradle. -org.gradle.jvmargs=-Xmx1G +org.gradle.jvmargs=-Xmx4G org.gradle.parallel=true # IntelliJ IDEA is not yet fully compatible with configuration cache, see: https://github.com/FabricMC/fabric-loom/issues/1349 @@ -8,13 +8,13 @@ org.gradle.configuration-cache=false # Fabric Properties # check these on https://fabricmc.net/develop minecraft_version=1.21.11 -loader_version=0.18.2 -loom_version=1.14-SNAPSHOT +loader_version=0.18.4 +loom_version=1.15-SNAPSHOT + +# Fabric API +fabric_api_version=0.141.2+1.21.11 # Mod Properties mod_version=1.0.0 maven_group=com.tcm -archives_base_name=MineTale - -# Dependencies -fabric_version=0.139.4+1.21.11 \ No newline at end of file +archives_base_name=MineTale \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java b/src/main/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java index 0589705..d9f4cec 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java @@ -1,6 +1,5 @@ package com.tcm.MineTale.block.workbenches.screen; -import com.tcm.MineTale.MineTale; import com.tcm.MineTale.block.workbenches.menu.FurnaceWorkbenchMenu; import net.minecraft.client.gui.GuiGraphics; From e5f7ae5d3963d6e653da578cbf7e27b63128167b Mon Sep 17 00:00:00 2001 From: Andy Wilson Date: Wed, 28 Jan 2026 16:12:01 +0000 Subject: [PATCH 3/7] fix: move screen into client directory --- .../block/workbenches/screen/FurnaceWorkbenchScreen.java | 6 ------ 1 file changed, 6 deletions(-) rename src/{main => client}/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java (93%) diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java b/src/client/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java similarity index 93% rename from src/main/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java rename to src/client/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java index d9f4cec..ef753f6 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java +++ b/src/client/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java @@ -9,12 +9,6 @@ import net.minecraft.world.entity.player.Inventory; import net.minecraft.network.chat.Component; -// FurnaceBlockEntity -// AbstractFurnaceMenu -// FurnaceMenu -// FurnaceScreen -// AbstractFurnaceScreen - public class FurnaceWorkbenchScreen extends AbstractContainerScreen { private static final Identifier TEXTURE = // Identifier.fromNamespaceAndPath(MineTale.MOD_ID, "textures/gui/container/furnace_workbench.png"); From 32cd980ea4ac29d48273d79c9337843f6d6765cd Mon Sep 17 00:00:00 2001 From: Andy Wilson Date: Wed, 28 Jan 2026 16:34:42 +0000 Subject: [PATCH 4/7] fix: AI Comments --- .../block/workbenches/FurnaceWorkbench.java | 3 +- .../entity/AbstractWorkbenchEntity.java | 3 ++ .../entity/FurnaceWorkbenchEntity.java | 31 ++++++++++++++++--- .../menu/FurnaceWorkbenchMenu.java | 15 ++++----- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/FurnaceWorkbench.java b/src/main/java/com/tcm/MineTale/block/workbenches/FurnaceWorkbench.java index db1b508..ebec50c 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/FurnaceWorkbench.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/FurnaceWorkbench.java @@ -21,8 +21,7 @@ public class FurnaceWorkbench extends AbstractWorkbench private static final boolean IS_WIDE = true; private static final boolean IS_TALL = true; - public static final MapCodec CODEC = simpleCodec((properties) -> - new FurnaceWorkbench(properties, () -> null)); + public static final MapCodec CODEC = simpleCodec(FurnaceWorkbench::new); /** * Standard constructor for registration. diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/entity/AbstractWorkbenchEntity.java b/src/main/java/com/tcm/MineTale/block/workbenches/entity/AbstractWorkbenchEntity.java index 7b05f5d..811a9c6 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/entity/AbstractWorkbenchEntity.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/entity/AbstractWorkbenchEntity.java @@ -32,6 +32,9 @@ public AbstractWorkbenchEntity(BlockEntityType type, BlockPos pos, BlockState // --- CHEST SCANNING --- public List getNearbyInventories() { List inventories = new ArrayList<>(); + if (level == null) { + return inventories; + } BlockPos.betweenClosed( worldPosition.offset((int)-scanRadius, -2, (int)-scanRadius), worldPosition.offset((int)scanRadius, 2, (int)scanRadius) diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java b/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java index 8e6b147..8389198 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java @@ -27,6 +27,11 @@ public class FurnaceWorkbenchEntity extends AbstractWorkbenchEntity { // 0 -> Fuel Slots // 1, 2 -> Input Slot // 3-6 -> Output Slots + private static final int FUEL_SLOT = 0; + private static final int INPUT_1 = 1; + private static final int INPUT_2 = 2; + private static final int OUTPUT_START = 3; + private static final int OUTPUT_END = 6; private final SimpleContainer inventory = new SimpleContainer(7); private int cookTime; @@ -68,8 +73,10 @@ public void tick(Level level, BlockPos pos, BlockState state) { if (level.isClientSide()) return; boolean changed = false; - ItemStack input = inventory.getItem(0); - ItemStack fuel = inventory.getItem(1); + ItemStack fuel = inventory.getItem(FUEL_SLOT); + ItemStack input = !inventory.getItem(INPUT_1).isEmpty() + ? inventory.getItem(INPUT_1) + : inventory.getItem(INPUT_2); // TRAIT: Streamline crafting by pulling from nearby chests if input is empty if (input.isEmpty() && level.getGameTime() % 20 == 0) { @@ -130,7 +137,10 @@ private void smeltItem(ItemStack input) { result = new ItemStack(Items.COPPER_INGOT); } - ItemStack output = inventory.getItem(2); + int outputSlot = findOutputSlot(result); + if (outputSlot == -1) return; + ItemStack output = inventory.getItem(outputSlot); + if (output.isEmpty()) { inventory.setItem(2, result.copy()); } else if (ItemStack.isSameItem(output, result)) { @@ -145,7 +155,9 @@ private void pullFromNearbyChests() { for (int i = 0; i < chest.getContainerSize(); i++) { ItemStack stack = chest.getItem(i); if (isOre(stack) || isWood(stack)) { - inventory.setItem(0, stack.split(1)); + int inputSlot = inventory.getItem(INPUT_1).isEmpty() ? INPUT_1 : (inventory.getItem(INPUT_2).isEmpty() ? INPUT_2 : -1); + if (inputSlot == -1) return; + inventory.setItem(inputSlot, stack.split(1)); chest.setChanged(); return; } @@ -153,6 +165,17 @@ private void pullFromNearbyChests() { } } + private int findOutputSlot(ItemStack result) { + for (int i = OUTPUT_START; i <= OUTPUT_END; i++) { + ItemStack out = inventory.getItem(i); + if (out.isEmpty() || (ItemStack.isSameItem(out, result) + && out.getCount() + result.getCount() <= out.getMaxStackSize())) { + return i; + } + } + return -1; + } + // --- Helpers --- private boolean isOre(ItemStack stack) { return stack.is(Items.RAW_COPPER); /* Add more ores */ } private boolean isWood(ItemStack stack) { return stack.getItem().toString().contains("log"); } diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/menu/FurnaceWorkbenchMenu.java b/src/main/java/com/tcm/MineTale/block/workbenches/menu/FurnaceWorkbenchMenu.java index 9f08f9b..c8ed4bc 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/menu/FurnaceWorkbenchMenu.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/menu/FurnaceWorkbenchMenu.java @@ -2,6 +2,7 @@ import com.tcm.MineTale.registry.ModMenuTypes; +import net.minecraft.tags.ItemTags; import net.minecraft.world.Container; import net.minecraft.world.SimpleContainer; import net.minecraft.world.entity.player.Inventory; @@ -36,6 +37,7 @@ public FurnaceWorkbenchMenu(int syncId, Inventory playerInventory) { public FurnaceWorkbenchMenu(int syncId, Inventory playerInventory, Container container, ContainerData data) { super(ModMenuTypes.FURNACE_WORKBENCH_MENU, syncId); checkContainerSize(container, containerSize); + checkContainerDataCount(data, 4); this.container = container; this.data = data; @@ -47,8 +49,7 @@ public FurnaceWorkbenchMenu(int syncId, Inventory playerInventory, Container con this.addSlot(new Slot(container, FUEL_SLOT, 80, 53) { @Override public boolean mayPlace(ItemStack stack) { - // Trait: Sticks, Fibres (String), and Logs - return stack.is(Items.STICK) || stack.is(Items.STRING) || stack.getItem().toString().contains("log"); + return isFuel(stack); } }); @@ -57,10 +58,10 @@ public boolean mayPlace(ItemStack stack) { this.addSlot(new Slot(container, INPUT_2, 44, 35)); // 3. Four Output Slots (2x2 Grid on the right) - this.addSlot(new FurnaceResultSlot(playerInventory.player, container, 3, 116, 21)); - this.addSlot(new FurnaceResultSlot(playerInventory.player, container, 4, 134, 21)); - this.addSlot(new FurnaceResultSlot(playerInventory.player, container, 5, 116, 39)); - this.addSlot(new FurnaceResultSlot(playerInventory.player, container, 6, 134, 39)); + this.addSlot(new FurnaceResultSlot(playerInventory.player, container, OUTPUT_START, 116, 21)); + this.addSlot(new FurnaceResultSlot(playerInventory.player, container, OUTPUT_START + 1, 134, 21)); + this.addSlot(new FurnaceResultSlot(playerInventory.player, container, OUTPUT_END - 1, 116, 39)); + this.addSlot(new FurnaceResultSlot(playerInventory.player, container, OUTPUT_END, 134, 39)); // --- PLAYER INVENTORY --- addPlayerInventory(playerInventory); @@ -106,7 +107,7 @@ else if (!this.moveItemStackTo(itemStack2, 1, 3, false)) { } private boolean isFuel(ItemStack stack) { - return stack.is(Items.STICK) || stack.is(Items.STRING) || stack.getItem().toString().contains("log"); + return stack.is(Items.STICK) || stack.is(Items.STRING) || stack.is(ItemTags.LOGS_THAT_BURN); } @Override From 152d88df8f661c1f06151b06c4ef37605605e669 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:36:09 +0000 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20to=20`fea?= =?UTF-8?q?t/add-furnace-block-basics`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docstrings generation was requested by @The-Code-Monkey. * https://github.com/CodeMonkeysMods/MineTale/pull/14#issuecomment-3811512892 The following files were modified: * `src/client/java/com/tcm/MineTale/MineTaleClient.java` * `src/client/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java` * `src/main/java/com/tcm/MineTale/MineTale.java` * `src/main/java/com/tcm/MineTale/block/workbenches/AbstractWorkbench.java` * `src/main/java/com/tcm/MineTale/block/workbenches/CampfireWorkbench.java` * `src/main/java/com/tcm/MineTale/block/workbenches/FurnaceWorkbench.java` * `src/main/java/com/tcm/MineTale/block/workbenches/entity/AbstractWorkbenchEntity.java` * `src/main/java/com/tcm/MineTale/block/workbenches/entity/CampfireWorkbenchEntity.java` * `src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java` * `src/main/java/com/tcm/MineTale/block/workbenches/menu/FurnaceWorkbenchMenu.java` * `src/main/java/com/tcm/MineTale/registry/ModBlockEntities.java` * `src/main/java/com/tcm/MineTale/registry/ModBlocks.java` * `src/main/java/com/tcm/MineTale/registry/ModMenuTypes.java` --- .../java/com/tcm/MineTale/MineTaleClient.java | 3 + .../screen/FurnaceWorkbenchScreen.java | 28 +++- src/main/java/com/tcm/MineTale/MineTale.java | 6 + .../block/workbenches/AbstractWorkbench.java | 34 +++++ .../block/workbenches/CampfireWorkbench.java | 50 ++++++- .../block/workbenches/FurnaceWorkbench.java | 42 +++++- .../entity/AbstractWorkbenchEntity.java | 52 +++++++- .../entity/CampfireWorkbenchEntity.java | 18 ++- .../entity/FurnaceWorkbenchEntity.java | 125 +++++++++++++++++- .../menu/FurnaceWorkbenchMenu.java | 74 ++++++++++- .../MineTale/registry/ModBlockEntities.java | 7 +- .../com/tcm/MineTale/registry/ModBlocks.java | 7 +- .../tcm/MineTale/registry/ModMenuTypes.java | 16 ++- 13 files changed, 440 insertions(+), 22 deletions(-) diff --git a/src/client/java/com/tcm/MineTale/MineTaleClient.java b/src/client/java/com/tcm/MineTale/MineTaleClient.java index d347081..d74b8c8 100644 --- a/src/client/java/com/tcm/MineTale/MineTaleClient.java +++ b/src/client/java/com/tcm/MineTale/MineTaleClient.java @@ -7,6 +7,9 @@ import net.minecraft.client.gui.screens.MenuScreens; public class MineTaleClient implements ClientModInitializer { + /** + * Registers the screen factory for the furnace workbench menu so the client can create FurnaceWorkbenchScreen instances for that menu type. + */ @Override public void onInitializeClient() { MenuScreens.register(ModMenuTypes.FURNACE_WORKBENCH_MENU, FurnaceWorkbenchScreen::new); diff --git a/src/client/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java b/src/client/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java index ef753f6..f321865 100644 --- a/src/client/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java +++ b/src/client/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java @@ -14,22 +14,48 @@ public class FurnaceWorkbenchScreen extends AbstractContainerScreenTriggers initialization for blocks, block entities, menu types, entities, items, and entity + * data serializers so they are registered with the game before gameplay begins.

+ */ @Override public void onInitialize() { ModBlocks.initialize(); diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/AbstractWorkbench.java b/src/main/java/com/tcm/MineTale/block/workbenches/AbstractWorkbench.java index 53c5534..3e4f01c 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/AbstractWorkbench.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/AbstractWorkbench.java @@ -134,11 +134,23 @@ private boolean isCompatiblePart(BlockState current, BlockState neighbor) { return true; } + /** + * Registers the block state properties used by this block. + * + * @param builder the state builder to populate with this block's properties (`FACING`, `HALF`, `TYPE`) + */ @Override protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(FACING, HALF, TYPE); } + /** + * Create the block entity for the primary anchor of a multipart workbench. + * + * @param pos the position where the block entity would be created + * @param state the block state at the position + * @return the new block entity when this block is the primary anchor (lower half with TYPE `LEFT` or `SINGLE`), or `null` if this block is a secondary part + */ @Nullable @Override public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { @@ -158,6 +170,16 @@ public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { return null; } + /** + * Open the workbench menu for the block's master part when a player interacts without an item. + * + * @param state the block state at the clicked position + * @param level the level in which the interaction occurs + * @param pos the position of the block that was interacted with + * @param player the player performing the interaction + * @param hitResult details about the hit (hit position and face) + * @return {@code InteractionResult.CONSUME} if a menu was opened on the server, {@code InteractionResult.SUCCESS} on the client, {@code InteractionResult.PASS} otherwise + */ @Override protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) { if (level.isClientSide()) { @@ -178,6 +200,18 @@ protected InteractionResult useWithoutItem(BlockState state, Level level, BlockP return InteractionResult.PASS; } + /** + * Compute the master (bottom-left) anchor position for this workbench block. + * + * The master is the block that serves as the primary anchor for multi-block + * behavior and block-entity placement: if this block is the upper half the + * master is one block below; if this block is the right-side part the master + * is one block to the left relative to the block's facing. + * + * @param state the block state used to determine HALF, TYPE, and FACING + * @param pos the current block position + * @return the position of the master (bottom-left) block for this workbench + */ public BlockPos getMasterPos(BlockState state, BlockPos pos) { BlockPos master = pos; Direction facing = state.getValue(FACING); diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/CampfireWorkbench.java b/src/main/java/com/tcm/MineTale/block/workbenches/CampfireWorkbench.java index d13fff2..8be4ea4 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/CampfireWorkbench.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/CampfireWorkbench.java @@ -34,11 +34,22 @@ public class CampfireWorkbench extends AbstractWorkbench CODEC = simpleCodec((properties) -> new CampfireWorkbench(properties, () -> null)); + /** + * Creates a CampfireWorkbench configured to use the mod's CAMPFIRE_WORKBENCH_BE block entity type. + * + * @param properties block properties used to construct this workbench + */ public CampfireWorkbench(Properties properties) { // Hardcode the supplier and sounds here if they never change super(properties, () -> ModBlockEntities.CAMPFIRE_WORKBENCH_BE, IS_WIDE, IS_TALL); } + /** + * Creates a CampfireWorkbench with the given block properties and block-entity type supplier. + * + * @param properties the block's properties + * @param supplier supplier that provides the BlockEntityType for this workbench + */ public CampfireWorkbench(Properties properties, Supplier> supplier) { // isWide = false, isTall = false (1x1 footprint) super(properties, supplier, IS_WIDE, IS_TALL); @@ -49,6 +60,11 @@ protected MapCodec codec() { return CODEC; } + /** + * Specifies that this block is rendered using its block model. + * + * @return RenderShape.MODEL to render the block using its JSON/model representation. + */ @Override public RenderShape getRenderShape(BlockState state) { // BaseEntityBlock defaults to INVISIBLE. @@ -58,11 +74,22 @@ public RenderShape getRenderShape(BlockState state) { private static final VoxelShape SHAPE = Block.box(0, 0, 0, 16, 7, 16); + /** + * Gets the block's collision and interaction shape. + * + * @return the voxel shape representing the block's collision and interaction bounds + */ @Override public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { return SHAPE; } + /** + * Creates and returns the block entity for this block only when the block represents the master + * position (the lower half and not of type RIGHT). + * + * @return the created BlockEntity when this block is the master (HALF == LOWER and TYPE != RIGHT), or `null` otherwise + */ @Nullable @Override public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { @@ -73,6 +100,20 @@ public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { return null; } + /** + * Handles a player's interaction with the workbench when no item is used. + * + *

On the client this acknowledges the interaction. On the server this method + * is a hook for workbench-specific handling; if the workbench processes the + * interaction it will consume it, otherwise the interaction is passed to other handlers.

+ * + * @param state the block state of the workbench + * @param level the world in which the interaction occurs + * @param pos the position of the interacted block + * @param player the player performing the interaction + * @param hit the hit result describing the interaction point + * @return {@code InteractionResult.SUCCESS} on client, {@code InteractionResult.CONSUME} if handled by the workbench, or {@code InteractionResult.PASS} otherwise + */ @Override protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) { if (level.isClientSide()) return InteractionResult.SUCCESS; @@ -89,6 +130,13 @@ protected InteractionResult useWithoutItem(BlockState state, Level level, BlockP return InteractionResult.PASS; } + /** + * Compute the master (base) block position for this block based on its state. + * + * @param state the block state of the current block + * @param pos the position of the current block + * @return the position of the master (base) block: if the block is the upper half, the block below is used; if the block's type is `RIGHT`, the position is offset one block counterclockwise from its facing direction; otherwise the original position + */ public BlockPos getMasterPos(BlockState state, BlockPos pos) { BlockPos master = pos; Direction facing = state.getValue(FACING); @@ -97,4 +145,4 @@ public BlockPos getMasterPos(BlockState state, BlockPos pos) { return master; } -} +} \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/FurnaceWorkbench.java b/src/main/java/com/tcm/MineTale/block/workbenches/FurnaceWorkbench.java index ebec50c..207d91b 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/FurnaceWorkbench.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/FurnaceWorkbench.java @@ -24,25 +24,49 @@ public class FurnaceWorkbench extends AbstractWorkbench public static final MapCodec CODEC = simpleCodec(FurnaceWorkbench::new); /** - * Standard constructor for registration. + * Creates a FurnaceWorkbench using the default furnace workbench block entity type and a 2×2 footprint. + * + * @param properties block properties for this workbench */ public FurnaceWorkbench(Properties properties) { super(properties, () -> ModBlockEntities.FURNACE_WORKBENCH_BE, IS_WIDE, IS_TALL); } /** - * Flexible constructor allowing for specialized Block Entity Types. + * Creates a FurnaceWorkbench that uses a custom BlockEntityType supplier and a 2x2 footprint. + * + * @param properties block properties for this workbench + * @param supplier supplies the BlockEntityType to use for the workbench's master block entity */ public FurnaceWorkbench(Properties properties, Supplier> supplier) { super(properties, supplier, IS_WIDE, IS_TALL); } + /** + * Ensures the block is rendered using its model so the 2x2 workbench model is visible. + * + * @return {@code RenderShape.MODEL} to render the block with its model + */ @Override public RenderShape getRenderShape(BlockState state) { // Essential so that the 2x2 model is visible return RenderShape.MODEL; } + /** + * Provides a ticker that drives furnace workbench logic for the master block. + * + * The returned ticker invokes {@link FurnaceWorkbenchEntity#tick(Level, BlockPos, BlockState)} on the master + * workbench block's entity when the supplied `type` matches the furnace workbench block entity type; + * otherwise no ticker is provided. + * + * @param block entity type + * @param level the level in which the ticker will run + * @param state the block state for which the ticker is requested + * @param type the block entity type being ticked + * @return a ticker that calls `FurnaceWorkbenchEntity.tick(...)` for the master furnace workbench entity, or + * `null` if the provided `type` does not match the furnace workbench block entity type + */ @Nullable @Override public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { @@ -55,6 +79,11 @@ public BlockEntityTicker getTicker(Level level, Block }); } + /** + * Create the block entity for this block; only the master block of the multi-block workbench receives an entity. + * + * @return the created {@link BlockEntity} for the master block, or `null` if this position does not host an entity + */ @Nullable @Override public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { @@ -63,9 +92,14 @@ public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { return super.newBlockEntity(pos, state); } - @Override + /** + * Supply the codec used to serialize and deserialize this FurnaceWorkbench. + * + * @return the MapCodec for this FurnaceWorkbench + */ + @Override protected MapCodec codec() { return CODEC; } -} +} \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/entity/AbstractWorkbenchEntity.java b/src/main/java/com/tcm/MineTale/block/workbenches/entity/AbstractWorkbenchEntity.java index 811a9c6..842b434 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/entity/AbstractWorkbenchEntity.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/entity/AbstractWorkbenchEntity.java @@ -21,15 +21,38 @@ public abstract class AbstractWorkbenchEntity extends BlockEntity implements Men protected int tier = 1; protected double scanRadius = 5.0; + /** + * Creates a new workbench block entity instance. + * + * @param type the BlockEntityType for this entity + * @param pos the world position of the block entity + * @param state the block state at the position + */ public AbstractWorkbenchEntity(BlockEntityType type, BlockPos pos, BlockState state) { super(type, pos, state); } - // --- TIER SYSTEM --- + /** + * Gets the workstation's current tier. + * + * @return the current tier value + */ public int getTier() { return tier; } - public void setTier(int tier) { this.tier = tier; setChanged(); } + /** + * Sets the workstation's tier and marks the block entity as changed. + * + * @param tier the new tier value for this workstation + */ +public void setTier(int tier) { this.tier = tier; setChanged(); } - // --- CHEST SCANNING --- + /** + * Collects nearby inventory-containing block entities within the configured scan radius and vertical range. + * + * Scans a square area centered on this entity from -scanRadius..+scanRadius on X/Z and -2..+2 on Y, gathers any + * block entities implementing `Container`, and returns them sorted by increasing distance to this entity. + * + * @return a list of nearby `Container` instances sorted by proximity; an empty list if none are found or if the world (`level`) is null + */ public List getNearbyInventories() { List inventories = new ArrayList<>(); if (level == null) { @@ -55,20 +78,39 @@ public List getNearbyInventories() { return inventories; } - // --- RECYCLER LOGIC --- + /** + * Attempt to recycle an ItemStack into component items and distribute those components to nearby inventories or drop them at the workbench location. + * + *

The base implementation is a no-op; subclasses should override to perform actual recycling. Implementations are expected to insert resulting items into nearby Container block entities when possible and otherwise spawn the items at this entity's world position.

+ * + * @param stack the ItemStack to recycle + */ public void attemptRecycle(ItemStack stack) { // Logic to break down stack.getItem() and return components // to nearby chests or drop them at worldPosition. } + /** + * Create the container menu presented to the player when they open this workbench. + * + * @param syncId the window synchronization id provided by the client + * @param playerInventory the player's inventory + * @param player the player opening the menu + * @return the created AbstractContainerMenu, or `null` if no menu should be opened + */ @Nullable @Override public abstract AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player); + /** + * Provides the display name for this workbench block. + * + * @return a translatable Component created from the block's description ID + */ @Override public Component getDisplayName() { return Component.translatable(this.getBlockState().getBlock().getDescriptionId()); } -} +} \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/entity/CampfireWorkbenchEntity.java b/src/main/java/com/tcm/MineTale/block/workbenches/entity/CampfireWorkbenchEntity.java index 40dc1ee..73db2a9 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/entity/CampfireWorkbenchEntity.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/entity/CampfireWorkbenchEntity.java @@ -11,6 +11,14 @@ import net.minecraft.world.level.block.state.BlockState; public class CampfireWorkbenchEntity extends AbstractWorkbenchEntity { + /** + * Creates a CampfireWorkbenchEntity at the given position with the provided block state. + * + * Initializes the entity's scan radius to 6.0 and tier to 1. + * + * @param blockPos the world position of this block entity + * @param blockState the block state for this block entity + */ public CampfireWorkbenchEntity(BlockPos blockPos, BlockState blockState) { super(ModBlockEntities.CAMPFIRE_WORKBENCH_BE, blockPos, blockState); @@ -18,9 +26,17 @@ public CampfireWorkbenchEntity(BlockPos blockPos, BlockState blockState) { this.tier = 1; } + /** + * Creates the server-side container menu for this workbench for the given player and window ID. + * + * @param syncId the window synchronization ID provided by the client + * @param playerInventory the player's inventory + * @param player the player opening the menu + * @return the created {@link AbstractContainerMenu}, or `null` if no menu should be opened + */ @Override public @Nullable AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'createMenu'"); } -} +} \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java b/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java index 8389198..0f4c36c 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java @@ -39,6 +39,13 @@ public class FurnaceWorkbenchEntity extends AbstractWorkbenchEntity { private int fuelTime; protected final ContainerData data = new ContainerData() { + /** + * Retrieves an internal data value by index for UI synchronization. + * + * @param index the data index: 0 = remaining fuel time, 1 = fuel total (constant 100), + * 2 = current cook progress, 3 = total cook time + * @return the value associated with {@code index}, or 0 for any other index + */ @Override public int get(int index) { return switch (index) { @@ -50,6 +57,19 @@ public int get(int index) { }; } + /** + * Sets an internal workbench data field identified by index. + * + * Supported indices: + *
    + *
  • 0 — sets {@code fuelTime}
  • + *
  • 2 — sets {@code cookTime}
  • + *
+ * Other indices are ignored. + * + * @param index the data index to set + * @param value the value to assign to the indexed field + */ @Override public void set(int index, int value) { switch (index) { @@ -58,17 +78,46 @@ public void set(int index, int value) { } } + /** + * The number of data values exposed by this ContainerData. + * + * @return the number of data entries (4) + */ @Override public int getCount() { return 4; } }; + /** + * Creates a new FurnaceWorkbenchEntity at the specified position and block state. + * + * Initializes the entity and sets the default nearby-inventory scan radius to 8.0. + * + * @param pos the block position of the entity + * @param state the block state at that position + */ public FurnaceWorkbenchEntity(BlockPos pos, BlockState state) { super(ModBlockEntities.FURNACE_WORKBENCH_BE, pos, state); this.scanRadius = 8.0; } + /** + * Performs server-side per-tick processing for the furnace workbench: attempts to pull input items from nearby + * containers, manages fuel consumption, advances smelting progress according to workbench tier, and produces output + * when a smelt cycle completes. + * + *

Behavioral notes: + * - Runs only on the server side. + * - When input is empty, attempts to pull a single eligible input item from nearby inventories once every 20 game ticks. + * - Workbench tier reduces the required cook duration; cooking advances while fuel is available and is reset when smelting is not possible. + * - Consumes fuel items to refill internal fuel time, decrements fuel time each tick, increments cook progress, and invokes smeltItem(...) when a cycle finishes. + * - Marks the block entity changed if any inventory or internal state is modified.

+ * + * @param level the world in which the workbench exists + * @param pos the block position of the workbench + * @param state the current block state of the workbench + */ public void tick(Level level, BlockPos pos, BlockState state) { if (level.isClientSide()) return; @@ -111,12 +160,26 @@ public void tick(Level level, BlockPos pos, BlockState state) { if (changed) setChanged(); } + /** + * Determines whether the provided stack is a valid smelting input (ore or log). + * + * @param input the item stack to test + * @return `true` if the stack represents an ore or a log, `false` otherwise + */ private boolean canSmelt(ItemStack input) { if (input.isEmpty()) return false; // Logic: Check if it's an ore (Copper to Adamantite) or Logs for Charcoal return isOre(input) || isWood(input); } + /** + * Consume one unit of the provided fuel item and set the internal fuel timer when the item is an accepted fuel. + * + * Accepted fuels: sticks, string (fibres), or any item recognized as wood. + * + * @param fuel the ItemStack to attempt to consume; one item will be removed if accepted + * @return `true` if a fuel unit was consumed and the internal fuel time was set to 100, `false` otherwise + */ private boolean consumeFuel(ItemStack fuel) { // TRAIT: Use fibres (string), sticks, or logs if (fuel.is(Items.STICK) || fuel.is(Items.STRING) || isWood(fuel)) { @@ -127,6 +190,15 @@ private boolean consumeFuel(ItemStack fuel) { return false; } + /** + * Smelts a single input item into its output and deposits the result into an available output slot. + * + *

If the input is wood, produces charcoal; otherwise produces a copper ingot (placeholder for ore-to-ingot mapping). + * The method finds a suitable output slot and either places the result there or increases the existing stack; if no + * output slot is available the method does nothing. The input stack is reduced by one on successful smelting. + * + * @param input the ItemStack to smelt; one item will be consumed from this stack when smelting occurs + */ private void smeltItem(ItemStack input) { ItemStack result; // TRAIT: Logs yield Charcoal @@ -149,6 +221,13 @@ private void smeltItem(ItemStack input) { input.shrink(1); } + /** + * Attempts to move a single ore or wood item from nearby inventories into this entity's input slots. + * + * If INPUT_1 is empty that slot is filled first; otherwise INPUT_2 is used. If neither input slot is + * available or no matching item is found, the method does nothing. When an item is moved, the source + * container is marked changed. + */ private void pullFromNearbyChests() { List nearby = this.getNearbyInventories(); for (Container chest : nearby) { @@ -165,6 +244,12 @@ private void pullFromNearbyChests() { } } + /** + * Finds the first output slot that can accept the given result. + * + * @param result the item stack to place into an output slot + * @return the index of the first suitable output slot between OUTPUT_START and OUTPUT_END, or -1 if none is available + */ private int findOutputSlot(ItemStack result) { for (int i = OUTPUT_START; i <= OUTPUT_END; i++) { ItemStack out = inventory.getItem(i); @@ -176,10 +261,29 @@ private int findOutputSlot(ItemStack result) { return -1; } - // --- Helpers --- + /** + * Determines whether the provided item stack is a supported ore. + * + * @param stack the item stack to test + * @return `true` if the stack is a supported ore (currently `Items.RAW_COPPER`), `false` otherwise + */ private boolean isOre(ItemStack stack) { return stack.is(Items.RAW_COPPER); /* Add more ores */ } - private boolean isWood(ItemStack stack) { return stack.getItem().toString().contains("log"); } + /** + * Determines whether the given item stack represents a wood log item. + * + * @param stack the item stack to inspect + * @return `true` if the stack's item is a wood log, `false` otherwise + */ +private boolean isWood(ItemStack stack) { return stack.getItem().toString().contains("log"); } + /** + * Persist entity-specific state into the provided ValueOutput. + * + * Stores the workbench's tier as "WorkbenchTier" and its scan radius as "ScanRadius" + * using type-safe Codecs. + * + * @param valueOutput the output writer used to serialize this entity's fields + */ @Override protected void saveAdditional(ValueOutput valueOutput) { super.saveAdditional(valueOutput); @@ -188,6 +292,13 @@ protected void saveAdditional(ValueOutput valueOutput) { valueOutput.store("ScanRadius", Codec.DOUBLE, this.scanRadius); } + /** + * Restores workbench-specific state from persistent storage and applies defaults when keys are absent. + * + * Delegates to the superclass load logic, then reads: + * - "WorkbenchTier" (int) into {@code tier}, defaulting to {@code 1} if missing. + * - "ScanRadius" (double) into {@code scanRadius}, defaulting to {@code 5.0} if missing. + */ @Override protected void loadAdditional(ValueInput valueInput) { super.loadAdditional(valueInput); @@ -196,8 +307,16 @@ protected void loadAdditional(ValueInput valueInput) { this.scanRadius = valueInput.read("ScanRadius", Codec.DOUBLE).orElse(5.0); } + /** + * Create the player-facing menu for this Furnace Workbench block entity. + * + * @param syncId the window id used to synchronize client and server for this menu + * @param playerInventory the player's inventory view passed to the menu + * @param player the player opening the menu + * @return the FurnaceWorkbenchMenu instance for this block entity, or `null` if a menu cannot be created + */ @Override public @Nullable AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) { return new FurnaceWorkbenchMenu(syncId, playerInventory, this.inventory, this.data); } -} +} \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/menu/FurnaceWorkbenchMenu.java b/src/main/java/com/tcm/MineTale/block/workbenches/menu/FurnaceWorkbenchMenu.java index c8ed4bc..616324c 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/menu/FurnaceWorkbenchMenu.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/menu/FurnaceWorkbenchMenu.java @@ -28,12 +28,25 @@ public class FurnaceWorkbenchMenu extends AbstractContainerMenu { private static final int OUTPUT_START = 3; private static final int OUTPUT_END = 6; - // This constructor is used by the Client + /** + * Client-side constructor that creates a FurnaceWorkbenchMenu backed by a new internal container and container data. + * + * @param syncId the window sync id assigned by the client + * @param playerInventory the player's inventory to attach to this menu + */ public FurnaceWorkbenchMenu(int syncId, Inventory playerInventory) { this(syncId, playerInventory, new SimpleContainer(7), new SimpleContainerData(4)); } - // This constructor is used by the Server (via the BlockEntity) + /** + * Initializes a server-side furnace-workbench menu for a player, setting up the container slots + * and attaching progress synchronization data. + * + * @param syncId the window synchronization id assigned by the server + * @param playerInventory the player's Inventory used to populate player inventory and hotbar slots + * @param container the backing container (expected size 7) that provides the workbench slots + * @param data the ContainerData (expected count 4) used to sync cook and burn progress + */ public FurnaceWorkbenchMenu(int syncId, Inventory playerInventory, Container container, ContainerData data) { super(ModMenuTypes.FURNACE_WORKBENCH_MENU, syncId); checkContainerSize(container, containerSize); @@ -47,6 +60,12 @@ public FurnaceWorkbenchMenu(int syncId, Inventory playerInventory, Container con // 1. Fuel Slot (Center-ish bottom) this.addSlot(new Slot(container, FUEL_SLOT, 80, 53) { + /** + * Determines whether the given item stack is allowed in the fuel slot. + * + * @param stack the item stack to test for fuel eligibility + * @return `true` if the stack is accepted as fuel, `false` otherwise + */ @Override public boolean mayPlace(ItemStack stack) { return isFuel(stack); @@ -71,6 +90,17 @@ public boolean mayPlace(ItemStack stack) { this.addDataSlots(data); } + /** + * Handles a quick (shift-click) transfer of an item stack between the furnace-workbench container and the player's inventory. + * + * Attempts to move the stack from the container area (slots belonging to this menu) to the player's inventory, or from the + * player inventory into the appropriate container slots. When moving into the container, fuel items are sent to the fuel + * slot and other items are sent to the input slots. If the transfer cannot be completed, no change is applied. + * + * @param player the player performing the transfer + * @param index the index of the slot that was shift-clicked + * @return the original ItemStack from the clicked slot, or ItemStack.EMPTY if the transfer failed + */ @Override public ItemStack quickMoveStack(Player player, int index) { ItemStack itemStack = ItemStack.EMPTY; @@ -106,15 +136,34 @@ else if (!this.moveItemStackTo(itemStack2, 1, 3, false)) { return itemStack; } + /** + * Checks whether an ItemStack is accepted as fuel by this furnace workbench. + * + * @param stack the ItemStack to test + * @return {@code true} if the stack is a stick, string, or matches the `ItemTags.LOGS_THAT_BURN` tag; {@code false} otherwise + */ private boolean isFuel(ItemStack stack) { return stack.is(Items.STICK) || stack.is(Items.STRING) || stack.is(ItemTags.LOGS_THAT_BURN); } + /** + * Checks whether the given player may continue interacting with this menu. + * + * @param player the player to check + * @return `true` if the player may continue interacting with the menu, `false` otherwise + */ @Override public boolean stillValid(Player player) { return this.container.stillValid(player); } + /** + * Adds the player's main 3x9 inventory grid to this menu. + * + * The grid is positioned starting at (8, 84) with 18-pixel spacing between slots. + * + * @param playerInventory the player's Inventory to populate the menu slots from + */ private void addPlayerInventory(Inventory playerInventory) { for (int i = 0; i < 3; ++i) { for (int j = 0; j < 9; ++j) { @@ -123,22 +172,39 @@ private void addPlayerInventory(Inventory playerInventory) { } } + /** + * Adds the player's 9-slot hotbar to this menu at the standard hotbar position. + * + * @param playerInventory the player's inventory to populate hotbar slots from + */ private void addPlayerHotbar(Inventory playerInventory) { for (int i = 0; i < 9; ++i) { this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 142)); } } - // Methods for the Screen to use for rendering progress bars + /** + * Provides the current cook progress for rendering the cook progress bar. + * + * @return the cook progress scaled to a 24-pixel width (`0` if there is no progress or total cook time is zero) + */ public int getCookProgress() { int i = this.data.get(2); // cookTime int j = this.data.get(3); // cookTimeTotal return j != 0 && i != 0 ? i * 24 / j : 0; } + /** + * Calculates the fuel burn progress for the GUI fuel indicator. + * + * Uses container data index 0 as the current fuel time and index 1 as the total fuel time; + * if the total is zero, a fallback of 200 ticks is used. The result is scaled to a 13-pixel width. + * + * @return an integer between 0 and 13 representing the current burn progress in pixels + */ public int getBurnProgress() { int i = this.data.get(1); // fuelTimeTotal if (i == 0) i = 200; return this.data.get(0) * 13 / i; // fuelTime } -} +} \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/registry/ModBlockEntities.java b/src/main/java/com/tcm/MineTale/registry/ModBlockEntities.java index 9571b9f..6daa760 100644 --- a/src/main/java/com/tcm/MineTale/registry/ModBlockEntities.java +++ b/src/main/java/com/tcm/MineTale/registry/ModBlockEntities.java @@ -27,6 +27,11 @@ public class ModBlockEntities { ); + /** + * Logs a confirmation that the mod's block entity types have been registered. + * + * Prints "Registered Mod Entities for {modId}" to standard output, where `{modId}` is the mod's identifier. + */ public static void initialize() { System.out.println("Registered Mod Entities for " + MineTale.MOD_ID); } @@ -40,4 +45,4 @@ private static BlockEntityType register( Identifier id = Identifier.fromNamespaceAndPath(MineTale.MOD_ID, name); return Registry.register(BuiltInRegistries.BLOCK_ENTITY_TYPE, id, FabricBlockEntityTypeBuilder.create(entityFactory, blocks).build()); } -} +} \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/registry/ModBlocks.java b/src/main/java/com/tcm/MineTale/registry/ModBlocks.java index fa0293b..2980570 100644 --- a/src/main/java/com/tcm/MineTale/registry/ModBlocks.java +++ b/src/main/java/com/tcm/MineTale/registry/ModBlocks.java @@ -35,6 +35,11 @@ public class ModBlocks { true ); + /** + * Registers this mod's blocks into the Functional Blocks creative tab and records the registration. + * + * Adds CAMPFIRE_WORKBENCH_BLOCK and FURNACE_WORKBENCH_BLOCK to CreativeModeTabs.FUNCTIONAL_BLOCKS and prints a registration message including the mod ID. + */ public static void initialize() { ItemGroupEvents.modifyEntriesEvent(CreativeModeTabs.FUNCTIONAL_BLOCKS).register(entries -> { entries.accept(CAMPFIRE_WORKBENCH_BLOCK); @@ -72,4 +77,4 @@ private static ResourceKey keyOfBlock(String name) { private static ResourceKey keyOfItem(String name) { return ResourceKey.create(Registries.ITEM, Identifier.fromNamespaceAndPath(MineTale.MOD_ID, name)); } -} +} \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/registry/ModMenuTypes.java b/src/main/java/com/tcm/MineTale/registry/ModMenuTypes.java index 05d3b88..71b6f1b 100644 --- a/src/main/java/com/tcm/MineTale/registry/ModMenuTypes.java +++ b/src/main/java/com/tcm/MineTale/registry/ModMenuTypes.java @@ -17,11 +17,25 @@ public class ModMenuTypes { FurnaceWorkbenchMenu::new ); + /** + * Triggers static registration of the mod's menu types. + * + *

Forces the class's static initializers to run and prints a registration confirmation + * message to standard output. + */ public static void initialize() { // Just used to trigger the static registration System.out.println("Registered Mod Menus for " + MineTale.MOD_ID); } + /** + * Register a MenuType under this mod's namespace and return the registered type. + * + * @param name the path component used to build the registry Identifier (namespace is MineTale.MOD_ID) + * @param factory a supplier that constructs instances of the menu type + * @param the concrete AbstractContainerMenu subtype + * @return the MenuType instance registered in BuiltInRegistries.MENU + */ private static MenuType register(String name, MenuType.MenuSupplier factory) { Identifier id = Identifier.fromNamespaceAndPath(MineTale.MOD_ID, name); return Registry.register( @@ -30,4 +44,4 @@ private static MenuType register(String nam new MenuType<>(factory, FeatureFlags.DEFAULT_FLAGS) ); } -} +} \ No newline at end of file From 7a67138e974abeee8e973612d86dcdd70962ad57 Mon Sep 17 00:00:00 2001 From: Andy Wilson Date: Wed, 28 Jan 2026 17:07:50 +0000 Subject: [PATCH 6/7] fix: final comments for now --- .../block/workbenches/CampfireWorkbench.java | 3 +-- .../entity/FurnaceWorkbenchEntity.java | 25 ++++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/CampfireWorkbench.java b/src/main/java/com/tcm/MineTale/block/workbenches/CampfireWorkbench.java index 8be4ea4..e5341c4 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/CampfireWorkbench.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/CampfireWorkbench.java @@ -31,8 +31,7 @@ public class CampfireWorkbench extends AbstractWorkbench CODEC = simpleCodec((properties) -> - new CampfireWorkbench(properties, () -> null)); + public static final MapCodec CODEC = simpleCodec(CampfireWorkbench::new); /** * Creates a CampfireWorkbench configured to use the mod's CAMPFIRE_WORKBENCH_BE block entity type. diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java b/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java index 0f4c36c..4a301a6 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java @@ -9,6 +9,7 @@ import com.tcm.MineTale.registry.ModBlockEntities; import net.minecraft.core.BlockPos; +import net.minecraft.tags.ItemTags; import net.minecraft.world.Container; import net.minecraft.world.SimpleContainer; import net.minecraft.world.entity.player.Inventory; @@ -214,7 +215,7 @@ private void smeltItem(ItemStack input) { ItemStack output = inventory.getItem(outputSlot); if (output.isEmpty()) { - inventory.setItem(2, result.copy()); + inventory.setItem(outputSlot, result.copy()) } else if (ItemStack.isSameItem(output, result)) { output.grow(result.getCount()); } @@ -262,19 +263,19 @@ private int findOutputSlot(ItemStack result) { } /** - * Determines whether the provided item stack is a supported ore. - * - * @param stack the item stack to test - * @return `true` if the stack is a supported ore (currently `Items.RAW_COPPER`), `false` otherwise - */ + * Determines whether the provided item stack is a supported ore. + * + * @param stack the item stack to test + * @return `true` if the stack is a supported ore (currently `Items.RAW_COPPER`), `false` otherwise + */ private boolean isOre(ItemStack stack) { return stack.is(Items.RAW_COPPER); /* Add more ores */ } /** - * Determines whether the given item stack represents a wood log item. - * - * @param stack the item stack to inspect - * @return `true` if the stack's item is a wood log, `false` otherwise - */ -private boolean isWood(ItemStack stack) { return stack.getItem().toString().contains("log"); } + * Determines whether the given item stack represents a wood log item. + * + * @param stack the item stack to inspect + * @return `true` if the stack's item is a wood log, `false` otherwise + */ + private boolean isWood(ItemStack stack) { return stack.is(ItemTags.LOGS_THAT_BURN); } /** * Persist entity-specific state into the provided ValueOutput. From 9d460312bcd1e4b38bc6ff303c458a87579ac86e Mon Sep 17 00:00:00 2001 From: Andy Wilson Date: Wed, 28 Jan 2026 22:09:19 +0000 Subject: [PATCH 7/7] Fix output item setting in FurnaceWorkbenchEntity --- .../block/workbenches/entity/FurnaceWorkbenchEntity.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java b/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java index 4a301a6..4a40f4d 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/entity/FurnaceWorkbenchEntity.java @@ -215,7 +215,7 @@ private void smeltItem(ItemStack input) { ItemStack output = inventory.getItem(outputSlot); if (output.isEmpty()) { - inventory.setItem(outputSlot, result.copy()) + inventory.setItem(outputSlot, result.copy()); } else if (ItemStack.isSameItem(output, result)) { output.grow(result.getCount()); } @@ -320,4 +320,4 @@ protected void loadAdditional(ValueInput valueInput) { public @Nullable AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) { return new FurnaceWorkbenchMenu(syncId, playerInventory, this.inventory, this.data); } -} \ No newline at end of file +}