Skip to content

Commit 05ca78e

Browse files
initial files for furnace block
1 parent bcb4d84 commit 05ca78e

13 files changed

Lines changed: 691 additions & 12 deletions
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package com.tcm.MineTale;
22

3+
import com.tcm.MineTale.block.workbenches.screen.FurnaceWorkbenchScreen;
4+
import com.tcm.MineTale.registry.ModMenuTypes;
5+
36
import net.fabricmc.api.ClientModInitializer;
7+
import net.minecraft.client.gui.screens.MenuScreens;
48

59
public class MineTaleClient implements ClientModInitializer {
610
@Override
711
public void onInitializeClient() {
8-
// This entrypoint is suitable for setting up client-specific logic, such as rendering.
12+
MenuScreens.register(ModMenuTypes.FURNACE_WORKBENCH_MENU, FurnaceWorkbenchScreen::new);
913
}
1014
}

src/main/java/com/tcm/MineTale/MineTale.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.tcm.MineTale.registry.ModEntities;
1111
import com.tcm.MineTale.registry.ModEntityDataSerializers;
1212
import com.tcm.MineTale.registry.ModItems;
13+
import com.tcm.MineTale.registry.ModMenuTypes;
1314

1415
public class MineTale implements ModInitializer {
1516
public static final String MOD_ID = "minetale";
@@ -23,6 +24,7 @@ public class MineTale implements ModInitializer {
2324
public void onInitialize() {
2425
ModBlocks.initialize();
2526
ModBlockEntities.initialize();
27+
ModMenuTypes.initialize();
2628
ModEntities.initialize();
2729
ModItems.initialize();
2830

src/main/java/com/tcm/MineTale/block/workbenches/AbstractWorkbench.java

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import net.minecraft.core.BlockPos;
44
import net.minecraft.core.Direction;
55
import net.minecraft.util.RandomSource;
6+
import net.minecraft.world.InteractionResult;
7+
import net.minecraft.world.MenuProvider;
68
import net.minecraft.world.entity.LivingEntity;
9+
import net.minecraft.world.entity.player.Player;
710
import net.minecraft.world.item.ItemStack;
811
import net.minecraft.world.item.context.BlockPlaceContext;
912
import net.minecraft.world.level.Level;
@@ -18,11 +21,15 @@
1821
import net.minecraft.world.level.block.state.BlockState;
1922
import net.minecraft.world.level.block.state.StateDefinition;
2023
import net.minecraft.world.level.block.state.properties.*;
24+
import net.minecraft.world.phys.BlockHitResult;
25+
2126
import org.jetbrains.annotations.Nullable;
2227

28+
import com.tcm.MineTale.block.workbenches.entity.AbstractWorkbenchEntity;
29+
2330
import java.util.function.Supplier;
2431

25-
public abstract class AbstractWorkbench<E extends BlockEntity> extends BaseEntityBlock {
32+
public abstract class AbstractWorkbench<E extends AbstractWorkbenchEntity> extends BaseEntityBlock {
2633
public static final EnumProperty<Direction> FACING = HorizontalDirectionalBlock.FACING;
2734
public static final EnumProperty<DoubleBlockHalf> HALF = BlockStateProperties.DOUBLE_BLOCK_HALF;
2835
public static final EnumProperty<ChestType> TYPE = BlockStateProperties.CHEST_TYPE;
@@ -131,4 +138,60 @@ private boolean isCompatiblePart(BlockState current, BlockState neighbor) {
131138
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
132139
builder.add(FACING, HALF, TYPE);
133140
}
141+
142+
@Nullable
143+
@Override
144+
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
145+
// We only want a Block Entity at the 'primary' anchor point of the structure.
146+
// For 1x1: HALF=LOWER, TYPE=SINGLE
147+
// For 2x1: HALF=LOWER, TYPE=LEFT
148+
// For 2x2: HALF=LOWER, TYPE=LEFT
149+
150+
boolean isLower = state.getValue(HALF) == DoubleBlockHalf.LOWER;
151+
ChestType type = state.getValue(TYPE);
152+
153+
// If it's the RIGHT side of a wide block, or the UPPER half of a tall block, return null.
154+
if (isLower && (type == ChestType.LEFT || type == ChestType.SINGLE)) {
155+
return this.blockEntityType.get().create(pos, state);
156+
}
157+
158+
return null;
159+
}
160+
161+
@Override
162+
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
163+
if (level.isClientSide()) {
164+
return InteractionResult.SUCCESS;
165+
}
166+
167+
// 1. Find the Master Position (Bottom-Left)
168+
BlockPos masterPos = getMasterPos(state, pos);
169+
BlockEntity blockEntity = level.getBlockEntity(masterPos);
170+
171+
// 2. Check if the Master block has the MenuProvider trait
172+
if (blockEntity instanceof MenuProvider menuProvider) {
173+
// 3. Open the Screen (this triggers the ScreenHandler/Menu)
174+
player.openMenu(menuProvider);
175+
return InteractionResult.CONSUME;
176+
}
177+
178+
return InteractionResult.PASS;
179+
}
180+
181+
public BlockPos getMasterPos(BlockState state, BlockPos pos) {
182+
BlockPos master = pos;
183+
Direction facing = state.getValue(FACING);
184+
185+
// Move down if we are the upper half
186+
if (state.getValue(HALF) == DoubleBlockHalf.UPPER) {
187+
master = master.below();
188+
}
189+
190+
// Move left if we are the right side (relative to facing)
191+
if (state.getValue(TYPE) == ChestType.RIGHT) {
192+
master = master.relative(facing.getCounterClockWise());
193+
}
194+
195+
return master;
196+
}
134197
}

src/main/java/com/tcm/MineTale/block/workbenches/CampfireWorkbench.java

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,29 @@
55
import org.jetbrains.annotations.Nullable;
66

77
import com.mojang.serialization.MapCodec;
8+
import com.tcm.MineTale.block.workbenches.entity.CampfireWorkbenchEntity;
89
import com.tcm.MineTale.registry.ModBlockEntities;
910

1011
import net.minecraft.core.BlockPos;
12+
import net.minecraft.core.Direction;
13+
import net.minecraft.world.InteractionResult;
14+
import net.minecraft.world.entity.player.Player;
1115
import net.minecraft.world.level.BlockGetter;
16+
import net.minecraft.world.level.Level;
1217
import net.minecraft.world.level.block.Block;
1318
import net.minecraft.world.level.block.RenderShape;
1419
import net.minecraft.world.level.block.entity.BlockEntity;
1520
import net.minecraft.world.level.block.entity.BlockEntityType;
1621
import net.minecraft.world.level.block.state.BlockState;
22+
import net.minecraft.world.level.block.state.properties.ChestType;
23+
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
24+
import net.minecraft.world.phys.BlockHitResult;
1725
import net.minecraft.world.phys.shapes.CollisionContext;
1826
import net.minecraft.world.phys.shapes.VoxelShape;
1927

2028
// ChestBlock
2129

22-
public class CampfireWorkbench extends AbstractWorkbench<BlockEntity> {
30+
public class CampfireWorkbench extends AbstractWorkbench<CampfireWorkbenchEntity> {
2331
public static final boolean IS_WIDE = false;
2432
public static final boolean IS_TALL = false;
2533

@@ -31,7 +39,7 @@ public CampfireWorkbench(Properties properties) {
3139
super(properties, () -> ModBlockEntities.CAMPFIRE_WORKBENCH_BE, IS_WIDE, IS_TALL);
3240
}
3341

34-
public CampfireWorkbench(Properties properties, Supplier<BlockEntityType<? extends BlockEntity>> supplier) {
42+
public CampfireWorkbench(Properties properties, Supplier<BlockEntityType<? extends CampfireWorkbenchEntity>> supplier) {
3543
// isWide = false, isTall = false (1x1 footprint)
3644
super(properties, supplier, IS_WIDE, IS_TALL);
3745
}
@@ -48,18 +56,45 @@ public RenderShape getRenderShape(BlockState state) {
4856
return RenderShape.MODEL;
4957
}
5058

59+
private static final VoxelShape SHAPE = Block.box(0, 0, 0, 16, 7, 16);
60+
61+
@Override
62+
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
63+
return SHAPE;
64+
}
65+
5166
@Nullable
5267
@Override
5368
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
54-
// For a 1x1, we always create the Block Entity at this position.
55-
return this.blockEntityType.get().create(pos, state);
69+
// Only spawn the entity at the "Master" position (LOWER + LEFT or LOWER + SINGLE)
70+
if (state.getValue(HALF) == DoubleBlockHalf.LOWER && state.getValue(TYPE) != ChestType.RIGHT) {
71+
return blockEntityType.get().create(pos, state);
72+
}
73+
return null;
5674
}
5775

58-
private static final VoxelShape SHAPE = Block.box(0, 0, 0, 16, 7, 16);
59-
6076
@Override
61-
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
62-
return SHAPE;
77+
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) {
78+
if (level.isClientSide()) return InteractionResult.SUCCESS;
79+
80+
// BlockPos masterPos = getMasterPos(state, pos);
81+
// BlockEntity be = level.getBlockEntity(masterPos);
82+
83+
// if (be instanceof AbstractWorkbenchEntity) {
84+
// // Open UI or handle Recycling logic here
85+
// // Example: if player is holding a tool, try to recycle it
86+
// return InteractionResult.CONSUME;
87+
// }
88+
89+
return InteractionResult.PASS;
90+
}
91+
92+
public BlockPos getMasterPos(BlockState state, BlockPos pos) {
93+
BlockPos master = pos;
94+
Direction facing = state.getValue(FACING);
95+
if (state.getValue(HALF) == DoubleBlockHalf.UPPER) master = master.below();
96+
if (state.getValue(TYPE) == ChestType.RIGHT) master = master.relative(facing.getCounterClockWise());
97+
return master;
6398
}
6499

65100
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.tcm.MineTale.block.workbenches;
2+
3+
import java.util.function.Supplier;
4+
5+
import org.jetbrains.annotations.Nullable;
6+
7+
import com.mojang.serialization.MapCodec;
8+
import com.tcm.MineTale.block.workbenches.entity.FurnaceWorkbenchEntity;
9+
import com.tcm.MineTale.registry.ModBlockEntities;
10+
11+
import net.minecraft.core.BlockPos;
12+
import net.minecraft.world.level.Level;
13+
import net.minecraft.world.level.block.RenderShape;
14+
import net.minecraft.world.level.block.entity.BlockEntity;
15+
import net.minecraft.world.level.block.entity.BlockEntityTicker;
16+
import net.minecraft.world.level.block.entity.BlockEntityType;
17+
import net.minecraft.world.level.block.state.BlockState;
18+
19+
public class FurnaceWorkbench extends AbstractWorkbench<FurnaceWorkbenchEntity> {
20+
// Setting these to true creates the 2x2 multi-block footprint
21+
private static final boolean IS_WIDE = true;
22+
private static final boolean IS_TALL = true;
23+
24+
public static final MapCodec<FurnaceWorkbench> CODEC = simpleCodec((properties) ->
25+
new FurnaceWorkbench(properties, () -> null));
26+
27+
/**
28+
* Standard constructor for registration.
29+
*/
30+
public FurnaceWorkbench(Properties properties) {
31+
super(properties, () -> ModBlockEntities.FURNACE_WORKBENCH_BE, IS_WIDE, IS_TALL);
32+
}
33+
34+
/**
35+
* Flexible constructor allowing for specialized Block Entity Types.
36+
*/
37+
public FurnaceWorkbench(Properties properties, Supplier<BlockEntityType<? extends FurnaceWorkbenchEntity>> supplier) {
38+
super(properties, supplier, IS_WIDE, IS_TALL);
39+
}
40+
41+
@Override
42+
public RenderShape getRenderShape(BlockState state) {
43+
// Essential so that the 2x2 model is visible
44+
return RenderShape.MODEL;
45+
}
46+
47+
@Nullable
48+
@Override
49+
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> type) {
50+
// Only the Master block (Lower-Left) should tick to process smelting
51+
// This helper ensures the logic only runs on the Server side for our specific BE
52+
return createTickerHelper(type, ModBlockEntities.FURNACE_WORKBENCH_BE, (lvl, pos, st, be) -> {
53+
if (be instanceof FurnaceWorkbenchEntity furnace) {
54+
furnace.tick(lvl, pos, st);
55+
}
56+
});
57+
}
58+
59+
@Nullable
60+
@Override
61+
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
62+
// AbstractWorkbench logic ensures only the Master block gets the entity.
63+
// We override it here to point specifically to our Furnace entity.
64+
return super.newBlockEntity(pos, state);
65+
}
66+
67+
@Override
68+
protected MapCodec<? extends FurnaceWorkbench> codec() {
69+
return CODEC;
70+
}
71+
72+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.tcm.MineTale.block.workbenches.entity;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import org.jspecify.annotations.Nullable;
7+
8+
import net.minecraft.core.BlockPos;
9+
import net.minecraft.network.chat.Component;
10+
import net.minecraft.world.Container;
11+
import net.minecraft.world.MenuProvider;
12+
import net.minecraft.world.entity.player.Inventory;
13+
import net.minecraft.world.entity.player.Player;
14+
import net.minecraft.world.inventory.AbstractContainerMenu;
15+
import net.minecraft.world.item.ItemStack;
16+
import net.minecraft.world.level.block.entity.BlockEntity;
17+
import net.minecraft.world.level.block.entity.BlockEntityType;
18+
import net.minecraft.world.level.block.state.BlockState;
19+
20+
public abstract class AbstractWorkbenchEntity extends BlockEntity implements MenuProvider {
21+
protected int tier = 1;
22+
protected double scanRadius = 5.0;
23+
24+
public AbstractWorkbenchEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
25+
super(type, pos, state);
26+
}
27+
28+
// --- TIER SYSTEM ---
29+
public int getTier() { return tier; }
30+
public void setTier(int tier) { this.tier = tier; setChanged(); }
31+
32+
// --- CHEST SCANNING ---
33+
public List<Container> getNearbyInventories() {
34+
List<Container> inventories = new ArrayList<>();
35+
BlockPos.betweenClosed(
36+
worldPosition.offset((int)-scanRadius, -2, (int)-scanRadius),
37+
worldPosition.offset((int)scanRadius, 2, (int)scanRadius)
38+
).forEach(pos -> {
39+
BlockEntity be = level.getBlockEntity(pos);
40+
if (be instanceof Container container) {
41+
inventories.add(container);
42+
}
43+
});
44+
45+
// Prioritization: Sort by proximity to prevent "chest prioritization" issues
46+
inventories.sort((a, b) -> {
47+
double distA = ((BlockEntity)a).getBlockPos().distSqr(this.worldPosition);
48+
double distB = ((BlockEntity)b).getBlockPos().distSqr(this.worldPosition);
49+
return Double.compare(distA, distB);
50+
});
51+
52+
return inventories;
53+
}
54+
55+
// --- RECYCLER LOGIC ---
56+
public void attemptRecycle(ItemStack stack) {
57+
// Logic to break down stack.getItem() and return components
58+
// to nearby chests or drop them at worldPosition.
59+
}
60+
61+
@Nullable
62+
@Override
63+
public abstract AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player);
64+
65+
@Override
66+
public Component getDisplayName() {
67+
return Component.translatable(this.getBlockState().getBlock().getDescriptionId());
68+
}
69+
70+
71+
}
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
package com.tcm.MineTale.block.workbenches.entity;
22

3+
import org.jspecify.annotations.Nullable;
4+
35
import com.tcm.MineTale.registry.ModBlockEntities;
46

57
import net.minecraft.core.BlockPos;
6-
import net.minecraft.world.level.block.entity.BlockEntity;
8+
import net.minecraft.world.entity.player.Inventory;
9+
import net.minecraft.world.entity.player.Player;
10+
import net.minecraft.world.inventory.AbstractContainerMenu;
711
import net.minecraft.world.level.block.state.BlockState;
812

9-
public class CampfireWorkbenchEntity extends BlockEntity {
13+
public class CampfireWorkbenchEntity extends AbstractWorkbenchEntity {
1014
public CampfireWorkbenchEntity(BlockPos blockPos, BlockState blockState) {
1115
super(ModBlockEntities.CAMPFIRE_WORKBENCH_BE, blockPos, blockState);
16+
17+
this.scanRadius = 6.0;
18+
this.tier = 1;
19+
}
20+
21+
@Override
22+
public @Nullable AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) {
23+
// TODO Auto-generated method stub
24+
throw new UnsupportedOperationException("Unimplemented method 'createMenu'");
1225
}
1326
}

0 commit comments

Comments
 (0)