From 6b878bd6a7c071a5b9eb261b73cb40bff8b80921 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 20:13:46 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20to=20`feat/ch?= =?UTF-8?q?icken-coop`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docstrings generation was requested by @The-Code-Monkey. * https://github.com/CodeMonkeysMods/MineTale/pull/64#issuecomment-4000003932 The following files were modified: * `src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java` * `src/main/java/com/tcm/MineTale/util/CoopPart.java` --- .../tcm/MineTale/block/ChickenCoopBlock.java | 272 ++++++++++++++++++ .../java/com/tcm/MineTale/util/CoopPart.java | 104 +++++++ 2 files changed, 376 insertions(+) create mode 100644 src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java create mode 100644 src/main/java/com/tcm/MineTale/util/CoopPart.java diff --git a/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java b/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java new file mode 100644 index 0000000..1ab44b5 --- /dev/null +++ b/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java @@ -0,0 +1,272 @@ +package com.tcm.MineTale.block; + +import com.mojang.serialization.MapCodec; +import com.tcm.MineTale.util.CoopPart; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.ScheduledTickAccess; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.HorizontalDirectionalBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import org.jetbrains.annotations.Nullable; + +public class ChickenCoopBlock extends HorizontalDirectionalBlock { + public static final EnumProperty PART = EnumProperty.create("part", CoopPart.class); + + public static final MapCodec CODEC = simpleCodec(ChickenCoopBlock::new); + + /** + * Constructs a ChickenCoopBlock and initialises its default state to face north and be the bottom front left part. + * + * @param properties block properties used to create this block + */ + public ChickenCoopBlock(Properties properties) { + super(properties); + // Default to the origin part (Bottom Front Left) facing North + this.registerDefaultState(this.stateDefinition.any() + .setValue(FACING, Direction.NORTH) + .setValue(PART, CoopPart.BOTTOM_FRONT_LEFT)); + } + + /** + * Declare the block-state properties used by this block. + * + * Adds the horizontal facing and coop-part properties to the provided state definition builder so instances + * can track orientation and which sub-part of the multi-block coop they represent. + * + * @param builder the block state definition builder to receive properties + */ + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + builder.add(FACING, PART); + } + + /** + * Determine the block state to use when placing a chicken coop, after verifying the full 3×2×3 area is replaceable. + * + * @return the initial block state with FACING set to the placement direction and PART set to BOTTOM_FRONT_CENTER, or `null` if any position in the required area cannot be replaced + */ + @Nullable + @Override + public BlockState getStateForPlacement(BlockPlaceContext context) { + BlockPos clickedPos = context.getClickedPos(); + Level level = context.getLevel(); + Direction facing = context.getHorizontalDirection(); + + // Verification loop to ensure space is clear + for (int x = 0; x < 3; x++) { + for (int z = 0; z < 2; z++) { + for (int y = 0; y < 3; y++) { + BlockPos targetPos = calculateOffset(clickedPos, facing, x, z, y); + if (!level.getBlockState(targetPos).canBeReplaced(context)) { + return null; + } + } + } + } + // Set the initial block to the Center-Front part + return this.defaultBlockState() + .setValue(FACING, facing) + .setValue(PART, CoopPart.BOTTOM_FRONT_CENTER); + } + + /** + * Populates the remaining parts of the 3x2x3 chicken coop around the placed block on the server. + * + * For each coordinate in the coop's local grid (3×2×3) except the bottom-front-centre cell (the block placed by the item), + * places the same block state with the corresponding `PART` value so the multiblock structure is fully constructed. + * + * @param level the level in which the block was placed; placement actions are only performed on the server side + * @param pos the position of the block placed by the player/item + * @param state the block state of the placed block (its `FACING` is used to orient the coop) + * @param placer the entity that placed the block, or `null` if none + * @param stack the item stack used to place the block + */ + @Override + public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { + if (!level.isClientSide()) { + Direction facing = state.getValue(FACING); + + for (int x = 0; x < 3; x++) { + for (int z = 0; z < 2; z++) { // z=0 is front, z=1 is back (away) + for (int y = 0; y < 3; y++) { + // Skip the block actually placed by the item (Bottom Front Center) + if (x == 1 && z == 0 && y == 0) continue; + + BlockPos targetPos = calculateOffset(pos, facing, x, z, y); + CoopPart part = CoopPart.getPartFromCoords(x, z, y); + + level.setBlock(targetPos, state.setValue(PART, part), 3); + } + } + } + } + } + + /** + * Ensures a coop part is removed when an adjacent block that should belong to the same coop is missing. + * + * @param direction the direction from this block to the neighbouring block that changed + * @param neighborState the new state of the neighbouring block + * @return the AIR block state if the neighbour was expected to be part of this coop and is no longer present; otherwise the result of the superclass implementation + */ + @Override + protected BlockState updateShape( + BlockState state, + LevelReader levelReader, + ScheduledTickAccess scheduledTickAccess, + BlockPos pos, + Direction direction, + BlockPos neighborPos, + BlockState neighborState, + RandomSource randomSource + ) { + // If a neighbor block that is supposed to be part of this coop is now AIR, + // we return AIR to destroy this part of the coop as well. + if (!neighborState.is(this) && isNeighborPartOfCoop(state, direction)) { + return Blocks.AIR.defaultBlockState(); + } + + return super.updateShape(state, levelReader, scheduledTickAccess, pos, direction, neighborPos, neighborState, randomSource); + } + + /** + * Determine whether the neighbouring block in the given direction belongs to the same 3x3x2 coop structure for this part. + * + * @param state the current block state of this coop part (must contain `PART` and `FACING`) + * @param dir the direction from this block to the neighbouring block + * @return `true` if the neighbour in the given direction corresponds to a valid coop part within the 3x3x2 grid for this block, `false` otherwise + */ + private boolean isNeighborPartOfCoop(BlockState state, Direction dir) { + CoopPart part = state.getValue(PART); + Direction facing = state.getValue(HorizontalDirectionalBlock.FACING); + + // 1. Get the local offset of the neighbor block relative to this part + // We convert the world Direction into a local x, y, z change + int dx = dir.getStepX(); + int dy = dir.getStepY(); + int dz = dir.getStepZ(); + + // 2. Adjust for rotation (Facing) + // This ensures that "Front" always matches your Enum's Z-axis logic + // Note: This math varies slightly depending on how your placement logic + // maps "Front" to the world. Below is a standard mapping: + int localDx, localDz; + switch (facing) { + case NORTH -> { localDx = dx; localDz = dz; } + case SOUTH -> { localDx = -dx; localDz = -dz; } + case WEST -> { localDx = dz; localDz = -dx; } + case EAST -> { localDx = -dz; localDz = dx; } + default -> { localDx = dx; localDz = dz; } + } + + // 3. Calculate the neighbor's hypothetical grid position + int neighborX = part.getXOffset() + localDx; + int neighborZ = part.getZOffset() + localDz; + int neighborY = part.getYOffset() + dy; + + // 4. Check if these coordinates are within the 3x2x3 bounds + // Width: 0-2 (X), Depth: 0-1 (Z), Height: 0-2 (Y) + return neighborX >= 0 && neighborX < 3 && + neighborZ >= 0 && neighborZ < 2 && + neighborY >= 0 && neighborY < 3; + } + + /** + * Removes the whole 3x3x2 chicken coop structure on the server when any coop part is destroyed by a player. + * + * For each part in the coop grid this method: + * - drops the part's resources if the player is not in Creative mode, + * - replaces the part with AIR, + * - triggers the break visual/sound effect. + * + * This operation is performed only on the server side; client-side destruction is not handled here. + * + * @return the block state that will remain at the destroyed position after processing + */ + @Override + public BlockState playerWillDestroy(Level level, BlockPos pos, BlockState state, Player player) { + if (!level.isClientSide()) { + Direction facing = state.getValue(FACING); + CoopPart currentPart = state.getValue(PART); + + // Calculate origin based on the piece being broken + BlockPos origin = pos.subtract(calculateOffset(BlockPos.ZERO, facing, + currentPart.getXOffset(), currentPart.getZOffset(), currentPart.getYOffset())); + + // Use a flag to prevent re-entry if isNeighborPartOfCoop triggers + for (int x = 0; x < 3; x++) { + for (int z = 0; z < 2; z++) { + for (int y = 0; y < 3; y++) { + BlockPos targetPos = calculateOffset(origin, facing, x, z, y); + BlockState targetState = level.getBlockState(targetPos); + + if (targetState.is(this)) { + // 1. Handle Drops: This checks the loot table (JSON) and drops items + if (!player.isCreative()) { + BlockEntity blockEntity = targetState.hasBlockEntity() ? level.getBlockEntity(targetPos) : null; + Block.dropResources(targetState, level, targetPos, blockEntity, player, player.getMainHandItem()); + } + + // 2. Set to AIR with flag 3 (Update neighbors + Send to clients) + // Using destroyBlock with 'false' for drops since we handled it above + // for better control, or just setBlock to AIR. + level.setBlock(targetPos, Blocks.AIR.defaultBlockState(), 3); + + // 3. Play break effects + level.levelEvent(2001, targetPos, Block.getId(targetState)); + } + } + } + } + } + + return super.playerWillDestroy(level, pos, state, player); + } + + /** + * Map a local 3x3x2 grid coordinate to a world position based on an origin and horizontal facing. + * + *

The local grid uses x ∈ {0,1,2} for left→right, z ∈ {0,1} for near→far (away from the player), + * and y ≥ 0 for height above the origin.

+ * + * @param origin the base world position corresponding to local grid coordinate (1,0,0) + * @param facing the horizontal facing that orients the grid's depth (z) direction + * @param x local x index: 0 = left, 1 = centre, 2 = right + * @param z local z index: 0 = front/near, 1 = back/far (further from the player) + * @param y local y index: vertical offset above the origin + * @return the world BlockPos corresponding to the given local grid coordinate + */ + private BlockPos calculateOffset(BlockPos origin, Direction facing, int x, int z, int y) { + // x-1 centers the 3-wide structure (0=left, 1=center, 2=right) + int xAdjusted = x - 1; + + // We use the 'facing' direction for depth (z). + // This ensures z=1 is always "further away" from the player. + return origin.relative(facing, z) + .relative(facing.getClockWise(), xAdjusted) + .above(y); + } + + /** + * Provide the MapCodec used by this block for serialising its state. + * + * @return the MapCodec for this block type + */ + @Override + protected MapCodec codec() { + return CODEC; + } +} \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/util/CoopPart.java b/src/main/java/com/tcm/MineTale/util/CoopPart.java new file mode 100644 index 0000000..d733a8c --- /dev/null +++ b/src/main/java/com/tcm/MineTale/util/CoopPart.java @@ -0,0 +1,104 @@ +package com.tcm.MineTale.util; + +import net.minecraft.util.StringRepresentable; +import org.jetbrains.annotations.NotNull; + +public enum CoopPart implements StringRepresentable { + // 18 Parts: 3 Wide (x) x 2 Deep (z) x 3 High (y) + + // --- BOTTOM LAYER (y=0) --- + BOTTOM_FRONT_LEFT("bottom_front_left", 0, 0, 0), + BOTTOM_FRONT_CENTER("bottom_front_center", 1, 0, 0), + BOTTOM_FRONT_RIGHT("bottom_front_right", 2, 0, 0), + BOTTOM_BACK_LEFT("bottom_back_left", 0, 1, 0), + BOTTOM_BACK_CENTER("bottom_back_center", 1, 1, 0), + BOTTOM_BACK_RIGHT("bottom_back_right", 2, 1, 0), + + // --- MIDDLE LAYER (y=1) --- + MIDDLE_FRONT_LEFT("middle_front_left", 0, 0, 1), + MIDDLE_FRONT_CENTER("middle_front_center", 1, 0, 1), + MIDDLE_FRONT_RIGHT("middle_front_right", 2, 0, 1), + MIDDLE_BACK_LEFT("middle_back_left", 0, 1, 1), + MIDDLE_BACK_CENTER("middle_back_center", 1, 1, 1), + MIDDLE_BACK_RIGHT("middle_back_right", 2, 1, 1), + + // --- TOP LAYER (y=2) --- + TOP_FRONT_LEFT("top_front_left", 0, 0, 2), + TOP_FRONT_CENTER("top_front_center", 1, 0, 2), + TOP_FRONT_RIGHT("top_front_right", 2, 0, 2), + TOP_BACK_LEFT("top_back_left", 0, 1, 2), + TOP_BACK_CENTER("top_back_center", 1, 1, 2), + TOP_BACK_RIGHT("top_back_right", 2, 1, 2); + + private final String name; + private final int xOffset; + private final int zOffset; + private final int yOffset; + + /** + * Initialise the enum constant with its serialized name and 3D grid offsets. + * + * @param name the serialized name for this part + * @param x the x-axis offset (width; 0 = left) + * @param z the z-axis offset (depth; 0 = front) + * @param y the y-axis offset (height; 0 = bottom) + */ + CoopPart(String name, int x, int z, int y) { + this.name = name; + this.xOffset = x; + this.zOffset = z; + this.yOffset = y; + } + + /** + * Gets the serialized name for this enum constant. + * + * @return the serialized name associated with this part + */ + @Override + public @NotNull String getSerializedName() { + return this.name; + } + + /** + * The part's x-axis offset within the structure's width. + * + * @return the x-axis offset (0 to 2) indicating the part's horizontal position + */ +public int getXOffset() { return xOffset; } + /** + * The vertical offset within the three-layer coop grid. + * + * @return the y-axis offset (height layer) where 0 is bottom, 1 is middle and 2 is top + */ +public int getYOffset() { return yOffset; } + /** + * The depth offset of this coop part within the grid. + * + * @return the z-axis offset (0 = front, 1 = back) + */ +public int getZOffset() { return zOffset; } + + /** + * Retrieves the CoopPart corresponding to the supplied grid offsets. + * + * Offsets map to a 3×2×3 (width × depth × height) grid along the x, z and y axes. + * + * @param x the width offset (0–2) + * @param z the depth offset (0–1) + * @param y the height offset (0–2) + * @return the matching CoopPart + * @throws IllegalArgumentException if no part exists at the specified coordinates + */ + public static CoopPart getPartFromCoords(int x, int z, int y) { + for (CoopPart part : values()) { + if (part.xOffset == x && part.zOffset == z && part.yOffset == y) { + return part; + } + } + + throw new IllegalArgumentException( + String.format("No CoopPart found at coordinates: x=%d, z=%d, y=%d", x, z, y) + ); + } +} \ No newline at end of file