-
Notifications
You must be signed in to change notification settings - Fork 0
feat: chicken coop 3x3x2 is placeable in world #64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
205 changes: 205 additions & 0 deletions
205
src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,205 @@ | ||
| 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<CoopPart> PART = EnumProperty.create("part", CoopPart.class); | ||
|
|
||
| public static final MapCodec<ChickenCoopBlock> CODEC = simpleCodec(ChickenCoopBlock::new); | ||
|
|
||
| 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)); | ||
| } | ||
|
|
||
| @Override | ||
| protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) { | ||
| builder.add(FACING, PART); | ||
| } | ||
|
|
||
| @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); | ||
| } | ||
|
|
||
| @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); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @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); | ||
| } | ||
|
|
||
| /** | ||
| * Helper to check if the block in a specific direction is technically "connected" | ||
| * to this specific part of the 3x3x2 grid. | ||
| */ | ||
| 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; | ||
| } | ||
|
|
||
| @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); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| /** | ||
| * Rotates the 3x3x2 grid logic based on which way the player is facing. | ||
| */ | ||
| 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); | ||
| } | ||
|
|
||
| @Override | ||
| protected MapCodec<? extends HorizontalDirectionalBlock> codec() { | ||
| return CODEC; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| 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; | ||
|
|
||
| CoopPart(String name, int x, int z, int y) { | ||
| this.name = name; | ||
| this.xOffset = x; | ||
| this.zOffset = z; | ||
| this.yOffset = y; | ||
| } | ||
|
|
||
| @Override | ||
| public @NotNull String getSerializedName() { | ||
| return this.name; | ||
| } | ||
|
|
||
| public int getXOffset() { return xOffset; } | ||
| public int getYOffset() { return yOffset; } | ||
| public int getZOffset() { return zOffset; } | ||
|
|
||
| /** | ||
| * Retrieves the CoopPart corresponding to the given grid offsets. | ||
| * The grid is structured as 3x2x3 (width x depth x height), | ||
| * corresponding to the x, z, and y axes respectively. | ||
| * | ||
| * @param x The width offset | ||
| * @param z The depth offset | ||
| * @param y The height offset | ||
| * @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) | ||
| ); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.