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+ }
0 commit comments