From e7232b28632e8abc2aeed10e6d17e2f57ea5a62a Mon Sep 17 00:00:00 2001 From: Andy Wilson Date: Thu, 5 Mar 2026 09:32:48 +0000 Subject: [PATCH 1/7] feat: add roost logic --- .../java/com/tcm/MineTale/MineTaleClient.java | 1 - .../screen/FurnitureWorkbenchScreen.java | 1 - .../MineTale/datagen/ModBlockTagProvider.java | 3 - .../MineTale/datagen/ModItemTagProvider.java | 1 - .../MineTale/datagen/ModModelProvider.java | 1 - .../datagen/recipes/AlchemistRecipes.java | 5 - .../datagen/recipes/FarmerRecipes.java | 1 - .../tcm/MineTale/block/ChickenCoopBlock.java | 32 +- .../block/entity/ChickenCoopEntity.java | 334 ++++++++++++++++++ .../block/workbenches/FurnitureWorkbench.java | 1 - .../MineTale/registry/ModBlockEntities.java | 7 + .../com/tcm/MineTale/registry/ModBlocks.java | 4 +- 12 files changed, 374 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java diff --git a/src/client/java/com/tcm/MineTale/MineTaleClient.java b/src/client/java/com/tcm/MineTale/MineTaleClient.java index 1af871c..e08ef33 100644 --- a/src/client/java/com/tcm/MineTale/MineTaleClient.java +++ b/src/client/java/com/tcm/MineTale/MineTaleClient.java @@ -1,6 +1,5 @@ package com.tcm.MineTale; -import com.tcm.MineTale.block.workbenches.menu.BlacksmithsWorkbenchMenu; import com.tcm.MineTale.block.workbenches.screen.*; import com.tcm.MineTale.network.ClientboundNearbyInventorySyncPacket; diff --git a/src/client/java/com/tcm/MineTale/block/workbenches/screen/FurnitureWorkbenchScreen.java b/src/client/java/com/tcm/MineTale/block/workbenches/screen/FurnitureWorkbenchScreen.java index ff78f8b..e17aa79 100644 --- a/src/client/java/com/tcm/MineTale/block/workbenches/screen/FurnitureWorkbenchScreen.java +++ b/src/client/java/com/tcm/MineTale/block/workbenches/screen/FurnitureWorkbenchScreen.java @@ -2,7 +2,6 @@ import com.tcm.MineTale.MineTale; import com.tcm.MineTale.block.workbenches.menu.AbstractWorkbenchContainerMenu; -import com.tcm.MineTale.block.workbenches.menu.BlacksmithsWorkbenchMenu; import com.tcm.MineTale.block.workbenches.menu.FurnitureWorkbenchMenu; import com.tcm.MineTale.mixin.client.ClientRecipeBookAccessor; import com.tcm.MineTale.network.CraftRequestPayload; diff --git a/src/client/java/com/tcm/MineTale/datagen/ModBlockTagProvider.java b/src/client/java/com/tcm/MineTale/datagen/ModBlockTagProvider.java index c7d7a15..bdf0b90 100644 --- a/src/client/java/com/tcm/MineTale/datagen/ModBlockTagProvider.java +++ b/src/client/java/com/tcm/MineTale/datagen/ModBlockTagProvider.java @@ -1,13 +1,10 @@ package com.tcm.MineTale.datagen; import com.tcm.MineTale.registry.ModBlocks; -import com.tcm.MineTale.util.ModTags; import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider; import net.minecraft.core.HolderLookup; import net.minecraft.tags.BlockTags; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; import java.util.concurrent.CompletableFuture; diff --git a/src/client/java/com/tcm/MineTale/datagen/ModItemTagProvider.java b/src/client/java/com/tcm/MineTale/datagen/ModItemTagProvider.java index 3f853e1..9f89483 100644 --- a/src/client/java/com/tcm/MineTale/datagen/ModItemTagProvider.java +++ b/src/client/java/com/tcm/MineTale/datagen/ModItemTagProvider.java @@ -7,7 +7,6 @@ import net.minecraft.core.HolderLookup; import net.minecraft.world.item.Item; import net.minecraft.world.item.Items; -import net.minecraft.world.level.block.Blocks; import java.util.concurrent.CompletableFuture; diff --git a/src/client/java/com/tcm/MineTale/datagen/ModModelProvider.java b/src/client/java/com/tcm/MineTale/datagen/ModModelProvider.java index 867007b..b47a719 100644 --- a/src/client/java/com/tcm/MineTale/datagen/ModModelProvider.java +++ b/src/client/java/com/tcm/MineTale/datagen/ModModelProvider.java @@ -14,7 +14,6 @@ import net.minecraft.client.renderer.block.model.VariantMutator; import net.minecraft.core.Direction; import net.minecraft.resources.Identifier; -import net.minecraft.world.item.Items; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; diff --git a/src/client/java/com/tcm/MineTale/datagen/recipes/AlchemistRecipes.java b/src/client/java/com/tcm/MineTale/datagen/recipes/AlchemistRecipes.java index a60b868..821a1a8 100644 --- a/src/client/java/com/tcm/MineTale/datagen/recipes/AlchemistRecipes.java +++ b/src/client/java/com/tcm/MineTale/datagen/recipes/AlchemistRecipes.java @@ -1,10 +1,5 @@ package com.tcm.MineTale.datagen.recipes; -import com.tcm.MineTale.datagen.builders.WorkbenchRecipeBuilder; -import com.tcm.MineTale.registry.ModBlocks; -import com.tcm.MineTale.registry.ModItems; -import com.tcm.MineTale.registry.ModRecipeDisplay; -import com.tcm.MineTale.registry.ModRecipes; import net.minecraft.core.HolderLookup; import net.minecraft.data.recipes.RecipeOutput; import net.minecraft.data.recipes.RecipeProvider; diff --git a/src/client/java/com/tcm/MineTale/datagen/recipes/FarmerRecipes.java b/src/client/java/com/tcm/MineTale/datagen/recipes/FarmerRecipes.java index 2d1733a..609528d 100644 --- a/src/client/java/com/tcm/MineTale/datagen/recipes/FarmerRecipes.java +++ b/src/client/java/com/tcm/MineTale/datagen/recipes/FarmerRecipes.java @@ -1,6 +1,5 @@ package com.tcm.MineTale.datagen.recipes; -import com.tcm.MineTale.datagen.ModItemTagProvider; import com.tcm.MineTale.datagen.builders.WorkbenchRecipeBuilder; import com.tcm.MineTale.registry.ModBlocks; import com.tcm.MineTale.registry.ModItems; diff --git a/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java b/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java index 14e0919..7059a39 100644 --- a/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java +++ b/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java @@ -1,6 +1,8 @@ package com.tcm.MineTale.block; import com.mojang.serialization.MapCodec; +import com.tcm.MineTale.block.entity.ChickenCoopEntity; +import com.tcm.MineTale.registry.ModBlockEntities; import com.tcm.MineTale.util.CoopPart; import net.minecraft.core.BlockPos; @@ -15,14 +17,17 @@ 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.EntityBlock; import net.minecraft.world.level.block.HorizontalDirectionalBlock; import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; 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 class ChickenCoopBlock extends HorizontalDirectionalBlock implements EntityBlock { public static final EnumProperty PART = EnumProperty.create("part", CoopPart.class); public static final MapCodec CODEC = simpleCodec(ChickenCoopBlock::new); @@ -35,6 +40,31 @@ public ChickenCoopBlock(Properties properties) { .setValue(PART, CoopPart.BOTTOM_FRONT_LEFT)); } + @Nullable + @Override + public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { + // 1. Only tick on server side + if (level.isClientSide()) return null; + + // 2. Only tick if this is the correct part of the coop + if (state.getValue(PART) != CoopPart.BOTTOM_FRONT_CENTER) return null; + + // 3. Link to the static tick method in your Entity class + return type == ModBlockEntities.CHICKEN_COOP_BE + ? (lvl, pos, st, be) -> ChickenCoopEntity.tick(lvl, pos, st, (ChickenCoopEntity) be) + : null; + } + + @Nullable + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + // Only the center-front part gets the "brain" + if (state.getValue(PART) == CoopPart.BOTTOM_FRONT_CENTER) { + return new ChickenCoopEntity(pos, state); + } + return null; + } + @Override protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(FACING, PART); diff --git a/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java b/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java new file mode 100644 index 0000000..89dbb8b --- /dev/null +++ b/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java @@ -0,0 +1,334 @@ +package com.tcm.MineTale.block.entity; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.animal.chicken.Chicken; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; +import net.minecraft.world.level.storage.ValueOutput.TypedOutputList; +import net.minecraft.world.level.storage.ValueOutput.ValueOutputList; +import net.minecraft.world.phys.AABB; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import org.jspecify.annotations.Nullable; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.RecordBuilder; +import com.tcm.MineTale.registry.ModBlockEntities; + +public class ChickenCoopEntity extends BlockEntity { + private final List storedChickensNbt = new ArrayList<>(); + private boolean isNightMode = false; + + public ChickenCoopEntity(BlockPos pos, BlockState state) { + super(ModBlockEntities.CHICKEN_COOP_BE, pos, state); + } + + public static void tick(Level level, BlockPos pos, BlockState state, ChickenCoopEntity be) { + if (level.isClientSide()) return; + + long time = level.getDayTime() % 24000; + boolean isLate = time >= 13000 && time < 23000; // Monster spawn window + + if (isLate && !be.isNightMode) { + System.out.println("Chicken Coop: Attempting to collect chickens!"); + be.collectChickens((ServerLevel) level, pos); + be.isNightMode = true; + be.setChanged(); + } else if (!isLate && be.isNightMode) { + be.releaseChickens((ServerLevel) level, pos); + be.isNightMode = false; + be.setChanged(); + } + } + + private void collectChickens(ServerLevel level, BlockPos pos) { + AABB area = new AABB(pos).inflate(8); + List nearbyChickens = level.getEntitiesOfClass(Chicken.class, area); + + for (int i = 0; i < Math.min(nearbyChickens.size(), 6); i++) { + Chicken chicken = nearbyChickens.get(i); + CompoundTag chickenData = new CompoundTag(); + + // Wrap our CompoundTag in the ValueOutput implementation + ValueOutput output = new ChickenValueOutput(chickenData, level.registryAccess()); + + // Satisfies the method signature perfectly! + chicken.saveWithoutId(output); + + // Store the result + storedChickensNbt.add(chickenData); + chicken.discard(); + } + } + + private void releaseChickens(ServerLevel level, BlockPos pos) { + for (CompoundTag chickenData : storedChickensNbt) { + Chicken chicken = new Chicken(EntityType.CHICKEN, level); + + ChickenValueInput inputBridge = new ChickenValueInput(chickenData, level.registryAccess()); + chicken.load(inputBridge); + + // 1. Find a safe spot on nearby grass + BlockPos spawnPos = findSafeGrassSpawn(level, pos, 3); // Searches a 3-block radius + + // 2. Set the position based on the found spot (centered) + // We add 0.5 to X/Z to center them in the block, and the Y stays at the top of the grass + double x = spawnPos.getX() + 0.5; + double y = spawnPos.getY() + 1.0; + double z = spawnPos.getZ() + 0.5; + + // 3. Apply the position with a random rotation + chicken.absSnapTo( + x, + y, + z, + level.random.nextFloat() * 360.0F, + 0.0F + ); + + level.addFreshEntity(chicken); + } + + storedChickensNbt.clear(); + this.setChanged(); + } + + /** + * Scans the area around the coop for grass blocks with air above them. + */ + private BlockPos findSafeGrassSpawn(ServerLevel level, BlockPos origin, int radius) { + java.util.List grassSpots = new java.util.ArrayList<>(); + + // Scan a cube around the coop: + // radius horizontally, and -1 to +2 vertically to catch slight hills + for (BlockPos p : BlockPos.betweenClosed(origin.offset(-radius, -1, -radius), origin.offset(radius, 2, radius))) { + // Is the block grass? + if (level.getBlockState(p).is(net.minecraft.world.level.block.Blocks.GRASS_BLOCK)) { + // Is there air above it so the chicken doesn't suffocate? + if (level.isEmptyBlock(p.above()) && level.isEmptyBlock(p.above(2))) { + grassSpots.add(p.immutable()); + } + } + } + + // If we found grass, pick a random one + if (!grassSpots.isEmpty()) { + return grassSpots.get(level.random.nextInt(grassSpots.size())); + } + + // Fallback: If no grass is found (e.g., coop is on wood), + // spawn them 1 block above the coop center to avoid being stuck in the "brain" block. + return origin.above(); + } + + private static class ChickenValueOutput implements ValueOutput { + private final CompoundTag tag; + private final HolderLookup.Provider registries; // Added this + + public ChickenValueOutput(CompoundTag tag, HolderLookup.Provider registries) { + this.tag = tag; + this.registries = registries; // Added this + } + + @Override public void putBoolean(String key, boolean value) { tag.putBoolean(key, value); } + @Override public void putByte(String key, byte value) { tag.putByte(key, value); } + @Override public void putShort(String key, short value) { tag.putShort(key, value); } + @Override public void putInt(String key, int value) { tag.putInt(key, value); } + @Override public void putLong(String key, long value) { tag.putLong(key, value); } + @Override public void putFloat(String key, float value) { tag.putFloat(key, value); } + @Override public void putDouble(String key, double value) { tag.putDouble(key, value); } + @Override public void putString(String key, String value) { tag.putString(key, value); } + @Override public void putIntArray(String key, int[] value) { tag.putIntArray(key, value); } + + @Override + public void store(String key, Codec codec, T value) { + // Use the codec to turn the object into NBT + codec.encodeStart(NbtOps.INSTANCE, value).resultOrPartial(System.err::println) + .ifPresent(nbt -> tag.put(key, nbt)); + } + + // Boilerplate for lists and children (can be implemented if needed) + @Override public ValueOutput child(String key) { + CompoundTag childTag = new CompoundTag(); + tag.put(key, childTag); + return new ChickenValueOutput(childTag, registries); + } + + // Implement other required methods with empty/default logic or full NBT support + @Override public boolean isEmpty() { return tag.isEmpty(); } + @Override public void discard(String string) { tag.remove(string); } + + @Override + public void storeNullable(String key, Codec codec, @Nullable T object) { + if (object == null) return; // Don't write anything if it's null + + // Encode the object into an NBT element + codec.encodeStart(registries.createSerializationContext(NbtOps.INSTANCE), object) + .resultOrPartial(err -> { /* Log error if needed */ }) + .ifPresent(nbt -> tag.put(key, nbt)); + } + + @Override + public void store(MapCodec mapCodec, T object) { + // 1. Create the context that knows about Minecraft registries + DynamicOps ops = registries.createSerializationContext(NbtOps.INSTANCE); + + // 2. MapCodec.encode requires a RecordBuilder. + // ops.mapBuilder() provides a fresh one that works with NBT. + RecordBuilder builder = ops.mapBuilder(); + + // 3. Tell the codec to fill the builder with the object's data + mapCodec.encode(object, ops, builder) + // 4. Build the result directly into our existing 'tag' (CompoundTag) + .build(this.tag) + .resultOrPartial(err -> { + // Optional: Log if something went wrong during encoding + System.err.println("Failed to encode map codec: " + err); + }); + } + + @Override + public ValueOutputList childrenList(String key) { + ListTag listTag = new ListTag(); + tag.put(key, listTag); + // You will need a helper class that writes to this ListTag + return new ChickenValueOutputList(listTag, registries); + } + + @Override + public TypedOutputList list(String key, Codec codec) { + ListTag listTag = new ListTag(); + tag.put(key, listTag); + // You will need a helper class that writes typed objects to this ListTag + return new ChickenTypedOutputList<>(listTag, codec, registries); + } + } + + private record ChickenValueOutputList(ListTag list, HolderLookup.Provider registries) implements ValueOutputList { + @Override + public ValueOutput addChild() { + CompoundTag nextTag = new CompoundTag(); + list.add(nextTag); + // Return a new output worker for the tag we just added to the list + return new ChickenValueOutput(nextTag, registries); + } + + @Override + public void discardLast() { + if (!list.isEmpty()) { + list.remove(list.size() - 1); + } + } + + @Override + public boolean isEmpty() { + return list.isEmpty(); + } + } + + private record ChickenTypedOutputList(ListTag list, Codec codec, HolderLookup.Provider registries) implements TypedOutputList { + @Override + public void add(T value) { + // serialize the object into NBT and add it to the list if successful + codec.encodeStart(registries.createSerializationContext(NbtOps.INSTANCE), value) + .resultOrPartial(System.err::println) + .ifPresent(list::add); + } + + @Override + public boolean isEmpty() { + return list.isEmpty(); + } + } + + private static class ChickenValueInput implements ValueInput { + private final CompoundTag tag; + private final HolderLookup.Provider registries; + + public ChickenValueInput(CompoundTag tag, HolderLookup.Provider registries) { + this.tag = tag; + this.registries = registries; + } + + @Override + public Optional read(String key, Codec codec) { + if (!tag.contains(key)) return Optional.empty(); + return codec.parse(NbtOps.INSTANCE, tag.get(key)).result(); + } + + @Override + public Optional read(MapCodec mapCodec) { + // 1. Convert the MapCodec into a standard Codec + // 2. Use the codec to parse the NBT tag + // 3. We use 'registryAccess' (registries) to handle things like Item types or Biomes + return mapCodec.codec() + .parse(registries.createSerializationContext(NbtOps.INSTANCE), tag) + .result(); // This returns an Optional automatically + } + + @Override + public Optional child(String key) { + if (!tag.contains(key)) return Optional.empty(); + ValueInput input = new ChickenValueInput(tag.getCompound(key).get(), registries); + return Optional.of(input); + } + + @Override + public ValueInput childOrEmpty(String key) { + return child(key).orElse(new ChickenValueInput(new CompoundTag(), registries)); + } + + // Primitive Getters with Fallbacks + @Override public boolean getBooleanOr(String key, boolean fallback) { return tag.contains(key) ? tag.getBoolean(key).get() : fallback; } + @Override public byte getByteOr(String key, byte fallback) { return tag.contains(key) ? tag.getByte(key).get() : fallback; } + @Override public int getShortOr(String key, short fallback) { return tag.contains(key) ? tag.getShort(key).get() : fallback; } + @Override public int getIntOr(String key, int fallback) { return tag.contains(key) ? tag.getInt(key).get() : fallback; } + @Override public long getLongOr(String key, long fallback) { return tag.contains(key) ? tag.getLong(key).get() : fallback; } + @Override public float getFloatOr(String key, float fallback) { return tag.contains(key) ? tag.getFloat(key).get() : fallback; } + @Override public double getDoubleOr(String key, double fallback) { return tag.contains(key) ? tag.getDouble(key).get() : fallback; } + @Override public String getStringOr(String key, String fallback) { return tag.contains(key) ? tag.getString(key).get() : fallback; } + + // Optional Getters + @Override public Optional getInt(String key) { return tag.contains(key) ? Optional.of(tag.getInt(key).get()) : Optional.empty(); } + @Override public Optional getLong(String key) { return tag.contains(key) ? Optional.of(tag.getLong(key).get()) : Optional.empty(); } + @Override public Optional getString(String key) { return tag.contains(key) ? Optional.of(tag.getString(key).get()) : Optional.empty(); } + @Override public Optional getIntArray(String key) { return tag.contains(key) ? Optional.of(tag.getIntArray(key).get()) : Optional.empty(); } + + @Override public HolderLookup.Provider lookup() { return registries; } + + // Minimal implementation for Lists (can be expanded if chickens store list data) + @Override public Optional> list(String key, Codec codec) { return Optional.empty(); } + @Override public TypedInputList listOrEmpty(String key, Codec codec) { + return new TypedInputList() { + @Override public boolean isEmpty() { return true; } + @Override public Stream stream() { return Stream.empty(); } + @Override public java.util.Iterator iterator() { return Stream.empty().iterator(); } + }; + } + + @Override public Optional childrenList(String string) { return Optional.empty(); } + @Override public ValueInputList childrenListOrEmpty(String string) { + return new ValueInputList() { + @Override public boolean isEmpty() { return true; } + @Override public Stream stream() { return Stream.empty(); } + @Override public java.util.Iterator iterator() { return Stream.empty().iterator(); } + }; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/FurnitureWorkbench.java b/src/main/java/com/tcm/MineTale/block/workbenches/FurnitureWorkbench.java index a770038..8a18565 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/FurnitureWorkbench.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/FurnitureWorkbench.java @@ -2,7 +2,6 @@ import com.mojang.serialization.MapCodec; import com.tcm.MineTale.block.workbenches.entity.AbstractWorkbenchEntity; -import com.tcm.MineTale.block.workbenches.entity.BlacksmithsWorkbenchEntity; import com.tcm.MineTale.block.workbenches.entity.FurnitureWorkbenchEntity; import com.tcm.MineTale.registry.ModBlockEntities; import net.minecraft.core.BlockPos; diff --git a/src/main/java/com/tcm/MineTale/registry/ModBlockEntities.java b/src/main/java/com/tcm/MineTale/registry/ModBlockEntities.java index 736a04b..f5fb94d 100644 --- a/src/main/java/com/tcm/MineTale/registry/ModBlockEntities.java +++ b/src/main/java/com/tcm/MineTale/registry/ModBlockEntities.java @@ -1,6 +1,7 @@ package com.tcm.MineTale.registry; import com.tcm.MineTale.MineTale; +import com.tcm.MineTale.block.entity.ChickenCoopEntity; import com.tcm.MineTale.block.workbenches.entity.*; import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; @@ -54,6 +55,12 @@ public class ModBlockEntities { ModBlocks.FURNITURE_WORKBENCH_BLOCK ); + public static final BlockEntityType CHICKEN_COOP_BE = register( + "chicken_coop_be", + ChickenCoopEntity::new, + ModBlocks.CHICKEN_COOP_BLOCK + ); + /** * Register a BlockEntityType for the given furnace tier and store it in the tier map. * diff --git a/src/main/java/com/tcm/MineTale/registry/ModBlocks.java b/src/main/java/com/tcm/MineTale/registry/ModBlocks.java index 498884c..ff737cc 100644 --- a/src/main/java/com/tcm/MineTale/registry/ModBlocks.java +++ b/src/main/java/com/tcm/MineTale/registry/ModBlocks.java @@ -364,8 +364,8 @@ public class ModBlocks { // 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); // 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); - public static final Block CHICKEN_COOP = register( - "chicken_coop", + public static final Block CHICKEN_COOP_BLOCK = register( + "chicken_coop_block", ChickenCoopBlock::new, BlockBehaviour.Properties.of() .mapColor(MapColor.WOOD) From 8998e6d99681d3b9ce46f9b7517696aead9f83fc Mon Sep 17 00:00:00 2001 From: Andy Wilson Date: Thu, 5 Mar 2026 21:53:51 +0000 Subject: [PATCH 2/7] Implement egg count and laying mechanics in ChickenCoopEntity Added egg count functionality to ChickenCoopEntity, allowing it to store and manage eggs laid by chickens during night mode. --- .../block/entity/ChickenCoopEntity.java | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java b/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java index 89dbb8b..0ec3e6f 100644 --- a/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java +++ b/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java @@ -34,6 +34,8 @@ public class ChickenCoopEntity extends BlockEntity { private final List storedChickensNbt = new ArrayList<>(); private boolean isNightMode = false; + private int eggCount = 0; + private static final int MAX_EGGS = 16; // Limit storage so it's not infinite public ChickenCoopEntity(BlockPos pos, BlockState state) { super(ModBlockEntities.CHICKEN_COOP_BE, pos, state); @@ -55,6 +57,28 @@ public static void tick(Level level, BlockPos pos, BlockState state, ChickenCoop be.isNightMode = false; be.setChanged(); } + + // If it's night and we have chickens inside, try to lay eggs + if (be.isNightMode && !be.storedChickensNbt.isEmpty() && be.eggCount < MAX_EGGS) { + // Minecraft chickens lay eggs every 6000-12000 ticks. + // With up to 6 chickens, a 1 in 1000 chance per tick is roughly realistic. + if (level.random.nextInt(1000) < be.storedChickensNbt.size()) { + be.eggCount++; + be.setChanged(); + // Optional: Play a muffled chicken sound from inside the coop + level.playSound(null, pos, SoundEvents.CHICKEN_EGG, SoundSource.BLOCKS, 0.5f, 1.0f); + } + } + } + + // Helper for the player to interact + public int takeEgg() { + if (eggCount > 0) { + eggCount--; + setChanged(); + return 1; + } + return 0; } private void collectChickens(ServerLevel level, BlockPos pos) { @@ -331,4 +355,4 @@ public ValueInput childOrEmpty(String key) { }; } } -} \ No newline at end of file +} From 10b601249d8ef8359e3a38020e632f6203f91ab4 Mon Sep 17 00:00:00 2001 From: Andy Wilson Date: Thu, 5 Mar 2026 21:55:39 +0000 Subject: [PATCH 3/7] Add egg collection interaction to Chicken Coop block Implement interaction for collecting eggs from the Chicken Coop block. --- .../tcm/MineTale/block/ChickenCoopBlock.java | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java b/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java index 7059a39..4efe0cf 100644 --- a/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java +++ b/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java @@ -228,8 +228,39 @@ private BlockPos calculateOffset(BlockPos origin, Direction facing, int x, int z .above(y); } - @Override + @Override protected MapCodec codec() { return CODEC; } -} \ No newline at end of file + + @Override + protected ItemInteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) { + if (level.isClientSide()) return ItemInteractionResult.SUCCESS; + + Direction facing = state.getValue(FACING); + CoopPart part = state.getValue(PART); + + // 1. Find the Brain (Bottom Front Center) + // We reverse the offset from the clicked part to find the origin (0,0,0) + // Then we add the specific offset for the Bottom Front Center (1,0,0) + BlockPos origin = pos.subtract(calculateOffset(BlockPos.ZERO, facing, part.getXOffset(), part.getZOffset(), part.getYOffset())); + BlockPos brainPos = calculateOffset(origin, facing, 1, 0, 0); + + if (level.getBlockEntity(brainPos) instanceof ChickenCoopBlockEntity be) { + if (be.takeEgg() > 0) { + // Give player the egg + ItemStack eggStack = new ItemStack(Items.EGG); + if (!player.getInventory().add(eggStack)) { + player.drop(eggStack, false); + } + + // Visual/Sound feedback + level.playSound(null, pos, SoundEvents.ITEM_PICKUP, SoundSource.PLAYERS, 0.2f, (level.random.nextFloat() - level.random.nextFloat()) * 0.7f + 1.2f); + return ItemInteractionResult.CONSUME; + } + } + + return ItemInteractionResult.PASS; +} + +} From 524dc0b5e60f68bb5628ec1c7d9c69fe31033bb9 Mon Sep 17 00:00:00 2001 From: Andy Wilson Date: Fri, 6 Mar 2026 06:48:27 +0000 Subject: [PATCH 4/7] feat: eggs laid in coop at night are collectable --- .../datagen/recipes/AlchemistRecipes.java | 6 + .../tcm/MineTale/block/ChickenCoopBlock.java | 61 +++++--- .../block/entity/ChickenCoopEntity.java | 135 +++++++++++++++--- .../entity/AlchemistsWorkbenchEntity.java | 2 - .../com/tcm/MineTale/registry/ModItems.java | 1 - 5 files changed, 158 insertions(+), 47 deletions(-) diff --git a/src/client/java/com/tcm/MineTale/datagen/recipes/AlchemistRecipes.java b/src/client/java/com/tcm/MineTale/datagen/recipes/AlchemistRecipes.java index a909d44..99ec90a 100644 --- a/src/client/java/com/tcm/MineTale/datagen/recipes/AlchemistRecipes.java +++ b/src/client/java/com/tcm/MineTale/datagen/recipes/AlchemistRecipes.java @@ -1,5 +1,11 @@ package com.tcm.MineTale.datagen.recipes; +import com.tcm.MineTale.datagen.builders.WorkbenchRecipeBuilder; +import com.tcm.MineTale.registry.ModBlocks; +import com.tcm.MineTale.registry.ModItems; +import com.tcm.MineTale.registry.ModRecipeDisplay; +import com.tcm.MineTale.registry.ModRecipes; + import net.minecraft.core.HolderLookup; import net.minecraft.data.recipes.RecipeOutput; import net.minecraft.data.recipes.RecipeProvider; diff --git a/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java b/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java index 4efe0cf..7846f71 100644 --- a/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java +++ b/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java @@ -7,10 +7,14 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; import net.minecraft.util.RandomSource; +import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelReader; @@ -25,6 +29,8 @@ 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 net.minecraft.world.phys.BlockHitResult; + import org.jetbrains.annotations.Nullable; public class ChickenCoopBlock extends HorizontalDirectionalBlock implements EntityBlock { @@ -234,33 +240,42 @@ protected MapCodec codec() { } @Override - protected ItemInteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) { - if (level.isClientSide()) return ItemInteractionResult.SUCCESS; + protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) { + if (level.isClientSide()) return InteractionResult.SUCCESS; - Direction facing = state.getValue(FACING); - CoopPart part = state.getValue(PART); - - // 1. Find the Brain (Bottom Front Center) - // We reverse the offset from the clicked part to find the origin (0,0,0) - // Then we add the specific offset for the Bottom Front Center (1,0,0) - BlockPos origin = pos.subtract(calculateOffset(BlockPos.ZERO, facing, part.getXOffset(), part.getZOffset(), part.getYOffset())); - BlockPos brainPos = calculateOffset(origin, facing, 1, 0, 0); - - if (level.getBlockEntity(brainPos) instanceof ChickenCoopBlockEntity be) { - if (be.takeEgg() > 0) { - // Give player the egg - ItemStack eggStack = new ItemStack(Items.EGG); - if (!player.getInventory().add(eggStack)) { - player.drop(eggStack, false); - } + // 1. Find the "brain" position (the Bottom Front Center) + Direction facing = state.getValue(FACING); + CoopPart currentPart = state.getValue(PART); + + // Calculate the origin (Bottom Front Center is our origin in calculateOffset logic) + // Based on your calculateOffset, the BFC is at x=1, z=0, y=0. + BlockPos brainPos = pos.subtract(calculateOffset(BlockPos.ZERO, facing, + currentPart.getXOffset(), currentPart.getZOffset(), currentPart.getYOffset())) + .relative(facing, 0) // already at z=0 + .relative(facing.getClockWise(), 0); // x=1 is center, so we shift back to it + + // Easier way: Since you know the brain is always at BOTTOM_FRONT_CENTER: + // We just need to find where that specific part is relative to the current block. + // However, your 'calculateOffset' is already the source of truth. + + if (level.getBlockEntity(brainPos) instanceof ChickenCoopEntity coopBe) { + int eggsToGive = coopBe.takeAllEggs(); - // Visual/Sound feedback - level.playSound(null, pos, SoundEvents.ITEM_PICKUP, SoundSource.PLAYERS, 0.2f, (level.random.nextFloat() - level.random.nextFloat()) * 0.7f + 1.2f); - return ItemInteractionResult.CONSUME; + if (eggsToGive > 0) { + // Give the player an egg + ItemStack eggStack = new ItemStack(Items.EGG, eggsToGive); + if (!player.getInventory().add(eggStack)) { + // If inventory full, drop at player's feet + player.drop(eggStack, false); + } + + // Play a sound to give feedback + level.playSound(null, pos, SoundEvents.CHICKEN_EGG, SoundSource.PLAYERS, 1.0f, 1.0f); + return InteractionResult.SUCCESS; + } } - } - return ItemInteractionResult.PASS; + return InteractionResult.PASS; } } diff --git a/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java b/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java index 0ec3e6f..86e7432 100644 --- a/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java +++ b/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java @@ -2,13 +2,19 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.HolderLookup; +import net.minecraft.core.particles.ItemParticleOption; +import net.minecraft.core.particles.ParticleTypes; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.animal.chicken.Chicken; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; @@ -35,6 +41,7 @@ public class ChickenCoopEntity extends BlockEntity { private final List storedChickensNbt = new ArrayList<>(); private boolean isNightMode = false; private int eggCount = 0; + private int eggsLaidThisNight = 0; private static final int MAX_EGGS = 16; // Limit storage so it's not infinite public ChickenCoopEntity(BlockPos pos, BlockState state) { @@ -45,38 +52,98 @@ public static void tick(Level level, BlockPos pos, BlockState state, ChickenCoop if (level.isClientSide()) return; long time = level.getDayTime() % 24000; - boolean isLate = time >= 13000 && time < 23000; // Monster spawn window + boolean isLate = time >= 13000 && time < 23000; + // Transition to Night if (isLate && !be.isNightMode) { - System.out.println("Chicken Coop: Attempting to collect chickens!"); be.collectChickens((ServerLevel) level, pos); be.isNightMode = true; + be.eggsLaidThisNight = 0; // Reset the counter for the new night be.setChanged(); - } else if (!isLate && be.isNightMode) { + } + // Transition to Day + else if (!isLate && be.isNightMode) { be.releaseChickens((ServerLevel) level, pos); be.isNightMode = false; be.setChanged(); } - // If it's night and we have chickens inside, try to lay eggs - if (be.isNightMode && !be.storedChickensNbt.isEmpty() && be.eggCount < MAX_EGGS) { - // Minecraft chickens lay eggs every 6000-12000 ticks. - // With up to 6 chickens, a 1 in 1000 chance per tick is roughly realistic. - if (level.random.nextInt(1000) < be.storedChickensNbt.size()) { - be.eggCount++; - be.setChanged(); - // Optional: Play a muffled chicken sound from inside the coop - level.playSound(null, pos, SoundEvents.CHICKEN_EGG, SoundSource.BLOCKS, 0.5f, 1.0f); + // Egg Laying Logic + if (be.isNightMode && !be.storedChickensNbt.isEmpty()) { + int chickensInside = be.storedChickensNbt.size(); + + // Condition 1: Total coop storage isn't full (MAX_EGGS = 16) + // Condition 2: This specific night hasn't exceeded the chicken count + if (be.eggCount < MAX_EGGS && be.eggsLaidThisNight < chickensInside) { + + // 1 in 1000 chance per tick is balanced for a full night + if (level.random.nextInt(1000) == 0) { + be.eggCount++; + be.eggsLaidThisNight++; // This ensures this specific chicken is "done" for the night + be.setChanged(); + + level.playSound(null, pos, SoundEvents.CHICKEN_EGG, SoundSource.BLOCKS, 0.5f, 1.2f); + } } } } - // Helper for the player to interact - public int takeEgg() { - if (eggCount > 0) { - eggCount--; - setChanged(); - return 1; + private void spawnFeatherParticles(ServerLevel level, BlockPos pos, int count) { + // Define the particle type using the Feather item texture + ItemParticleOption particleData = + new ItemParticleOption( + ParticleTypes.ITEM, + new ItemStack(Items.FEATHER) + ); + + // Spawn the particles + // Parameters: particle, pos.x, pos.y, pos.z, count, speedX, speedY, speedZ, velocityScale + level.sendParticles(particleData, + pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, + count, // amount of feathers + 0.3, 0.3, 0.3, // spread (delta) + 0.15 // speed/velocity + ); + } + + @Override + protected void saveAdditional(ValueOutput valueOutput) { + super.saveAdditional(valueOutput); + + // Save the simple primitives + valueOutput.putInt("EggCount", this.eggCount); + valueOutput.putBoolean("IsNightMode", this.isNightMode); + + // Save the list of chickens using your TypedOutputList logic + // We use the CompoundTag.CODEC to store the raw NBT of each chicken + ValueOutput.TypedOutputList chickenList = valueOutput.list("StoredChickens", CompoundTag.CODEC); + for (CompoundTag chickenNbt : storedChickensNbt) { + chickenList.add(chickenNbt); + } + } + + @Override + protected void loadAdditional(ValueInput valueInput) { + super.loadAdditional(valueInput); + + this.eggCount = valueInput.getIntOr("EggCount", 0); + this.isNightMode = valueInput.getBooleanOr("IsNightMode", false); + + // Clear current chickens + this.storedChickensNbt.clear(); + + // Use map to transform the Optional into a Stream of NBT + valueInput.list("StoredChickens", CompoundTag.CODEC) + .map(ValueInput.TypedInputList::stream) + .ifPresent(stream -> stream.forEach(this.storedChickensNbt::add)); + } + + public int takeAllEggs() { + int total = this.eggCount; + if (total > 0) { + this.eggCount = 0; + this.setChanged(); + return total; } return 0; } @@ -91,10 +158,11 @@ private void collectChickens(ServerLevel level, BlockPos pos) { // Wrap our CompoundTag in the ValueOutput implementation ValueOutput output = new ChickenValueOutput(chickenData, level.registryAccess()); + + spawnFeatherParticles(level, chicken.blockPosition(), 15); // Satisfies the method signature perfectly! chicken.saveWithoutId(output); - // Store the result storedChickensNbt.add(chickenData); chicken.discard(); @@ -127,6 +195,8 @@ private void releaseChickens(ServerLevel level, BlockPos pos) { ); level.addFreshEntity(chicken); + + spawnFeatherParticles(level, chicken.blockPosition(), 15); } storedChickensNbt.clear(); @@ -336,8 +406,31 @@ public ValueInput childOrEmpty(String key) { @Override public HolderLookup.Provider lookup() { return registries; } - // Minimal implementation for Lists (can be expanded if chickens store list data) - @Override public Optional> list(String key, Codec codec) { return Optional.empty(); } + @Override + public Optional> list(String key, Codec codec) { + // Check if the key exists and is a List + if (!tag.contains(key)) return Optional.empty(); + + // In modern mappings, getList only takes the Key. + // It returns the list if found, or an empty one if not. + Optional listTag = tag.getList(key); + + // Map the NBT tags to objects using the codec + List items = listTag.stream() + .map(nbt -> codec.parse(registries.createSerializationContext(NbtOps.INSTANCE), nbt) + .resultOrPartial(System.err::println)) + .flatMap(Optional::stream) // Flattens Optional into the stream + .toList(); + + return Optional.of(new ChickenTypedInputList<>(items)); + } + + // Ensure the helper record implements stream() + private record ChickenTypedInputList(List items) implements TypedInputList { + @Override public boolean isEmpty() { return items.isEmpty(); } + @Override public Stream stream() { return items.stream(); } + @Override public java.util.Iterator iterator() { return items.iterator(); } + } @Override public TypedInputList listOrEmpty(String key, Codec codec) { return new TypedInputList() { @Override public boolean isEmpty() { return true; } diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/entity/AlchemistsWorkbenchEntity.java b/src/main/java/com/tcm/MineTale/block/workbenches/entity/AlchemistsWorkbenchEntity.java index 7dfa4fd..37b7e77 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/entity/AlchemistsWorkbenchEntity.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/entity/AlchemistsWorkbenchEntity.java @@ -5,7 +5,6 @@ import com.tcm.MineTale.recipe.WorkbenchRecipe; import com.tcm.MineTale.registry.ModBlockEntities; import com.tcm.MineTale.registry.ModRecipes; -import com.tcm.MineTale.util.Constants; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.player.Inventory; @@ -19,7 +18,6 @@ import net.minecraft.world.level.storage.ValueOutput; import org.jspecify.annotations.Nullable; -import java.util.ArrayList; import java.util.List; import java.util.stream.IntStream; diff --git a/src/main/java/com/tcm/MineTale/registry/ModItems.java b/src/main/java/com/tcm/MineTale/registry/ModItems.java index 4d2f95d..f75feab 100644 --- a/src/main/java/com/tcm/MineTale/registry/ModItems.java +++ b/src/main/java/com/tcm/MineTale/registry/ModItems.java @@ -9,7 +9,6 @@ import com.tcm.MineTale.item.ModArmorMaterials; import com.tcm.MineTale.item.ModCreativeTab; -import com.tcm.MineTale.util.ModTags; import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents; import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponents; From 342e6b1da208dc1388dabbd0825e6e2a1a840fba Mon Sep 17 00:00:00 2001 From: Andy Wilson Date: Fri, 6 Mar 2026 07:05:46 +0000 Subject: [PATCH 5/7] AI: fix comments --- .../block/entity/ChickenCoopEntity.java | 74 ++++++++++--------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java b/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java index 86e7432..ab24dfb 100644 --- a/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java +++ b/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java @@ -109,13 +109,10 @@ private void spawnFeatherParticles(ServerLevel level, BlockPos pos, int count) { @Override protected void saveAdditional(ValueOutput valueOutput) { super.saveAdditional(valueOutput); - - // Save the simple primitives valueOutput.putInt("EggCount", this.eggCount); + valueOutput.putInt("EggsLaidThisNight", this.eggsLaidThisNight); // Added valueOutput.putBoolean("IsNightMode", this.isNightMode); - // Save the list of chickens using your TypedOutputList logic - // We use the CompoundTag.CODEC to store the raw NBT of each chicken ValueOutput.TypedOutputList chickenList = valueOutput.list("StoredChickens", CompoundTag.CODEC); for (CompoundTag chickenNbt : storedChickensNbt) { chickenList.add(chickenNbt); @@ -125,16 +122,13 @@ protected void saveAdditional(ValueOutput valueOutput) { @Override protected void loadAdditional(ValueInput valueInput) { super.loadAdditional(valueInput); - this.eggCount = valueInput.getIntOr("EggCount", 0); + this.eggsLaidThisNight = valueInput.getIntOr("EggsLaidThisNight", 0); // Added this.isNightMode = valueInput.getBooleanOr("IsNightMode", false); - // Clear current chickens this.storedChickensNbt.clear(); - - // Use map to transform the Optional into a Stream of NBT valueInput.list("StoredChickens", CompoundTag.CODEC) - .map(ValueInput.TypedInputList::stream) + .map(ValueInput.TypedInputList::stream) .ifPresent(stream -> stream.forEach(this.storedChickensNbt::add)); } @@ -406,46 +400,58 @@ public ValueInput childOrEmpty(String key) { @Override public HolderLookup.Provider lookup() { return registries; } + // Ensure the helper record implements stream() + private record ChickenTypedInputList(List items) implements TypedInputList { + @Override public boolean isEmpty() { return items.isEmpty(); } + @Override public Stream stream() { return items.stream(); } + @Override public java.util.Iterator iterator() { return items.iterator(); } + } + @Override public Optional> list(String key, Codec codec) { - // Check if the key exists and is a List if (!tag.contains(key)) return Optional.empty(); - - // In modern mappings, getList only takes the Key. - // It returns the list if found, or an empty one if not. - Optional listTag = tag.getList(key); - // Map the NBT tags to objects using the codec + Optional listTag = tag.getList(key); List items = listTag.stream() .map(nbt -> codec.parse(registries.createSerializationContext(NbtOps.INSTANCE), nbt) .resultOrPartial(System.err::println)) - .flatMap(Optional::stream) // Flattens Optional into the stream + .flatMap(Optional::stream) .toList(); return Optional.of(new ChickenTypedInputList<>(items)); } - // Ensure the helper record implements stream() - private record ChickenTypedInputList(List items) implements TypedInputList { - @Override public boolean isEmpty() { return items.isEmpty(); } - @Override public Stream stream() { return items.stream(); } - @Override public java.util.Iterator iterator() { return items.iterator(); } + @Override + public TypedInputList listOrEmpty(String key, Codec codec) { + return list(key, codec).orElse(new ChickenTypedInputList<>(List.of())); } - @Override public TypedInputList listOrEmpty(String key, Codec codec) { - return new TypedInputList() { - @Override public boolean isEmpty() { return true; } - @Override public Stream stream() { return Stream.empty(); } - @Override public java.util.Iterator iterator() { return Stream.empty().iterator(); } - }; + + @Override + public Optional childrenList(String key) { + // Check if the tag exists and is a List + if (!(tag.get(key) instanceof ListTag listTag)) { + return Optional.empty(); + } + + // Map each element inside the ListTag (which should be CompoundTags) to ValueInputs + List children = listTag.stream() + .filter(t -> t instanceof CompoundTag) // Ensure the entry is a CompoundTag + .map(t -> (ValueInput) new ChickenValueInput((CompoundTag) t, registries)) + .toList(); + + return Optional.of(new ChickenValueInputListImpl(children)); + } + + @Override + public ValueInputList childrenListOrEmpty(String key) { + return childrenList(key).orElse(new ChickenValueInputListImpl(List.of())); } - @Override public Optional childrenList(String string) { return Optional.empty(); } - @Override public ValueInputList childrenListOrEmpty(String string) { - return new ValueInputList() { - @Override public boolean isEmpty() { return true; } - @Override public Stream stream() { return Stream.empty(); } - @Override public java.util.Iterator iterator() { return Stream.empty().iterator(); } - }; + // Support record for ValueInputList + private record ChickenValueInputListImpl(List children) implements ValueInputList { + @Override public boolean isEmpty() { return children.isEmpty(); } + @Override public Stream stream() { return children.stream(); } + @Override public java.util.Iterator iterator() { return children.iterator(); } } } } From cf119cf4d844261f320fad7c3a5e5a8c20d15e8b Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 07:18:09 +0000 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20to=20`fea?= =?UTF-8?q?t/chicken-coop`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docstrings generation was requested by @The-Code-Monkey. The following files were modified: * `src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java` * `src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java` --- .../tcm/MineTale/block/ChickenCoopBlock.java | 63 ++- .../block/entity/ChickenCoopEntity.java | 460 ++++++++++++++++-- 2 files changed, 482 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java b/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java index 7846f71..1be9982 100644 --- a/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java +++ b/src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java @@ -38,6 +38,13 @@ public class ChickenCoopBlock extends HorizontalDirectionalBlock implements Enti public static final MapCodec CODEC = simpleCodec(ChickenCoopBlock::new); + /** + * Create a ChickenCoopBlock configured with the provided block properties and a default state. + * + * The default state sets FACING to NORTH and PART to CoopPart.BOTTOM_FRONT_LEFT. + * + * @param properties block properties used to configure this block's behaviour and characteristics + */ public ChickenCoopBlock(Properties properties) { super(properties); // Default to the origin part (Bottom Front Left) facing North @@ -46,6 +53,18 @@ public ChickenCoopBlock(Properties properties) { .setValue(PART, CoopPart.BOTTOM_FRONT_LEFT)); } + /** + * Provides a BlockEntityTicker for the coop's centre-front part on the server. + * + * Returns a ticker that delegates to ChickenCoopEntity.tick when the call is on the logical server, + * the block state's PART is BOTTOM_FRONT_CENTER and the requested BlockEntityType equals ModBlockEntities.CHICKEN_COOP_BE. + * + * @param the block entity type + * @param level the level containing the block + * @param state the block state for which a ticker is requested + * @param type the requested block entity type + * @return a ticker delegating to ChickenCoopEntity.tick when applicable, `null` otherwise + */ @Nullable @Override public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { @@ -61,6 +80,13 @@ public BlockEntityTicker getTicker(Level level, Block : null; } + /** + * Creates the block entity for the coop when this block represents the centre-front (brain) part. + * + * @param pos the block position + * @param state the current block state + * @return {@code ChickenCoopEntity} for the centre-front part, {@code null} otherwise + */ @Nullable @Override public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { @@ -71,6 +97,13 @@ public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { return null; } + /** + * Registers this block's state properties. + * + * Adds the horizontal facing and coop part properties so block states can represent orientation and segment. + * + * @param builder the state definition builder to register properties with + */ @Override protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(FACING, PART); @@ -221,7 +254,14 @@ public BlockState playerWillDestroy(Level level, BlockPos pos, BlockState state, } /** - * Rotates the 3x3x2 grid logic based on which way the player is facing. + * Compute the world block position for a local coordinate inside the coop's 3×2×3 grid, taking block facing into account. + * + * @param origin the reference origin position (the block considered as the grid origin) + * @param facing the horizontal direction the coop is facing; used to convert local depth into world direction + * @param x local x index in the 3-wide grid (0 = left, 1 = centre, 2 = right) + * @param z local depth index along the facing direction (0..2); larger values are further away from the player + * @param y local vertical index (0..2) measured as blocks above the origin + * @return the computed BlockPos in world coordinates for 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) @@ -234,12 +274,31 @@ private BlockPos calculateOffset(BlockPos origin, Direction facing, int x, int z .above(y); } + /** + * Provide the block's MapCodec used by the game's codec system for (de)serialisation. + * + * @return the MapCodec instance for this block's state + */ @Override protected MapCodec codec() { return CODEC; } - @Override + /** + * Handle interaction with the coop when used without an item, dispensing any collected eggs to the player. + * + * On the client this returns `InteractionResult.SUCCESS`. On the server this locates the coop's brain + * block entity (the bottom-front-center part); if that entity has eggs they are transferred to the player + * (or dropped at the player's feet if their inventory is full) and a chicken-egg sound is played. + * + * @param state the current block state + * @param level the level where the block is located + * @param pos the position of the interacted block + * @param player the player performing the interaction + * @param hitResult hit information for the interaction + * @return `InteractionResult.SUCCESS` if eggs were given or on the client, `InteractionResult.PASS` otherwise. + */ +@Override protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) { if (level.isClientSide()) return InteractionResult.SUCCESS; diff --git a/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java b/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java index ab24dfb..6b4c409 100644 --- a/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java +++ b/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java @@ -42,12 +42,27 @@ public class ChickenCoopEntity extends BlockEntity { private boolean isNightMode = false; private int eggCount = 0; private int eggsLaidThisNight = 0; - private static final int MAX_EGGS = 16; // Limit storage so it's not infinite + private static final int MAX_EGGS = 16; /** + * Creates a ChickenCoopEntity for the given block position and block state. + * + * @param pos the block position for this block entity + * @param state the block state associated with this block entity + */ public ChickenCoopEntity(BlockPos pos, BlockState state) { super(ModBlockEntities.CHICKEN_COOP_BE, pos, state); } + /** + * Update tick handler that transitions the coop between day and night, manages stored chickens and their release, and controls egg production. + * + *

Runs only on the server: when time enters the night range (game time 13000–22999) it collects nearby chickens into the coop and resets the per-night lay counter; when time exits that range it releases stored chickens. While in night mode and with chickens stored, the coop may produce eggs subject to constraints: total eggs are limited by MAX_EGGS, the number of eggs laid during the current night is limited to the number of chickens stored, and each tick there is a 1-in-1000 chance to lay a single egg (which increments the egg count and per-night counter and plays the chicken egg sound).

+ * + * @param level the world in which the coop exists (must be server-side for effects to occur) + * @param pos the block position of the coop + * @param state the block state of the coop + * @param be the ChickenCoopEntity instance to update + */ public static void tick(Level level, BlockPos pos, BlockState state, ChickenCoopEntity be) { if (level.isClientSide()) return; @@ -88,6 +103,16 @@ else if (!isLate && be.isNightMode) { } } + /** + * Spawn a burst of feather particles centred on the given block position. + * + * Spawns `count` feather particles with a small spread and velocity to simulate feathers + * drifting from the block's centre. + * + * @param level the server level to emit the particles in + * @param pos the block position at whose centre the particles will appear + * @param count the number of feather particles to spawn + */ private void spawnFeatherParticles(ServerLevel level, BlockPos pos, int count) { // Define the particle type using the Feather item texture ItemParticleOption particleData = @@ -106,6 +131,14 @@ private void spawnFeatherParticles(ServerLevel level, BlockPos pos, int count) { ); } + /** + * Serialises the coop's persistent state into the provided ValueOutput. + * + * Saves the current egg count, the number of eggs laid this night, the night-mode flag, + * and the stored chickens as a list under the key "StoredChickens". + * + * @param valueOutput destination used to write the entity's persisted fields + */ @Override protected void saveAdditional(ValueOutput valueOutput) { super.saveAdditional(valueOutput); @@ -119,6 +152,14 @@ protected void saveAdditional(ValueOutput valueOutput) { } } + /** + * Restores the chicken coop's persisted state from the given input. + * + * Reads and restores eggCount, eggsLaidThisNight and isNightMode, clears any in-memory stored chicken data + * then repopulates storedChickensNbt from the `StoredChickens` list in the input. The superclass state is loaded first. + * + * @param valueInput source of persisted values and tag lists for this block entity + */ @Override protected void loadAdditional(ValueInput valueInput) { super.loadAdditional(valueInput); @@ -132,6 +173,13 @@ protected void loadAdditional(ValueInput valueInput) { .ifPresent(stream -> stream.forEach(this.storedChickensNbt::add)); } + /** + * Remove and return all eggs currently stored in the coop. + * + * Resets the internal egg count to 0 and marks the block entity as changed. + * + * @return the number of eggs removed from storage, 0 if there were none + */ public int takeAllEggs() { int total = this.eggCount; if (total > 0) { @@ -142,6 +190,15 @@ public int takeAllEggs() { return 0; } + /** + * Finds nearby chickens around the coop, serialises up to six of them into + * {@link #storedChickensNbt}, and removes the original entities from the world. + * + * Spawns feather particles at each collected chicken's position to indicate collection. + * + * @param level the server level containing the coop + * @param pos the block position of the coop used as the search centre + */ private void collectChickens(ServerLevel level, BlockPos pos) { AABB area = new AABB(pos).inflate(8); List nearbyChickens = level.getEntitiesOfClass(Chicken.class, area); @@ -163,6 +220,17 @@ private void collectChickens(ServerLevel level, BlockPos pos) { } } + /** + * Deserialises stored chicken data and spawns those chickens at safe grass positions around the coop. + * + * For each stored chicken this method creates a new Chicken entity from its saved NBT, places it at a + * nearby valid grass spawn within a 3‑block radius of the coop, adds the entity to the level and emits + * feather particles at the spawn location. After spawning all stored chickens the stored data list is + * cleared and the block entity is marked as changed. + * + * @param level the server level in which to spawn the chickens + * @param pos the block position of the coop used to find nearby spawn locations + */ private void releaseChickens(ServerLevel level, BlockPos pos) { for (CompoundTag chickenData : storedChickensNbt) { Chicken chicken = new Chicken(EntityType.CHICKEN, level); @@ -198,7 +266,12 @@ private void releaseChickens(ServerLevel level, BlockPos pos) { } /** - * Scans the area around the coop for grass blocks with air above them. + * Finds a safe spawn position near the coop, preferring grass with two air blocks above. + * + * @param level the server level to search in + * @param origin the block position of the coop centre used as the search origin + * @param radius horizontal radius, in blocks, to search from the origin + * @return a BlockPos on a grass block that has two air blocks above for safe spawning, or origin.above() if no suitable grass spot is found */ private BlockPos findSafeGrassSpawn(ServerLevel level, BlockPos origin, int radius) { java.util.List grassSpots = new java.util.ArrayList<>(); @@ -227,23 +300,89 @@ private BlockPos findSafeGrassSpawn(ServerLevel level, BlockPos origin, int radi private static class ChickenValueOutput implements ValueOutput { private final CompoundTag tag; - private final HolderLookup.Provider registries; // Added this + private final HolderLookup.Provider registries; /** + * Creates a ChickenValueOutput that writes to the given CompoundTag using the provided registry lookup. + * + * @param tag the CompoundTag that will be used as the backing NBT storage + * @param registries a registry lookup provider used when encoding values with codecs + */ public ChickenValueOutput(CompoundTag tag, HolderLookup.Provider registries) { this.tag = tag; this.registries = registries; // Added this } - @Override public void putBoolean(String key, boolean value) { tag.putBoolean(key, value); } - @Override public void putByte(String key, byte value) { tag.putByte(key, value); } - @Override public void putShort(String key, short value) { tag.putShort(key, value); } - @Override public void putInt(String key, int value) { tag.putInt(key, value); } - @Override public void putLong(String key, long value) { tag.putLong(key, value); } - @Override public void putFloat(String key, float value) { tag.putFloat(key, value); } - @Override public void putDouble(String key, double value) { tag.putDouble(key, value); } - @Override public void putString(String key, String value) { tag.putString(key, value); } - @Override public void putIntArray(String key, int[] value) { tag.putIntArray(key, value); } - + /** + * Store a boolean into the underlying NBT compound under the given key. + * + * @param key the NBT key to write the value to + * @param value the boolean value to store + */ +@Override public void putBoolean(String key, boolean value) { tag.putBoolean(key, value); } + /** + * Store a byte value in the backing tag under the given key. + * + * @param key the NBT key name to write the byte to + * @param value the byte value to store + */ +@Override public void putByte(String key, byte value) { tag.putByte(key, value); } + /** + * Stores a 16-bit signed integer into the underlying NBT compound under the given key. + * + * @param key the NBT key to write to + * @param value the 16-bit signed integer value to store + */ +@Override public void putShort(String key, short value) { tag.putShort(key, value); } + /** + * Stores an integer value in the underlying NBT tag under the given key. + * + * @param key the NBT key to store the integer under + * @param value the integer value to store + */ +@Override public void putInt(String key, int value) { tag.putInt(key, value); } + /** + * Store a long value in the internal NBT tag under the specified key. + * + * @param key the NBT key to write the value to + * @param value the long value to store + */ +@Override public void putLong(String key, long value) { tag.putLong(key, value); } + /** + * Stores a float under the specified key in the underlying CompoundTag. + * + * @param key the NBT key to store the value under + * @param value the float value to write + */ +@Override public void putFloat(String key, float value) { tag.putFloat(key, value); } + /** + * Store a double value in the underlying compound tag under the specified key. + * + * @param key the NBT key to store the value under + * @param value the double value to write + */ +@Override public void putDouble(String key, double value) { tag.putDouble(key, value); } + /** + * Store a string value in the underlying NBT tag under the given key. + * + * @param key the NBT key to write the string to + * @param value the string value to store + */ +@Override public void putString(String key, String value) { tag.putString(key, value); } + /** + * Store an integer array in the underlying CompoundTag under the specified key. + * + * @param key the NBT key to write the array to + * @param value the integer array to store + */ +@Override public void putIntArray(String key, int[] value) { tag.putIntArray(key, value); } + + /** + * Encode an object with the provided Codec and store its resulting NBT under the given key. + * + * @param key the tag key to store the encoded value under + * @param codec the Codec used to encode the value into NBT + * @param value the object to encode and store + */ @Override public void store(String key, Codec codec, T value) { // Use the codec to turn the object into NBT @@ -251,17 +390,41 @@ public void store(String key, Codec codec, T value) { .ifPresent(nbt -> tag.put(key, nbt)); } - // Boilerplate for lists and children (can be implemented if needed) + /** + * Create and attach a new child compound tag under the given key and provide a ValueOutput that writes into it. + * + * @param key the key under which the new child CompoundTag will be stored + * @return a `ValueOutput` that targets the newly created child CompoundTag + */ @Override public ValueOutput child(String key) { CompoundTag childTag = new CompoundTag(); tag.put(key, childTag); return new ChickenValueOutput(childTag, registries); } - // Implement other required methods with empty/default logic or full NBT support + /** + * Indicates whether the underlying NBT list is empty. + * + * @return `true` if the list contains no elements, `false` otherwise. + */ @Override public boolean isEmpty() { return tag.isEmpty(); } - @Override public void discard(String string) { tag.remove(string); } - + /** + * Removes the mapping for the given key from the underlying NBT tag. + * + * @param string the key to remove from the compound tag + */ +@Override public void discard(String string) { tag.remove(string); } + + /** + * Serialises a non-null value to NBT and stores it under the given key in this tag. + * + * Uses the provided `codec` and the entity's registry-backed serialization context to encode `object` + * into an NBT element; if encoding succeeds the resulting NBT is written to `tag` under `key`. + * + * @param key the NBT key to store the encoded value under + * @param codec the codec used to serialise the value into NBT + * @param object the value to serialise; if `null` nothing is written + */ @Override public void storeNullable(String key, Codec codec, @Nullable T object) { if (object == null) return; // Don't write anything if it's null @@ -272,7 +435,15 @@ public void storeNullable(String key, Codec codec, @Nullable T object) { .ifPresent(nbt -> tag.put(key, nbt)); } - @Override + /** + * Serialises the given object with the provided MapCodec and stores the resulting NBT in this.tag. + * + * Encodes using a registry-aware NBT serialization context; on encoding failure an error message is printed to standard error. + * + * @param mapCodec the MapCodec used to encode the object into NBT + * @param object the object to serialise and store + */ + @Override public void store(MapCodec mapCodec, T object) { // 1. Create the context that knows about Minecraft registries DynamicOps ops = registries.createSerializationContext(NbtOps.INSTANCE); @@ -291,6 +462,12 @@ public void store(MapCodec mapCodec, T object) { }); } + /** + * Create and attach an empty list tag under the given key and return a writer for its elements. + * + * @param key the NBT key under which the new ListTag will be stored + * @return a ValueOutputList backed by the newly created ListTag for adding child compound entries + */ @Override public ValueOutputList childrenList(String key) { ListTag listTag = new ListTag(); @@ -299,6 +476,13 @@ public ValueOutputList childrenList(String key) { return new ChickenValueOutputList(listTag, registries); } + /** + * Create and attach a new NBT list under the given key and provide a typed output view for adding encoded elements. + * + * @param key the tag name under which the new list will be stored + * @param codec the codec used to encode elements placed into the list + * @return a {@link TypedOutputList} whose additions are encoded with the provided codec and appended to the created NBT list + */ @Override public TypedOutputList list(String key, Codec codec) { ListTag listTag = new ListTag(); @@ -309,6 +493,11 @@ public TypedOutputList list(String key, Codec codec) { } private record ChickenValueOutputList(ListTag list, HolderLookup.Provider registries) implements ValueOutputList { + /** + * Create a new child output element and append it to the list. + * + * @return a ValueOutput that writes into the newly added CompoundTag list element + */ @Override public ValueOutput addChild() { CompoundTag nextTag = new CompoundTag(); @@ -317,6 +506,11 @@ public ValueOutput addChild() { return new ChickenValueOutput(nextTag, registries); } + /** + * Remove the last child element from the list. + * + * If the list is empty this method performs no action. + */ @Override public void discardLast() { if (!list.isEmpty()) { @@ -324,6 +518,11 @@ public void discardLast() { } } + /** + * Checks whether the output list contains no elements. + * + * @return `true` if the list contains no elements, `false` otherwise. + */ @Override public boolean isEmpty() { return list.isEmpty(); @@ -331,6 +530,11 @@ public boolean isEmpty() { } private record ChickenTypedOutputList(ListTag list, Codec codec, HolderLookup.Provider registries) implements TypedOutputList { + /** + * Encodes a value with the list's codec and appends the resulting NBT element to the backing list. + * + * @param value the item to encode and add + * If encoding fails the error is logged and the value is not added. @Override public void add(T value) { // serialize the object into NBT and add it to the list if successful @@ -339,6 +543,11 @@ public void add(T value) { .ifPresent(list::add); } + /** + * Checks whether the output list contains no elements. + * + * @return `true` if the list contains no elements, `false` otherwise. + */ @Override public boolean isEmpty() { return list.isEmpty(); @@ -349,17 +558,37 @@ private static class ChickenValueInput implements ValueInput { private final CompoundTag tag; private final HolderLookup.Provider registries; + /** + * Create a ChickenValueInput that reads values from the given NBT tag using the provided registries. + * + * @param tag the CompoundTag containing the serialized chicken data to read + * @param registries a HolderLookup.Provider used to resolve registry-backed codecs and lookups during decoding + */ public ChickenValueInput(CompoundTag tag, HolderLookup.Provider registries) { this.tag = tag; this.registries = registries; } + /** + * Read and decode a value stored under the given key from the backing CompoundTag. + * + * @param the target type produced by the codec + * @param key the tag key to read + * @param codec the codec used to decode the tag value + * @return an Optional containing the decoded value if the key exists and decoding succeeds, `Optional.empty()` otherwise + */ @Override public Optional read(String key, Codec codec) { if (!tag.contains(key)) return Optional.empty(); return codec.parse(NbtOps.INSTANCE, tag.get(key)).result(); } + /** + * Decode a value from this CompoundTag using the provided MapCodec and the entity's registry context. + * + * @param mapCodec the MapCodec describing how to decode the tag into a value of type `T` + * @return an Optional containing the decoded value when parsing succeeds, or an empty Optional when parsing fails + */ @Override public Optional read(MapCodec mapCodec) { // 1. Convert the MapCodec into a standard Codec @@ -370,6 +599,12 @@ public Optional read(MapCodec mapCodec) { .result(); // This returns an Optional automatically } + /** + * Obtain a nested ValueInput for the compound tag stored under the given key. + * + * @param key the name of the compound tag to read + * @return an Optional containing a ValueInput for the nested CompoundTag if the key exists, otherwise Optional.empty() + */ @Override public Optional child(String key) { if (!tag.contains(key)) return Optional.empty(); @@ -377,36 +612,148 @@ public Optional child(String key) { return Optional.of(input); } + /** + * Return a child ValueInput for the given key, or an empty input when no child tag exists. + * + * @param key the name of the child tag to retrieve + * @return a `ValueInput` for the child tag if present, otherwise an empty `ValueInput` backed by a fresh `CompoundTag` + */ @Override public ValueInput childOrEmpty(String key) { return child(key).orElse(new ChickenValueInput(new CompoundTag(), registries)); } - // Primitive Getters with Fallbacks + /** + * Retrieves the boolean stored under the given key in the tag, or returns the supplied fallback if the key is absent. + * + * @param key the key to look up in the tag + * @param fallback value to return when the key is not present + * @return `true` if the tag contains `true` for the key, otherwise the supplied fallback + */ @Override public boolean getBooleanOr(String key, boolean fallback) { return tag.contains(key) ? tag.getBoolean(key).get() : fallback; } - @Override public byte getByteOr(String key, byte fallback) { return tag.contains(key) ? tag.getByte(key).get() : fallback; } - @Override public int getShortOr(String key, short fallback) { return tag.contains(key) ? tag.getShort(key).get() : fallback; } - @Override public int getIntOr(String key, int fallback) { return tag.contains(key) ? tag.getInt(key).get() : fallback; } - @Override public long getLongOr(String key, long fallback) { return tag.contains(key) ? tag.getLong(key).get() : fallback; } - @Override public float getFloatOr(String key, float fallback) { return tag.contains(key) ? tag.getFloat(key).get() : fallback; } - @Override public double getDoubleOr(String key, double fallback) { return tag.contains(key) ? tag.getDouble(key).get() : fallback; } - @Override public String getStringOr(String key, String fallback) { return tag.contains(key) ? tag.getString(key).get() : fallback; } - - // Optional Getters + /** + * Retrieve the byte stored under the given key from the entity tag, or return the provided fallback if the key is missing. + * + * @param key the NBT key to read + * @param fallback value to return when the key is not present + * @return the byte stored under `key`, or `fallback` if the key is absent + */ +@Override public byte getByteOr(String key, byte fallback) { return tag.contains(key) ? tag.getByte(key).get() : fallback; } + /** + * Retrieve the short value stored under the given key, or return the provided fallback. + * + * @param key the NBT key to read + * @param fallback value to return if the key is not present + * @return the stored short value as an int if present, otherwise the fallback + */ +@Override public int getShortOr(String key, short fallback) { return tag.contains(key) ? tag.getShort(key).get() : fallback; } + /** + * Retrieve an integer value for the given key from the backing tag, or return the provided fallback if the key is absent. + * + * @param key the tag key to read + * @param fallback the value to return when the key is not present + * @return the integer stored under `key`, or `fallback` if the key is missing + */ +@Override public int getIntOr(String key, int fallback) { return tag.contains(key) ? tag.getInt(key).get() : fallback; } + /** + * Retrieve the long value stored under the given key in the tag, or return the provided fallback if the key is absent. + * + * @param key the tag key to read + * @param fallback the value to return when the key is not present + * @return the long value for `key` if present, `fallback` otherwise + */ +@Override public long getLongOr(String key, long fallback) { return tag.contains(key) ? tag.getLong(key).get() : fallback; } + /** + * Retrieve the float value stored under the given key in the tag, or return the provided fallback if the key is not present. + * + * @param key the tag key to read + * @param fallback the value to return when the key is absent + * @return the float value stored under {@code key}, or {@code fallback} if the key is missing + */ +@Override public float getFloatOr(String key, float fallback) { return tag.contains(key) ? tag.getFloat(key).get() : fallback; } + /** + * Retrieve a double value from the entity's NBT tag or return a fallback. + * + * @param key the tag key to read + * @param fallback the value to return if the tag key is not present + * @return the double stored under `key`, or `fallback` if the key is absent + */ +@Override public double getDoubleOr(String key, double fallback) { return tag.contains(key) ? tag.getDouble(key).get() : fallback; } + /** + * Get the string stored under the given key, or the provided fallback if the key is absent. + * + * @param key the NBT key to read + * @param fallback the value to return when the key is not present + * @return the string value associated with `key`, or `fallback` if the key is missing + */ +@Override public String getStringOr(String key, String fallback) { return tag.contains(key) ? tag.getString(key).get() : fallback; } + + /** + * Retrieve an integer value stored under the given NBT key, if present. + * + * @param key the NBT tag key to read + * @return an {@code Optional} containing the integer value if the key exists, {@code Optional.empty()} otherwise + */ @Override public Optional getInt(String key) { return tag.contains(key) ? Optional.of(tag.getInt(key).get()) : Optional.empty(); } - @Override public Optional getLong(String key) { return tag.contains(key) ? Optional.of(tag.getLong(key).get()) : Optional.empty(); } - @Override public Optional getString(String key) { return tag.contains(key) ? Optional.of(tag.getString(key).get()) : Optional.empty(); } - @Override public Optional getIntArray(String key) { return tag.contains(key) ? Optional.of(tag.getIntArray(key).get()) : Optional.empty(); } - - @Override public HolderLookup.Provider lookup() { return registries; } + /** + * Retrieve the long value stored under the given NBT key. + * + * @param key the NBT key to read from + * @return an Optional containing the long value for the given key, or empty if the key is not present + */ +@Override public Optional getLong(String key) { return tag.contains(key) ? Optional.of(tag.getLong(key).get()) : Optional.empty(); } + /** + * Retrieve the string value stored under the given NBT key, if present. + * + * @param key the NBT key to read + * @return an Optional containing the string value for the key, or Optional.empty() if the key is not present + */ +@Override public Optional getString(String key) { return tag.contains(key) ? Optional.of(tag.getString(key).get()) : Optional.empty(); } + /** + * Obtain the value stored under the given NBT key as an array, if present. + * + * @param key the NBT key to read from + * @return an Optional containing the int array stored under the key, or empty if the key is not present + */ +@Override public Optional getIntArray(String key) { return tag.contains(key) ? Optional.of(tag.getIntArray(key).get()) : Optional.empty(); } + + /** + * Gets the registry lookup provider used by this input for decoding values. + * + * @return the HolderLookup.Provider used for registry lookups + */ +@Override public HolderLookup.Provider lookup() { return registries; } // Ensure the helper record implements stream() private record ChickenTypedInputList(List items) implements TypedInputList { - @Override public boolean isEmpty() { return items.isEmpty(); } - @Override public Stream stream() { return items.stream(); } - @Override public java.util.Iterator iterator() { return items.iterator(); } + /** + * Check whether the entity contains no items. + * + * @return `true` if no items are stored, `false` otherwise. + */ +@Override public boolean isEmpty() { return items.isEmpty(); } + /** + * Provide a stream over the list's elements. + * + * @return a stream of the list's elements + */ +@Override public Stream stream() { return items.stream(); } + /** + * Provide an iterator over the list's elements. + * + * @return an Iterator over the contained items. + */ +@Override public java.util.Iterator iterator() { return items.iterator(); } } + /** + * Read a list of values from the CompoundTag under the given key and decode them with the provided codec. + * + * @param key the tag key that should contain a ListTag of element NBTs + * @param codec the codec used to decode each list element from NBT + * @return an Optional containing a TypedInputList of decoded items if the key exists; Optional.empty() if the key is absent. + * Elements that fail to decode are omitted from the returned list and decoding errors are printed to standard error. + */ @Override public Optional> list(String key, Codec codec) { if (!tag.contains(key)) return Optional.empty(); @@ -421,11 +768,25 @@ public Optional> list(String key, Codec codec) { return Optional.of(new ChickenTypedInputList<>(items)); } + /** + * Obtain a typed list decoded from the stored NBT under the given key, or an empty list if the key is not present. + * + * @param key the NBT tag key to read the list from + * @param codec the codec used to decode each list element + * @return a TypedInputList containing decoded items for the key, or an empty list if the key is absent + */ @Override public TypedInputList listOrEmpty(String key, Codec codec) { return list(key, codec).orElse(new ChickenTypedInputList<>(List.of())); } + /** + * Retrieve the list of child compound tags stored under the specified NBT key as a ValueInputList. + * + * @param key the NBT key whose value is expected to be a ListTag of CompoundTag elements + * @return an Optional containing a ValueInputList wrapping the child CompoundTags found at `key`, + * or Optional.empty() if the key is absent or its value is not a list + */ @Override public Optional childrenList(String key) { // Check if the tag exists and is a List @@ -442,6 +803,12 @@ public Optional childrenList(String key) { return Optional.of(new ChickenValueInputListImpl(children)); } + /** + * Get the list of child ValueInput entries stored under the given key, or an empty list if the key is absent or not a list. + * + * @param key the tag key to read child elements from + * @return a ValueInputList containing a ValueInput for each child CompoundTag under `key`, or an empty ValueInputList if none exist + */ @Override public ValueInputList childrenListOrEmpty(String key) { return childrenList(key).orElse(new ChickenValueInputListImpl(List.of())); @@ -449,9 +816,24 @@ public ValueInputList childrenListOrEmpty(String key) { // Support record for ValueInputList private record ChickenValueInputListImpl(List children) implements ValueInputList { - @Override public boolean isEmpty() { return children.isEmpty(); } - @Override public Stream stream() { return children.stream(); } - @Override public java.util.Iterator iterator() { return children.iterator(); } + /** + * Checks whether there are no child value inputs in the list. + * + * @return `true` if the list contains no child inputs, `false` otherwise. + */ +@Override public boolean isEmpty() { return children.isEmpty(); } + /** + * Provide a stream of the child ValueInput elements. + * + * @return a Stream of the contained ValueInput instances in iteration order + */ +@Override public Stream stream() { return children.stream(); } + /** + * Provide an iterator over the contained ValueInput elements. + * + * @return an Iterator over the contained ValueInput instances + */ +@Override public java.util.Iterator iterator() { return children.iterator(); } } } } From 5aeca54f9c44e86d9d79a4f3613f4dbb187a22aa Mon Sep 17 00:00:00 2001 From: Andy Wilson Date: Fri, 6 Mar 2026 07:22:29 +0000 Subject: [PATCH 7/7] fix: docstrings broke code --- .../block/entity/ChickenCoopEntity.java | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java b/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java index 6b4c409..b594906 100644 --- a/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java +++ b/src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java @@ -492,6 +492,32 @@ public TypedOutputList list(String key, Codec codec) { } } + private record ChickenTypedOutputList(ListTag list, Codec codec, HolderLookup.Provider registries) implements TypedOutputList { + /** + * Encodes a value with the list's codec and appends the resulting NBT element to the backing list. + * + * @param value the item to encode and add + * If encoding fails the error is logged and the value is not added. + */ + @Override + public void add(T value) { + // serialize the object into NBT and add it to the list if successful + codec.encodeStart(registries.createSerializationContext(NbtOps.INSTANCE), value) + .resultOrPartial(System.err::println) + .ifPresent(list::add); + } + + /** + * Checks whether the output list contains no elements. + * + * @return `true` if the list contains no elements, `false` otherwise. + */ + @Override + public boolean isEmpty() { + return list.isEmpty(); + } + } + private record ChickenValueOutputList(ListTag list, HolderLookup.Provider registries) implements ValueOutputList { /** * Create a new child output element and append it to the list. @@ -529,31 +555,6 @@ public boolean isEmpty() { } } - private record ChickenTypedOutputList(ListTag list, Codec codec, HolderLookup.Provider registries) implements TypedOutputList { - /** - * Encodes a value with the list's codec and appends the resulting NBT element to the backing list. - * - * @param value the item to encode and add - * If encoding fails the error is logged and the value is not added. - @Override - public void add(T value) { - // serialize the object into NBT and add it to the list if successful - codec.encodeStart(registries.createSerializationContext(NbtOps.INSTANCE), value) - .resultOrPartial(System.err::println) - .ifPresent(list::add); - } - - /** - * Checks whether the output list contains no elements. - * - * @return `true` if the list contains no elements, `false` otherwise. - */ - @Override - public boolean isEmpty() { - return list.isEmpty(); - } - } - private static class ChickenValueInput implements ValueInput { private final CompoundTag tag; private final HolderLookup.Provider registries;