Skip to content

Commit 890482a

Browse files
fix: make model line up with bounding box
1 parent 43cf082 commit 890482a

17 files changed

Lines changed: 176 additions & 539 deletions

src/client/java/com/tcm/MineTale/block/workbenches/screen/WorkbenchWorkbenchScreen.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@
77
import java.util.stream.Collectors;
88

99
import com.tcm.MineTale.MineTale;
10-
import com.tcm.MineTale.block.workbenches.entity.AbstractWorkbenchEntity;
1110
import com.tcm.MineTale.block.workbenches.menu.AbstractWorkbenchContainerMenu;
1211
import com.tcm.MineTale.block.workbenches.menu.WorkbenchWorkbenchMenu;
1312
import com.tcm.MineTale.mixin.client.ClientRecipeBookAccessor;
1413
import com.tcm.MineTale.mixin.client.RecipeBookComponentAccessor;
1514
import com.tcm.MineTale.network.CraftRequestPayload;
1615
import com.tcm.MineTale.recipe.MineTaleRecipeBookComponent;
17-
import com.tcm.MineTale.recipe.WorkbenchRecipe;
1816
import com.tcm.MineTale.registry.ModBlocks;
1917
import com.tcm.MineTale.registry.ModRecipeDisplay;
2018
import com.tcm.MineTale.registry.ModRecipes;
@@ -30,7 +28,6 @@
3028
import net.minecraft.client.renderer.RenderPipelines;
3129
import net.minecraft.core.HolderSet;
3230
import net.minecraft.resources.Identifier;
33-
import net.minecraft.server.level.ServerPlayer;
3431
import net.minecraft.world.entity.player.Inventory;
3532
import net.minecraft.world.entity.player.Player;
3633
import net.minecraft.world.item.Item;
@@ -231,6 +228,7 @@ private boolean canCraft(Player player, RecipeDisplayEntry entry, int craftCount
231228
// IF we use a helper that handles the hashing correctly.
232229

233230
// Strategy: Use the stream of holders as a List key (Lists have stable hashcodes)
231+
@SuppressWarnings("deprecation")
234232
HolderSet<Item> key = ing.items().collect(Collectors.collectingAndThen(Collectors.toList(), HolderSet::direct));
235233

236234
aggregatedRequirements.put(key, aggregatedRequirements.getOrDefault(key, 0) + 1);

src/client/java/com/tcm/MineTale/datagen/ModModelProvider.java

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
package com.tcm.MineTale.datagen;
22

3+
import com.tcm.MineTale.MineTale;
34
import com.tcm.MineTale.registry.ModBlocks;
45
import net.fabricmc.fabric.api.client.datagen.v1.provider.FabricModelProvider;
56
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
67
import net.minecraft.client.data.models.BlockModelGenerators;
78
import net.minecraft.client.data.models.ItemModelGenerators;
89
import net.minecraft.client.data.models.blockstates.MultiVariantGenerator;
910
import net.minecraft.client.data.models.blockstates.PropertyDispatch;
10-
import net.minecraft.client.data.models.model.ModelLocationUtils;
1111
import net.minecraft.client.renderer.block.model.VariantMutator;
1212
import net.minecraft.core.Direction;
1313
import net.minecraft.resources.Identifier;
1414
import net.minecraft.world.level.block.Block;
1515
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
16-
import net.minecraft.world.level.block.state.properties.ChestType;
1716
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
1817

1918
public class ModModelProvider extends FabricModelProvider {
@@ -72,38 +71,28 @@ public void generateBlockStateModels(BlockModelGenerators blockStateModelGenerat
7271
blockStateModelGenerator.woodProvider(ModBlocks.WINDWILLOW_LOG).logWithHorizontal(ModBlocks.WINDWILLOW_LOG);
7372
blockStateModelGenerator.woodProvider(ModBlocks.WILD_WISTERIA_LOG).logWithHorizontal(ModBlocks.WILD_WISTERIA_LOG).wood(ModBlocks.WILD_WISTERIA_WOOD);
7473

75-
registerLargeWorkbench(blockStateModelGenerator, ModBlocks.FURNACE_WORKBENCH_BLOCK_T1);
76-
registerLargeWorkbench(blockStateModelGenerator, ModBlocks.FURNACE_WORKBENCH_BLOCK_T2);
74+
registerFurnaceWorkbench(blockStateModelGenerator, ModBlocks.FURNACE_WORKBENCH_BLOCK_T1);
75+
registerFurnaceWorkbench(blockStateModelGenerator, ModBlocks.FURNACE_WORKBENCH_BLOCK_T2);
7776
}
7877

79-
private void registerLargeWorkbench(BlockModelGenerators generator, Block block) {
80-
// 1. Get the base identifier (e.g., minetale:block/furnace_workbench_block_t1)
81-
Identifier blockId = ModelLocationUtils.getModelLocation(block);
78+
private void registerFurnaceWorkbench(BlockModelGenerators generator, Block block) {
79+
// 1. Manually define the shared model paths
80+
// Path: assets/minetale/models/block/bench/furnace_top.json
81+
Identifier topModel = Identifier.fromNamespaceAndPath(MineTale.MOD_ID, "block/bench/furnace_top");
82+
Identifier bottomModel = Identifier.fromNamespaceAndPath(MineTale.MOD_ID, "block/bench/furnace_bottom");
83+
Identifier inventoryModel = Identifier.fromNamespaceAndPath(MineTale.MOD_ID, "block/bench/furnace_inventory");
8284

83-
// 2. Build the references to your manual JSON files
84-
// .withSuffix() creates: minetale:block/furnace_workbench_block_t1_bottom_left, etc.
85-
Identifier bottomLeft = blockId.withSuffix("_bottom_left");
86-
Identifier bottomRight = blockId.withSuffix("_bottom_right");
87-
Identifier topLeft = blockId.withSuffix("_top_left");
88-
Identifier topRight = blockId.withSuffix("_top_right");
89-
Identifier inventory = blockId.withSuffix("_inventory");
90-
91-
// 3. Dispatch to Blockstate (Tells the game which model to show for each state)
85+
// 4. Dispatch to Blockstate
9286
generator.blockStateOutput.accept(MultiVariantGenerator.dispatch(block)
93-
.with(PropertyDispatch.initial(BlockStateProperties.DOUBLE_BLOCK_HALF, BlockStateProperties.CHEST_TYPE)
94-
.select(DoubleBlockHalf.LOWER, ChestType.LEFT, BlockModelGenerators.plainVariant(bottomLeft))
95-
.select(DoubleBlockHalf.LOWER, ChestType.RIGHT, BlockModelGenerators.plainVariant(bottomRight))
96-
.select(DoubleBlockHalf.UPPER, ChestType.LEFT, BlockModelGenerators.plainVariant(topLeft))
97-
.select(DoubleBlockHalf.UPPER, ChestType.RIGHT, BlockModelGenerators.plainVariant(topRight))
98-
// Support the 'SINGLE' state as a fallback
99-
.select(DoubleBlockHalf.LOWER, ChestType.SINGLE, BlockModelGenerators.plainVariant(bottomLeft))
100-
.select(DoubleBlockHalf.UPPER, ChestType.SINGLE, BlockModelGenerators.plainVariant(topLeft))
87+
.with(PropertyDispatch.initial(BlockStateProperties.DOUBLE_BLOCK_HALF)
88+
.select(DoubleBlockHalf.LOWER, BlockModelGenerators.plainVariant(bottomModel))
89+
.select(DoubleBlockHalf.UPPER, BlockModelGenerators.plainVariant(topModel))
10190
)
10291
.with(WORKBENCH_ROTATION)
10392
);
10493

105-
// 4. Map the Item in your hand to the inventory JSON
106-
generator.registerSimpleItemModel(block, inventory);
94+
// 5. Register the Item Model
95+
generator.registerSimpleItemModel(block, inventoryModel);
10796
}
10897

10998
/**

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import static com.tcm.MineTale.item.ModCreativeTab.MINETALE_CREATIVE_TAB;
3434
import static com.tcm.MineTale.item.ModCreativeTab.MINETALE_CREATIVE_TAB_KEY;
3535

36-
import java.util.List;
3736
import java.util.Optional;
3837

3938
public class MineTale implements ModInitializer {

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

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import net.minecraft.world.entity.player.Player;
1010
import net.minecraft.world.item.ItemStack;
1111
import net.minecraft.world.item.context.BlockPlaceContext;
12+
import net.minecraft.world.level.BlockGetter;
13+
import net.minecraft.world.level.EmptyBlockGetter;
1214
import net.minecraft.world.level.Level;
1315
import net.minecraft.world.level.LevelReader;
1416
import net.minecraft.world.level.ScheduledTickAccess;
@@ -22,6 +24,8 @@
2224
import net.minecraft.world.level.block.state.StateDefinition;
2325
import net.minecraft.world.level.block.state.properties.*;
2426
import net.minecraft.world.phys.BlockHitResult;
27+
import net.minecraft.world.phys.shapes.Shapes;
28+
import net.minecraft.world.phys.shapes.VoxelShape;
2529

2630
import org.jetbrains.annotations.Nullable;
2731

@@ -33,6 +37,7 @@ public abstract class AbstractWorkbench<E extends AbstractWorkbenchEntity> exten
3337
public static final EnumProperty<Direction> FACING = HorizontalDirectionalBlock.FACING;
3438
public static final EnumProperty<DoubleBlockHalf> HALF = BlockStateProperties.DOUBLE_BLOCK_HALF;
3539
public static final EnumProperty<ChestType> TYPE = BlockStateProperties.CHEST_TYPE;
40+
public static final BooleanProperty LIT = BlockStateProperties.LIT;
3641

3742
protected final Supplier<BlockEntityType<? extends E>> blockEntityType;
3843
protected final boolean isWide;
@@ -48,7 +53,8 @@ protected AbstractWorkbench(Properties properties, Supplier<BlockEntityType<? ex
4853
this.registerDefaultState(this.stateDefinition.any()
4954
.setValue(FACING, Direction.NORTH)
5055
.setValue(HALF, DoubleBlockHalf.LOWER)
51-
.setValue(TYPE, ChestType.SINGLE));
56+
.setValue(TYPE, ChestType.SINGLE)
57+
.setValue(LIT, false));
5258
}
5359

5460
public int getTier() {
@@ -58,37 +64,42 @@ public int getTier() {
5864
@Override
5965
@Nullable
6066
public BlockState getStateForPlacement(BlockPlaceContext context) {
67+
Direction facing = context.getHorizontalDirection().getOpposite();
6168
BlockPos pos = context.getClickedPos();
6269
Level level = context.getLevel();
63-
Direction facing = context.getHorizontalDirection();
6470

65-
// Check horizontal space
6671
if (isWide) {
72+
// Find the 'Side' block relative to the player's perspective
73+
// Clockwise from the 'Front' (Opposite) is the Right side
6774
BlockPos sidePos = pos.relative(facing.getClockWise());
75+
6876
if (!level.getBlockState(sidePos).canBeReplaced(context)) return null;
69-
if (isTall && !level.getBlockState(sidePos.above()).canBeReplaced(context)) return null;
70-
}
71-
72-
// Check vertical space
73-
if (isTall) {
74-
if (!level.getBlockState(pos.above()).canBeReplaced(context)) return null;
7577
}
7678

77-
return this.defaultBlockState().setValue(FACING, facing).setValue(TYPE, isWide ? ChestType.LEFT : ChestType.SINGLE);
79+
// MANDATORY: The block you clicked MUST be the LEFT (Master) block.
80+
return this.defaultBlockState()
81+
.setValue(FACING, facing)
82+
.setValue(TYPE, isWide ? ChestType.LEFT : ChestType.SINGLE)
83+
.setValue(HALF, DoubleBlockHalf.LOWER)
84+
.setValue(LIT, false);
7885
}
7986

8087
@Override
8188
public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
8289
Direction facing = state.getValue(FACING);
8390

8491
if (isWide) {
92+
// Calculate the side block exactly as we did in getStateForPlacement
8593
BlockPos sidePos = pos.relative(facing.getClockWise());
86-
// Place Right Side
94+
95+
// Place the RIGHT (Slave/Invisible) side
8796
level.setBlock(sidePos, state.setValue(TYPE, ChestType.RIGHT), 3);
8897

8998
if (isTall) {
90-
// Place Upper Row
99+
// Place the TOP row
100+
// The block above the click is TOP-LEFT
91101
level.setBlock(pos.above(), state.setValue(HALF, DoubleBlockHalf.UPPER).setValue(TYPE, ChestType.LEFT), 3);
102+
// The block above the side is TOP-RIGHT
92103
level.setBlock(sidePos.above(), state.setValue(HALF, DoubleBlockHalf.UPPER).setValue(TYPE, ChestType.RIGHT), 3);
93104
}
94105
} else if (isTall) {
@@ -147,7 +158,7 @@ private boolean isCompatiblePart(BlockState current, BlockState neighbor) {
147158
*/
148159
@Override
149160
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
150-
builder.add(FACING, HALF, TYPE);
161+
builder.add(FACING, HALF, TYPE, LIT);
151162
}
152163

153164
@Nullable
@@ -222,4 +233,50 @@ public BlockPos getMasterPos(BlockState state, BlockPos pos) {
222233

223234
return master;
224235
}
236+
237+
/**
238+
* Rotates a VoxelShape to match the target Direction, assuming the original was North.
239+
*/
240+
protected static VoxelShape rotateShape(Direction to, VoxelShape shape) {
241+
VoxelShape[] buffer = { shape, Shapes.empty() };
242+
// get2DDataValue returns: S=0, W=1, N=2, E=3.
243+
// We calculate steps relative to North.
244+
int times = (to.get2DDataValue() - Direction.NORTH.get2DDataValue() + 4) % 4;
245+
246+
for (int i = 0; i < times; i++) {
247+
buffer[1] = Shapes.empty();
248+
buffer[0].forAllBoxes((minX, minY, minZ, maxX, maxY, maxZ) -> {
249+
// Standard 90-degree rotation formula for bounding boxes
250+
buffer[1] = Shapes.or(buffer[1], Block.box(
251+
(1.0 - maxZ) * 16.0,
252+
minY * 16.0,
253+
minX * 16.0,
254+
(1.0 - minZ) * 16.0,
255+
maxY * 16.0,
256+
maxX * 16.0
257+
));
258+
});
259+
buffer[0] = buffer[1];
260+
}
261+
return buffer[0];
262+
}
263+
264+
@Override
265+
protected boolean propagatesSkylightDown(BlockState state) {
266+
return true;
267+
}
268+
269+
@Override
270+
protected VoxelShape getOcclusionShape(BlockState state) {
271+
// This tells the engine exactly which parts of the block hide others.
272+
// Returning the custom shape instead of a full cube prevents culling of neighbor faces.
273+
return state.getShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
274+
}
275+
276+
@Override
277+
protected float getShadeBrightness(BlockState state, BlockGetter level, BlockPos pos) {
278+
// In your source: return blockState.isCollisionShapeFullBlock(...) ? 0.2F : 1.0F;
279+
// We want 1.0F to ensure the block doesn't cast a pitch-black shadow on itself.
280+
return 1.0F;
281+
}
225282
}

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

Lines changed: 55 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.tcm.MineTale.registry.ModTiers.FurnaceTier;
1111

1212
import net.minecraft.core.BlockPos;
13+
import net.minecraft.core.Direction;
1314
import net.minecraft.world.level.BlockGetter;
1415
import net.minecraft.world.level.Level;
1516
import net.minecraft.world.level.block.Block;
@@ -90,45 +91,66 @@ protected MapCodec<? extends FurnaceWorkbench> codec() {
9091
*/
9192
@Override
9293
public RenderShape getRenderShape(BlockState state) {
93-
// Essential so that the 2x2 model is visible
94-
return RenderShape.MODEL;
94+
// We only render the model for the LEFT side (Master)
95+
// Both Bottom-Left and Top-Left are 'LEFT', so they both render their respective halves
96+
if (state.getValue(TYPE) == ChestType.LEFT || state.getValue(TYPE) == ChestType.SINGLE) {
97+
return RenderShape.MODEL;
98+
}
99+
100+
// Hide the RIGHT side blocks (They still have collision thanks to your getShape method)
101+
return RenderShape.INVISIBLE;
95102
}
103+
104+
// left, bottom, back, right, top, front;
105+
private static final VoxelShape HALF_SLAB = Block.box(0.0, 0.0, 0.0, 16.0, 8.0, 16.0);
106+
private static final VoxelShape FURNACE_CORE = Block.box(4.0, 8.0, 2.0, 16.0, 15.0, 14.0);
107+
private static final VoxelShape TOP_SLAB = Block.box(2.0, -1.0, 1.0, 16.0, 5.0, 15.0);
108+
private static final VoxelShape FURNACE_CHIMNEY = Block.box(7.0, 5.0, 4.0, 16.0, 15.0, 12.0);
96109

97-
protected static final VoxelShape LOWER_LEFT_SHAPE = Shapes.or(
98-
Block.box(0.0, 0.0, 0.0, 16.0, 8.0, 16.0), // Base
99-
Block.box(2.0, 8.0, 2.0, 16.0, 16.0, 14.0) // Mid-section
100-
);
110+
// Pre-combine them into the 4 final shapes
111+
private static final VoxelShape RAW_LL = Shapes.or(HALF_SLAB, FURNACE_CORE);
112+
private static final VoxelShape RAW_LR = Shapes.or(HALF_SLAB, mirrorX(FURNACE_CORE));
113+
private static final VoxelShape RAW_UL = Shapes.or(TOP_SLAB, FURNACE_CHIMNEY);
114+
private static final VoxelShape RAW_UR = Shapes.or(mirrorX(TOP_SLAB), mirrorX(FURNACE_CHIMNEY));
101115

102-
protected static final VoxelShape LOWER_RIGHT_SHAPE = Shapes.or(
103-
Block.box(0.0, 0.0, 0.0, 16.0, 8.0, 16.0), // Base
104-
Block.box(0.0, 8.0, 2.0, 13.0, 16.0, 14.0) // Mid-section (Shifted X: 29-16=13)
105-
);
116+
@Override
117+
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
118+
Direction dir = state.getValue(FACING);
119+
boolean isUpper = state.getValue(HALF) == DoubleBlockHalf.UPPER;
120+
boolean isRightSide = state.getValue(TYPE) == ChestType.LEFT || state.getValue(TYPE) == ChestType.SINGLE;
106121

107-
protected static final VoxelShape UPPER_LEFT_SHAPE = Shapes.or(
108-
Block.box(1.0, 0.0, 1.0, 16.0, 5.0, 14.0), // Tabletop (flush right)
109-
Block.box(8.0, 5.0, 4.0, 16.0, 9.0, 12.0), // Shelf (flush right)
110-
Block.box(6.0, 9.0, 3.0, 16.0, 12.0, 13.0) // Top Cap (flush right)
111-
);
122+
VoxelShape baseShape = isUpper
123+
? (isRightSide ? RAW_UL : RAW_UR)
124+
: (isRightSide ? RAW_LL : RAW_LR);
112125

126+
// Call the method now living in AbstractWorkbench
127+
return rotateShape(dir, baseShape);
128+
}
113129

114-
protected static final VoxelShape UPPER_RIGHT_SHAPE = Shapes.or(
115-
Block.box(0.0, 0.0, 1.0, 15.0, 5.0, 14.0), // Tabletop (Shifted X: 31-16=15)
116-
Block.box(0.0, 5.0, 4.0, 8.0, 9.0, 12.0), // Shelf (Shifted X: 24-16=8)
117-
Block.box(0.0, 9.0, 3.0, 10.0, 12.0, 13.0) // Top Cap (Shifted X: 26-16=10)
118-
);
130+
/**
131+
* Mirrors a VoxelShape horizontally across the X-axis (West/East).
132+
* @param shape The original shape to mirror.
133+
* @return A new VoxelShape mirrored on the X-axis.
134+
*/
135+
public static VoxelShape mirrorX(VoxelShape shape) {
136+
VoxelShape[] result = { Shapes.empty() };
119137

120-
@Override
121-
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
122-
DoubleBlockHalf half = state.getValue(HALF);
123-
ChestType type = state.getValue(TYPE);
124-
125-
// We select the correct pre-defined VoxelShape based on position
126-
if (half == DoubleBlockHalf.LOWER) {
127-
return (type == ChestType.LEFT || type == ChestType.SINGLE)
128-
? LOWER_LEFT_SHAPE : LOWER_RIGHT_SHAPE;
129-
} else {
130-
return (type == ChestType.LEFT || type == ChestType.SINGLE)
131-
? UPPER_LEFT_SHAPE : UPPER_RIGHT_SHAPE;
132-
}
138+
shape.forAllBoxes((minX, minY, minZ, maxX, maxY, maxZ) -> {
139+
// Mirror the X coordinates:
140+
// The new minX is 1.0 minus the old maxX
141+
// The new maxX is 1.0 minus the old minX
142+
// (Note: VoxelShape uses 0.0-1.0 scale internally, not 0-16)
143+
VoxelShape flippedBox = Block.box(
144+
(1.0 - maxX) * 16.0,
145+
minY * 16.0,
146+
minZ * 16.0,
147+
(1.0 - minX) * 16.0,
148+
maxY * 16.0,
149+
maxZ * 16.0
150+
);
151+
result[0] = Shapes.or(result[0], flippedBox);
152+
});
153+
154+
return result[0];
133155
}
134156
}

0 commit comments

Comments
 (0)