Skip to content

Commit 445ba9a

Browse files
feat: chicken coop 3x3x2 is placeable in world
1 parent 8568d46 commit 445ba9a

3 files changed

Lines changed: 250 additions & 3 deletions

File tree

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package com.tcm.MineTale.block;
2+
3+
import com.mojang.serialization.MapCodec;
4+
import com.tcm.MineTale.util.CoopPart;
5+
6+
import net.minecraft.core.BlockPos;
7+
import net.minecraft.core.Direction;
8+
import net.minecraft.util.RandomSource;
9+
import net.minecraft.world.entity.LivingEntity;
10+
import net.minecraft.world.entity.player.Player;
11+
import net.minecraft.world.item.ItemStack;
12+
import net.minecraft.world.item.context.BlockPlaceContext;
13+
import net.minecraft.world.level.Level;
14+
import net.minecraft.world.level.LevelReader;
15+
import net.minecraft.world.level.ScheduledTickAccess;
16+
import net.minecraft.world.level.block.Block;
17+
import net.minecraft.world.level.block.Blocks;
18+
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
19+
import net.minecraft.world.level.block.state.BlockState;
20+
import net.minecraft.world.level.block.state.StateDefinition;
21+
import net.minecraft.world.level.block.state.properties.EnumProperty;
22+
import org.jetbrains.annotations.Nullable;
23+
24+
public class ChickenCoopBlock extends HorizontalDirectionalBlock {
25+
public static final EnumProperty<CoopPart> PART = EnumProperty.create("part", CoopPart.class);
26+
27+
public static final MapCodec<ChickenCoopBlock> CODEC = simpleCodec(ChickenCoopBlock::new);
28+
29+
public ChickenCoopBlock(Properties properties) {
30+
super(properties);
31+
// Default to the origin part (Bottom Front Left) facing North
32+
this.registerDefaultState(this.stateDefinition.any()
33+
.setValue(FACING, Direction.NORTH)
34+
.setValue(PART, CoopPart.BOTTOM_FRONT_LEFT));
35+
}
36+
37+
@Override
38+
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
39+
builder.add(FACING, PART);
40+
}
41+
42+
@Nullable
43+
@Override
44+
public BlockState getStateForPlacement(BlockPlaceContext context) {
45+
BlockPos clickedPos = context.getClickedPos();
46+
Level level = context.getLevel();
47+
Direction facing = context.getHorizontalDirection();
48+
49+
// Verification loop to ensure space is clear
50+
for (int x = 0; x < 3; x++) {
51+
for (int z = 0; z < 2; z++) {
52+
for (int y = 0; y < 3; y++) {
53+
BlockPos targetPos = calculateOffset(clickedPos, facing, x, z, y);
54+
if (!level.getBlockState(targetPos).canBeReplaced(context)) {
55+
return null;
56+
}
57+
}
58+
}
59+
}
60+
// Set the initial block to the Center-Front part
61+
return this.defaultBlockState()
62+
.setValue(FACING, facing)
63+
.setValue(PART, CoopPart.BOTTOM_FRONT_CENTER);
64+
}
65+
66+
@Override
67+
public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
68+
if (!level.isClientSide()) {
69+
Direction facing = state.getValue(FACING);
70+
71+
for (int x = 0; x < 3; x++) {
72+
for (int z = 0; z < 2; z++) { // z=0 is front, z=1 is back (away)
73+
for (int y = 0; y < 3; y++) {
74+
// Skip the block actually placed by the item (Bottom Front Center)
75+
if (x == 1 && z == 0 && y == 0) continue;
76+
77+
BlockPos targetPos = calculateOffset(pos, facing, x, z, y);
78+
CoopPart part = CoopPart.getPartFromCoords(x, z, y);
79+
80+
level.setBlock(targetPos, state.setValue(PART, part), 3);
81+
}
82+
}
83+
}
84+
}
85+
}
86+
87+
@Override
88+
protected BlockState updateShape(
89+
BlockState state,
90+
LevelReader levelReader,
91+
ScheduledTickAccess scheduledTickAccess,
92+
BlockPos pos,
93+
Direction direction,
94+
BlockPos neighborPos,
95+
BlockState neighborState,
96+
RandomSource randomSource
97+
) {
98+
// If a neighbor block that is supposed to be part of this coop is now AIR,
99+
// we return AIR to destroy this part of the coop as well.
100+
if (!neighborState.is(this) && isNeighborPartOfCoop(state, direction)) {
101+
return Blocks.AIR.defaultBlockState();
102+
}
103+
104+
return super.updateShape(state, levelReader, scheduledTickAccess, pos, direction, neighborPos, neighborState, randomSource);
105+
}
106+
107+
/**
108+
* Helper to check if the block in a specific direction is technically "connected"
109+
* to this specific part of the 3x3x2 grid.
110+
*/
111+
private boolean isNeighborPartOfCoop(BlockState state, Direction dir) {
112+
// For a 3x3x2, we can be lazy: if ANY adjacent block of the same type is removed,
113+
// the whole thing should probably go.
114+
// You can refine this to only check the "Master" block if you want more stability.
115+
return true;
116+
}
117+
118+
@Override
119+
public BlockState playerWillDestroy(Level level, BlockPos pos, BlockState state, Player player) {
120+
if (!level.isClientSide()) {
121+
Direction facing = state.getValue(FACING);
122+
CoopPart currentPart = state.getValue(PART);
123+
124+
// Find the absolute origin (0,0,0) by subtracting the current part's offset
125+
BlockPos origin = pos.subtract(calculateOffset(BlockPos.ZERO, facing,
126+
currentPart.getXOffset(), currentPart.getZOffset(), currentPart.getYOffset()));
127+
128+
// Break all 18 blocks in the 3x3x2 grid
129+
for (int x = 0; x < 3; x++) {
130+
for (int z = 0; z < 2; z++) {
131+
for (int y = 0; y < 3; y++) {
132+
BlockPos targetPos = calculateOffset(origin, facing, x, z, y);
133+
BlockState targetState = level.getBlockState(targetPos);
134+
135+
// Only break blocks that belong to this mod's chicken coop
136+
if (targetState.is(this)) {
137+
level.setBlock(targetPos, Blocks.AIR.defaultBlockState(), 3);
138+
// 2001 is the ID for block break particles + sound
139+
level.levelEvent(2001, targetPos, Block.getId(targetState));
140+
}
141+
}
142+
}
143+
}
144+
}
145+
146+
// Call super and return the resulting state as required by 1.21.1
147+
return super.playerWillDestroy(level, pos, state, player);
148+
}
149+
150+
/**
151+
* Rotates the 3x3x2 grid logic based on which way the player is facing.
152+
*/
153+
private BlockPos calculateOffset(BlockPos origin, Direction facing, int x, int z, int y) {
154+
// x-1 centers the 3-wide structure (0=left, 1=center, 2=right)
155+
int xAdjusted = x - 1;
156+
157+
// We use the 'facing' direction for depth (z).
158+
// This ensures z=1 is always "further away" from the player.
159+
return origin.relative(facing, z)
160+
.relative(facing.getClockWise(), xAdjusted)
161+
.above(y);
162+
}
163+
164+
@Override
165+
protected MapCodec<? extends HorizontalDirectionalBlock> codec() {
166+
return CODEC;
167+
}
168+
}

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
import java.util.ArrayList;
44
import java.util.List;
5-
import java.util.Map;
65
import java.util.function.Function;
76

87
import com.tcm.MineTale.MineTale;
98
import com.tcm.MineTale.block.workbenches.*;
9+
import com.tcm.MineTale.block.ChickenCoopBlock;
1010
import com.tcm.MineTale.item.ModCreativeTab;
1111

1212
import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents;
@@ -18,13 +18,11 @@
1818
import net.minecraft.resources.ResourceKey;
1919
import net.minecraft.sounds.SoundEvents;
2020
import net.minecraft.world.item.BlockItem;
21-
import net.minecraft.world.item.DyeColor;
2221
import net.minecraft.world.item.Item;
2322
import net.minecraft.world.item.Items;
2423
import net.minecraft.world.level.block.*;
2524
import net.minecraft.world.level.block.entity.BlockEntityType;
2625
import net.minecraft.world.level.block.state.BlockBehaviour;
27-
import net.minecraft.world.level.block.state.properties.BedPart;
2826
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
2927
import net.minecraft.world.level.material.MapColor;
3028
import net.minecraft.world.level.material.PushReaction;
@@ -366,6 +364,22 @@ public class ModBlocks {
366364
// public static final Block THORIUM_ORE_STONE = registerOreBlock("thorium_ore_stone", Block::new, BlockBehaviour.Properties.of().strength(2).requiresCorrectToolForDrops(), Blocks.STONE, Items.COPPER_ORE, 1);
367365
// public static final Block THORIUM_ORE_SANDSTONE = registerOreBlock("thorium_ore_sandstone", Block::new, BlockBehaviour.Properties.of().strength(2).requiresCorrectToolForDrops(), Blocks.SANDSTONE, Items.COPPER_ORE, 1);
368366

367+
public static final Block CHICKEN_COOP = register(
368+
"chicken_coop",
369+
ChickenCoopBlock::new,
370+
BlockBehaviour.Properties.of()
371+
.mapColor(MapColor.WOOD)
372+
.instrument(NoteBlockInstrument.BASS)
373+
.strength(2.0f, 3.0f)
374+
.sound(SoundType.WOOD)
375+
// This is important for multi-blocks:
376+
.noOcclusion()
377+
// 1.21.1 requires manual ignition/burning logic if you want it flammable,
378+
// but standard wood properties are a good start.
379+
.ignitedByLava(),
380+
true // We want a BlockItem so we can place it!
381+
);
382+
369383
/**
370384
* Adds all mod-registered blocks to the MineTale creative tab and logs the action.
371385
*
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package com.tcm.MineTale.util;
2+
3+
import net.minecraft.util.StringRepresentable;
4+
import org.jetbrains.annotations.NotNull;
5+
6+
public enum CoopPart implements StringRepresentable {
7+
// 18 Parts: 3 Wide (x) x 2 Deep (z) x 3 High (y)
8+
9+
// --- BOTTOM LAYER (y=0) ---
10+
BOTTOM_FRONT_LEFT("bottom_front_left", 0, 0, 0),
11+
BOTTOM_FRONT_CENTER("bottom_front_center", 1, 0, 0),
12+
BOTTOM_FRONT_RIGHT("bottom_front_right", 2, 0, 0),
13+
BOTTOM_BACK_LEFT("bottom_back_left", 0, 1, 0),
14+
BOTTOM_BACK_CENTER("bottom_back_center", 1, 1, 0),
15+
BOTTOM_BACK_RIGHT("bottom_back_right", 2, 1, 0),
16+
17+
// --- MIDDLE LAYER (y=1) ---
18+
MIDDLE_FRONT_LEFT("middle_front_left", 0, 0, 1),
19+
MIDDLE_FRONT_CENTER("middle_front_center", 1, 0, 1),
20+
MIDDLE_FRONT_RIGHT("middle_front_right", 2, 0, 1),
21+
MIDDLE_BACK_LEFT("middle_back_left", 0, 1, 1),
22+
MIDDLE_BACK_CENTER("middle_back_center", 1, 1, 1),
23+
MIDDLE_BACK_RIGHT("middle_back_right", 2, 1, 1),
24+
25+
// --- TOP LAYER (y=2) ---
26+
TOP_FRONT_LEFT("top_front_left", 0, 0, 2),
27+
TOP_FRONT_CENTER("top_front_center", 1, 0, 2),
28+
TOP_FRONT_RIGHT("top_front_right", 2, 0, 2),
29+
TOP_BACK_LEFT("top_back_left", 0, 1, 2),
30+
TOP_BACK_CENTER("top_back_center", 1, 1, 2),
31+
TOP_BACK_RIGHT("top_back_right", 2, 1, 2);
32+
33+
private final String name;
34+
private final int xOffset;
35+
private final int zOffset;
36+
private final int yOffset;
37+
38+
CoopPart(String name, int x, int z, int y) {
39+
this.name = name;
40+
this.xOffset = x;
41+
this.zOffset = z;
42+
this.yOffset = y;
43+
}
44+
45+
@Override
46+
public @NotNull String getSerializedName() {
47+
return this.name;
48+
}
49+
50+
public int getXOffset() { return xOffset; }
51+
public int getYOffset() { return yOffset; }
52+
public int getZOffset() { return zOffset; }
53+
54+
/**
55+
* Logic to find the enum based on the position in the 3x3x2 grid.
56+
*/
57+
public static CoopPart getPartFromCoords(int x, int z, int y) {
58+
for (CoopPart part : values()) {
59+
if (part.xOffset == x && part.zOffset == z && part.yOffset == y) {
60+
return part;
61+
}
62+
}
63+
return BOTTOM_FRONT_LEFT;
64+
}
65+
}

0 commit comments

Comments
 (0)