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 .entity .BlockEntity ;
20+ import net .minecraft .world .level .block .state .BlockState ;
21+ import net .minecraft .world .level .block .state .StateDefinition ;
22+ import net .minecraft .world .level .block .state .properties .EnumProperty ;
23+ import org .jetbrains .annotations .Nullable ;
24+
25+ public class ChickenCoopBlock extends HorizontalDirectionalBlock {
26+ public static final EnumProperty <CoopPart > PART = EnumProperty .create ("part" , CoopPart .class );
27+
28+ public static final MapCodec <ChickenCoopBlock > CODEC = simpleCodec (ChickenCoopBlock ::new );
29+
30+ public ChickenCoopBlock (Properties properties ) {
31+ super (properties );
32+ // Default to the origin part (Bottom Front Left) facing North
33+ this .registerDefaultState (this .stateDefinition .any ()
34+ .setValue (FACING , Direction .NORTH )
35+ .setValue (PART , CoopPart .BOTTOM_FRONT_LEFT ));
36+ }
37+
38+ @ Override
39+ protected void createBlockStateDefinition (StateDefinition .Builder <Block , BlockState > builder ) {
40+ builder .add (FACING , PART );
41+ }
42+
43+ @ Nullable
44+ @ Override
45+ public BlockState getStateForPlacement (BlockPlaceContext context ) {
46+ BlockPos clickedPos = context .getClickedPos ();
47+ Level level = context .getLevel ();
48+ Direction facing = context .getHorizontalDirection ();
49+
50+ // Verification loop to ensure space is clear
51+ for (int x = 0 ; x < 3 ; x ++) {
52+ for (int z = 0 ; z < 2 ; z ++) {
53+ for (int y = 0 ; y < 3 ; y ++) {
54+ BlockPos targetPos = calculateOffset (clickedPos , facing , x , z , y );
55+ if (!level .getBlockState (targetPos ).canBeReplaced (context )) {
56+ return null ;
57+ }
58+ }
59+ }
60+ }
61+ // Set the initial block to the Center-Front part
62+ return this .defaultBlockState ()
63+ .setValue (FACING , facing )
64+ .setValue (PART , CoopPart .BOTTOM_FRONT_CENTER );
65+ }
66+
67+ @ Override
68+ public void setPlacedBy (Level level , BlockPos pos , BlockState state , @ Nullable LivingEntity placer , ItemStack stack ) {
69+ if (!level .isClientSide ()) {
70+ Direction facing = state .getValue (FACING );
71+
72+ for (int x = 0 ; x < 3 ; x ++) {
73+ for (int z = 0 ; z < 2 ; z ++) { // z=0 is front, z=1 is back (away)
74+ for (int y = 0 ; y < 3 ; y ++) {
75+ // Skip the block actually placed by the item (Bottom Front Center)
76+ if (x == 1 && z == 0 && y == 0 ) continue ;
77+
78+ BlockPos targetPos = calculateOffset (pos , facing , x , z , y );
79+ CoopPart part = CoopPart .getPartFromCoords (x , z , y );
80+
81+ level .setBlock (targetPos , state .setValue (PART , part ), 3 );
82+ }
83+ }
84+ }
85+ }
86+ }
87+
88+ @ Override
89+ protected BlockState updateShape (
90+ BlockState state ,
91+ LevelReader levelReader ,
92+ ScheduledTickAccess scheduledTickAccess ,
93+ BlockPos pos ,
94+ Direction direction ,
95+ BlockPos neighborPos ,
96+ BlockState neighborState ,
97+ RandomSource randomSource
98+ ) {
99+ // If a neighbor block that is supposed to be part of this coop is now AIR,
100+ // we return AIR to destroy this part of the coop as well.
101+ if (!neighborState .is (this ) && isNeighborPartOfCoop (state , direction )) {
102+ return Blocks .AIR .defaultBlockState ();
103+ }
104+
105+ return super .updateShape (state , levelReader , scheduledTickAccess , pos , direction , neighborPos , neighborState , randomSource );
106+ }
107+
108+ /**
109+ * Helper to check if the block in a specific direction is technically "connected"
110+ * to this specific part of the 3x3x2 grid.
111+ */
112+ private boolean isNeighborPartOfCoop (BlockState state , Direction dir ) {
113+ CoopPart part = state .getValue (PART );
114+ Direction facing = state .getValue (HorizontalDirectionalBlock .FACING );
115+
116+ // 1. Get the local offset of the neighbor block relative to this part
117+ // We convert the world Direction into a local x, y, z change
118+ int dx = dir .getStepX ();
119+ int dy = dir .getStepY ();
120+ int dz = dir .getStepZ ();
121+
122+ // 2. Adjust for rotation (Facing)
123+ // This ensures that "Front" always matches your Enum's Z-axis logic
124+ // Note: This math varies slightly depending on how your placement logic
125+ // maps "Front" to the world. Below is a standard mapping:
126+ int localDx , localDz ;
127+ switch (facing ) {
128+ case NORTH -> { localDx = dx ; localDz = dz ; }
129+ case SOUTH -> { localDx = -dx ; localDz = -dz ; }
130+ case WEST -> { localDx = dz ; localDz = -dx ; }
131+ case EAST -> { localDx = -dz ; localDz = dx ; }
132+ default -> { localDx = dx ; localDz = dz ; }
133+ }
134+
135+ // 3. Calculate the neighbor's hypothetical grid position
136+ int neighborX = part .getXOffset () + localDx ;
137+ int neighborZ = part .getZOffset () + localDz ;
138+ int neighborY = part .getYOffset () + dy ;
139+
140+ // 4. Check if these coordinates are within the 3x2x3 bounds
141+ // Width: 0-2 (X), Depth: 0-1 (Z), Height: 0-2 (Y)
142+ return neighborX >= 0 && neighborX < 3 &&
143+ neighborZ >= 0 && neighborZ < 2 &&
144+ neighborY >= 0 && neighborY < 3 ;
145+ }
146+
147+ @ Override
148+ public BlockState playerWillDestroy (Level level , BlockPos pos , BlockState state , Player player ) {
149+ if (!level .isClientSide ()) {
150+ Direction facing = state .getValue (FACING );
151+ CoopPart currentPart = state .getValue (PART );
152+
153+ // Calculate origin based on the piece being broken
154+ BlockPos origin = pos .subtract (calculateOffset (BlockPos .ZERO , facing ,
155+ currentPart .getXOffset (), currentPart .getZOffset (), currentPart .getYOffset ()));
156+
157+ // Use a flag to prevent re-entry if isNeighborPartOfCoop triggers
158+ for (int x = 0 ; x < 3 ; x ++) {
159+ for (int z = 0 ; z < 2 ; z ++) {
160+ for (int y = 0 ; y < 3 ; y ++) {
161+ BlockPos targetPos = calculateOffset (origin , facing , x , z , y );
162+ BlockState targetState = level .getBlockState (targetPos );
163+
164+ if (targetState .is (this )) {
165+ // 1. Handle Drops: This checks the loot table (JSON) and drops items
166+ if (!player .isCreative ()) {
167+ BlockEntity blockEntity = targetState .hasBlockEntity () ? level .getBlockEntity (targetPos ) : null ;
168+ Block .dropResources (targetState , level , targetPos , blockEntity , player , player .getMainHandItem ());
169+ }
170+
171+ // 2. Set to AIR with flag 3 (Update neighbors + Send to clients)
172+ // Using destroyBlock with 'false' for drops since we handled it above
173+ // for better control, or just setBlock to AIR.
174+ level .setBlock (targetPos , Blocks .AIR .defaultBlockState (), 3 );
175+
176+ // 3. Play break effects
177+ level .levelEvent (2001 , targetPos , Block .getId (targetState ));
178+ }
179+ }
180+ }
181+ }
182+ }
183+
184+ return super .playerWillDestroy (level , pos , state , player );
185+ }
186+
187+ /**
188+ * Rotates the 3x3x2 grid logic based on which way the player is facing.
189+ */
190+ private BlockPos calculateOffset (BlockPos origin , Direction facing , int x , int z , int y ) {
191+ // x-1 centers the 3-wide structure (0=left, 1=center, 2=right)
192+ int xAdjusted = x - 1 ;
193+
194+ // We use the 'facing' direction for depth (z).
195+ // This ensures z=1 is always "further away" from the player.
196+ return origin .relative (facing , z )
197+ .relative (facing .getClockWise (), xAdjusted )
198+ .above (y );
199+ }
200+
201+ @ Override
202+ protected MapCodec <? extends HorizontalDirectionalBlock > codec () {
203+ return CODEC ;
204+ }
205+ }
0 commit comments