Skip to content

Commit 524dc0b

Browse files
feat: eggs laid in coop at night are collectable
1 parent 10b6012 commit 524dc0b

5 files changed

Lines changed: 158 additions & 47 deletions

File tree

src/client/java/com/tcm/MineTale/datagen/recipes/AlchemistRecipes.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
package com.tcm.MineTale.datagen.recipes;
22

3+
import com.tcm.MineTale.datagen.builders.WorkbenchRecipeBuilder;
4+
import com.tcm.MineTale.registry.ModBlocks;
5+
import com.tcm.MineTale.registry.ModItems;
6+
import com.tcm.MineTale.registry.ModRecipeDisplay;
7+
import com.tcm.MineTale.registry.ModRecipes;
8+
39
import net.minecraft.core.HolderLookup;
410
import net.minecraft.data.recipes.RecipeOutput;
511
import net.minecraft.data.recipes.RecipeProvider;

src/main/java/com/tcm/MineTale/block/ChickenCoopBlock.java

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@
77

88
import net.minecraft.core.BlockPos;
99
import net.minecraft.core.Direction;
10+
import net.minecraft.sounds.SoundEvents;
11+
import net.minecraft.sounds.SoundSource;
1012
import net.minecraft.util.RandomSource;
13+
import net.minecraft.world.InteractionResult;
1114
import net.minecraft.world.entity.LivingEntity;
1215
import net.minecraft.world.entity.player.Player;
1316
import net.minecraft.world.item.ItemStack;
17+
import net.minecraft.world.item.Items;
1418
import net.minecraft.world.item.context.BlockPlaceContext;
1519
import net.minecraft.world.level.Level;
1620
import net.minecraft.world.level.LevelReader;
@@ -25,6 +29,8 @@
2529
import net.minecraft.world.level.block.state.BlockState;
2630
import net.minecraft.world.level.block.state.StateDefinition;
2731
import net.minecraft.world.level.block.state.properties.EnumProperty;
32+
import net.minecraft.world.phys.BlockHitResult;
33+
2834
import org.jetbrains.annotations.Nullable;
2935

3036
public class ChickenCoopBlock extends HorizontalDirectionalBlock implements EntityBlock {
@@ -234,33 +240,42 @@ protected MapCodec<? extends HorizontalDirectionalBlock> codec() {
234240
}
235241

236242
@Override
237-
protected ItemInteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) {
238-
if (level.isClientSide()) return ItemInteractionResult.SUCCESS;
243+
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
244+
if (level.isClientSide()) return InteractionResult.SUCCESS;
239245

240-
Direction facing = state.getValue(FACING);
241-
CoopPart part = state.getValue(PART);
242-
243-
// 1. Find the Brain (Bottom Front Center)
244-
// We reverse the offset from the clicked part to find the origin (0,0,0)
245-
// Then we add the specific offset for the Bottom Front Center (1,0,0)
246-
BlockPos origin = pos.subtract(calculateOffset(BlockPos.ZERO, facing, part.getXOffset(), part.getZOffset(), part.getYOffset()));
247-
BlockPos brainPos = calculateOffset(origin, facing, 1, 0, 0);
248-
249-
if (level.getBlockEntity(brainPos) instanceof ChickenCoopBlockEntity be) {
250-
if (be.takeEgg() > 0) {
251-
// Give player the egg
252-
ItemStack eggStack = new ItemStack(Items.EGG);
253-
if (!player.getInventory().add(eggStack)) {
254-
player.drop(eggStack, false);
255-
}
246+
// 1. Find the "brain" position (the Bottom Front Center)
247+
Direction facing = state.getValue(FACING);
248+
CoopPart currentPart = state.getValue(PART);
249+
250+
// Calculate the origin (Bottom Front Center is our origin in calculateOffset logic)
251+
// Based on your calculateOffset, the BFC is at x=1, z=0, y=0.
252+
BlockPos brainPos = pos.subtract(calculateOffset(BlockPos.ZERO, facing,
253+
currentPart.getXOffset(), currentPart.getZOffset(), currentPart.getYOffset()))
254+
.relative(facing, 0) // already at z=0
255+
.relative(facing.getClockWise(), 0); // x=1 is center, so we shift back to it
256+
257+
// Easier way: Since you know the brain is always at BOTTOM_FRONT_CENTER:
258+
// We just need to find where that specific part is relative to the current block.
259+
// However, your 'calculateOffset' is already the source of truth.
260+
261+
if (level.getBlockEntity(brainPos) instanceof ChickenCoopEntity coopBe) {
262+
int eggsToGive = coopBe.takeAllEggs();
256263

257-
// Visual/Sound feedback
258-
level.playSound(null, pos, SoundEvents.ITEM_PICKUP, SoundSource.PLAYERS, 0.2f, (level.random.nextFloat() - level.random.nextFloat()) * 0.7f + 1.2f);
259-
return ItemInteractionResult.CONSUME;
264+
if (eggsToGive > 0) {
265+
// Give the player an egg
266+
ItemStack eggStack = new ItemStack(Items.EGG, eggsToGive);
267+
if (!player.getInventory().add(eggStack)) {
268+
// If inventory full, drop at player's feet
269+
player.drop(eggStack, false);
270+
}
271+
272+
// Play a sound to give feedback
273+
level.playSound(null, pos, SoundEvents.CHICKEN_EGG, SoundSource.PLAYERS, 1.0f, 1.0f);
274+
return InteractionResult.SUCCESS;
275+
}
260276
}
261-
}
262277

263-
return ItemInteractionResult.PASS;
278+
return InteractionResult.PASS;
264279
}
265280

266281
}

src/main/java/com/tcm/MineTale/block/entity/ChickenCoopEntity.java

Lines changed: 114 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@
22

33
import net.minecraft.core.BlockPos;
44
import net.minecraft.core.HolderLookup;
5+
import net.minecraft.core.particles.ItemParticleOption;
6+
import net.minecraft.core.particles.ParticleTypes;
57
import net.minecraft.nbt.CompoundTag;
68
import net.minecraft.nbt.ListTag;
79
import net.minecraft.nbt.NbtOps;
810
import net.minecraft.nbt.Tag;
911
import net.minecraft.server.level.ServerLevel;
12+
import net.minecraft.sounds.SoundEvents;
13+
import net.minecraft.sounds.SoundSource;
1014
import net.minecraft.world.entity.EntityType;
1115
import net.minecraft.world.entity.animal.chicken.Chicken;
16+
import net.minecraft.world.item.ItemStack;
17+
import net.minecraft.world.item.Items;
1218
import net.minecraft.world.level.Level;
1319
import net.minecraft.world.level.block.entity.BlockEntity;
1420
import net.minecraft.world.level.block.state.BlockState;
@@ -35,6 +41,7 @@ public class ChickenCoopEntity extends BlockEntity {
3541
private final List<CompoundTag> storedChickensNbt = new ArrayList<>();
3642
private boolean isNightMode = false;
3743
private int eggCount = 0;
44+
private int eggsLaidThisNight = 0;
3845
private static final int MAX_EGGS = 16; // Limit storage so it's not infinite
3946

4047
public ChickenCoopEntity(BlockPos pos, BlockState state) {
@@ -45,38 +52,98 @@ public static void tick(Level level, BlockPos pos, BlockState state, ChickenCoop
4552
if (level.isClientSide()) return;
4653

4754
long time = level.getDayTime() % 24000;
48-
boolean isLate = time >= 13000 && time < 23000; // Monster spawn window
55+
boolean isLate = time >= 13000 && time < 23000;
4956

57+
// Transition to Night
5058
if (isLate && !be.isNightMode) {
51-
System.out.println("Chicken Coop: Attempting to collect chickens!");
5259
be.collectChickens((ServerLevel) level, pos);
5360
be.isNightMode = true;
61+
be.eggsLaidThisNight = 0; // Reset the counter for the new night
5462
be.setChanged();
55-
} else if (!isLate && be.isNightMode) {
63+
}
64+
// Transition to Day
65+
else if (!isLate && be.isNightMode) {
5666
be.releaseChickens((ServerLevel) level, pos);
5767
be.isNightMode = false;
5868
be.setChanged();
5969
}
6070

61-
// If it's night and we have chickens inside, try to lay eggs
62-
if (be.isNightMode && !be.storedChickensNbt.isEmpty() && be.eggCount < MAX_EGGS) {
63-
// Minecraft chickens lay eggs every 6000-12000 ticks.
64-
// With up to 6 chickens, a 1 in 1000 chance per tick is roughly realistic.
65-
if (level.random.nextInt(1000) < be.storedChickensNbt.size()) {
66-
be.eggCount++;
67-
be.setChanged();
68-
// Optional: Play a muffled chicken sound from inside the coop
69-
level.playSound(null, pos, SoundEvents.CHICKEN_EGG, SoundSource.BLOCKS, 0.5f, 1.0f);
71+
// Egg Laying Logic
72+
if (be.isNightMode && !be.storedChickensNbt.isEmpty()) {
73+
int chickensInside = be.storedChickensNbt.size();
74+
75+
// Condition 1: Total coop storage isn't full (MAX_EGGS = 16)
76+
// Condition 2: This specific night hasn't exceeded the chicken count
77+
if (be.eggCount < MAX_EGGS && be.eggsLaidThisNight < chickensInside) {
78+
79+
// 1 in 1000 chance per tick is balanced for a full night
80+
if (level.random.nextInt(1000) == 0) {
81+
be.eggCount++;
82+
be.eggsLaidThisNight++; // This ensures this specific chicken is "done" for the night
83+
be.setChanged();
84+
85+
level.playSound(null, pos, SoundEvents.CHICKEN_EGG, SoundSource.BLOCKS, 0.5f, 1.2f);
86+
}
7087
}
7188
}
7289
}
7390

74-
// Helper for the player to interact
75-
public int takeEgg() {
76-
if (eggCount > 0) {
77-
eggCount--;
78-
setChanged();
79-
return 1;
91+
private void spawnFeatherParticles(ServerLevel level, BlockPos pos, int count) {
92+
// Define the particle type using the Feather item texture
93+
ItemParticleOption particleData =
94+
new ItemParticleOption(
95+
ParticleTypes.ITEM,
96+
new ItemStack(Items.FEATHER)
97+
);
98+
99+
// Spawn the particles
100+
// Parameters: particle, pos.x, pos.y, pos.z, count, speedX, speedY, speedZ, velocityScale
101+
level.sendParticles(particleData,
102+
pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
103+
count, // amount of feathers
104+
0.3, 0.3, 0.3, // spread (delta)
105+
0.15 // speed/velocity
106+
);
107+
}
108+
109+
@Override
110+
protected void saveAdditional(ValueOutput valueOutput) {
111+
super.saveAdditional(valueOutput);
112+
113+
// Save the simple primitives
114+
valueOutput.putInt("EggCount", this.eggCount);
115+
valueOutput.putBoolean("IsNightMode", this.isNightMode);
116+
117+
// Save the list of chickens using your TypedOutputList logic
118+
// We use the CompoundTag.CODEC to store the raw NBT of each chicken
119+
ValueOutput.TypedOutputList<CompoundTag> chickenList = valueOutput.list("StoredChickens", CompoundTag.CODEC);
120+
for (CompoundTag chickenNbt : storedChickensNbt) {
121+
chickenList.add(chickenNbt);
122+
}
123+
}
124+
125+
@Override
126+
protected void loadAdditional(ValueInput valueInput) {
127+
super.loadAdditional(valueInput);
128+
129+
this.eggCount = valueInput.getIntOr("EggCount", 0);
130+
this.isNightMode = valueInput.getBooleanOr("IsNightMode", false);
131+
132+
// Clear current chickens
133+
this.storedChickensNbt.clear();
134+
135+
// Use map to transform the Optional<TypedInputList> into a Stream of NBT
136+
valueInput.list("StoredChickens", CompoundTag.CODEC)
137+
.map(ValueInput.TypedInputList::stream)
138+
.ifPresent(stream -> stream.forEach(this.storedChickensNbt::add));
139+
}
140+
141+
public int takeAllEggs() {
142+
int total = this.eggCount;
143+
if (total > 0) {
144+
this.eggCount = 0;
145+
this.setChanged();
146+
return total;
80147
}
81148
return 0;
82149
}
@@ -91,10 +158,11 @@ private void collectChickens(ServerLevel level, BlockPos pos) {
91158

92159
// Wrap our CompoundTag in the ValueOutput implementation
93160
ValueOutput output = new ChickenValueOutput(chickenData, level.registryAccess());
161+
162+
spawnFeatherParticles(level, chicken.blockPosition(), 15);
94163

95164
// Satisfies the method signature perfectly!
96165
chicken.saveWithoutId(output);
97-
98166
// Store the result
99167
storedChickensNbt.add(chickenData);
100168
chicken.discard();
@@ -127,6 +195,8 @@ private void releaseChickens(ServerLevel level, BlockPos pos) {
127195
);
128196

129197
level.addFreshEntity(chicken);
198+
199+
spawnFeatherParticles(level, chicken.blockPosition(), 15);
130200
}
131201

132202
storedChickensNbt.clear();
@@ -336,8 +406,31 @@ public ValueInput childOrEmpty(String key) {
336406

337407
@Override public HolderLookup.Provider lookup() { return registries; }
338408

339-
// Minimal implementation for Lists (can be expanded if chickens store list data)
340-
@Override public <T> Optional<TypedInputList<T>> list(String key, Codec<T> codec) { return Optional.empty(); }
409+
@Override
410+
public <T> Optional<TypedInputList<T>> list(String key, Codec<T> codec) {
411+
// Check if the key exists and is a List
412+
if (!tag.contains(key)) return Optional.empty();
413+
414+
// In modern mappings, getList only takes the Key.
415+
// It returns the list if found, or an empty one if not.
416+
Optional<ListTag> listTag = tag.getList(key);
417+
418+
// Map the NBT tags to objects using the codec
419+
List<T> items = listTag.stream()
420+
.map(nbt -> codec.parse(registries.createSerializationContext(NbtOps.INSTANCE), nbt)
421+
.resultOrPartial(System.err::println))
422+
.flatMap(Optional::stream) // Flattens Optional<T> into the stream
423+
.toList();
424+
425+
return Optional.of(new ChickenTypedInputList<>(items));
426+
}
427+
428+
// Ensure the helper record implements stream()
429+
private record ChickenTypedInputList<T>(List<T> items) implements TypedInputList<T> {
430+
@Override public boolean isEmpty() { return items.isEmpty(); }
431+
@Override public Stream<T> stream() { return items.stream(); }
432+
@Override public java.util.Iterator<T> iterator() { return items.iterator(); }
433+
}
341434
@Override public <T> TypedInputList<T> listOrEmpty(String key, Codec<T> codec) {
342435
return new TypedInputList<T>() {
343436
@Override public boolean isEmpty() { return true; }

src/main/java/com/tcm/MineTale/block/workbenches/entity/AlchemistsWorkbenchEntity.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import com.tcm.MineTale.recipe.WorkbenchRecipe;
66
import com.tcm.MineTale.registry.ModBlockEntities;
77
import com.tcm.MineTale.registry.ModRecipes;
8-
import com.tcm.MineTale.util.Constants;
98
import net.minecraft.core.BlockPos;
109
import net.minecraft.server.level.ServerPlayer;
1110
import net.minecraft.world.entity.player.Inventory;
@@ -19,7 +18,6 @@
1918
import net.minecraft.world.level.storage.ValueOutput;
2019
import org.jspecify.annotations.Nullable;
2120

22-
import java.util.ArrayList;
2321
import java.util.List;
2422
import java.util.stream.IntStream;
2523

src/main/java/com/tcm/MineTale/registry/ModItems.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import com.tcm.MineTale.item.ModArmorMaterials;
1010
import com.tcm.MineTale.item.ModCreativeTab;
1111

12-
import com.tcm.MineTale.util.ModTags;
1312
import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents;
1413
import net.minecraft.core.Registry;
1514
import net.minecraft.core.component.DataComponents;

0 commit comments

Comments
 (0)