net/minecraft/world/entity/ai/behavior/

This commit is contained in:
Owen1212055
2024-12-14 17:25:17 -05:00
parent 4707f46b25
commit d096e6baaf
34 changed files with 324 additions and 560 deletions

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
+++ b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
@@ -70,6 +_,7 @@
return false;
} else {
mutableLong.setValue(time + 20L + level.getRandom().nextInt(20));
+ if (mob.getNavigation().isStuck()) mutableLong.add(200); // Paper - Perf: Wait an additional 10s to check again if they're stuck
PoiManager poiManager = level.getPoiManager();
map.long2ObjectEntrySet().removeIf(entry -> !entry.getValue().isStillValid(time));
Predicate<BlockPos> predicate1 = pos -> {

View File

@@ -0,0 +1,18 @@
--- a/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java
+++ b/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java
@@ -38,7 +_,14 @@
.findFirst()
)
.ifPresent(profession -> {
- villager.setVillagerData(villager.getVillagerData().setProfession(profession));
+ // CraftBukkit start - Fire VillagerCareerChangeEvent where Villager gets employed
+ org.bukkit.event.entity.VillagerCareerChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callVillagerCareerChangeEvent(villager, org.bukkit.craftbukkit.entity.CraftVillager.CraftProfession.minecraftToBukkit(profession), org.bukkit.event.entity.VillagerCareerChangeEvent.ChangeReason.EMPLOYED);
+ if (event.isCancelled()) {
+ return;
+ }
+
+ villager.setVillagerData(villager.getVillagerData().setProfession(org.bukkit.craftbukkit.entity.CraftVillager.CraftProfession.bukkitToMinecraft(event.getProfession())));
+ // CraftBukkit end
villager.refreshBrain(level);
});
return true;

View File

@@ -0,0 +1,23 @@
--- a/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java
+++ b/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java
@@ -24,8 +_,19 @@
if (!mob.isBaby()) {
return false;
} else {
- AgeableMob ageableMob = instance.get(nearestVisibleAdult);
+ LivingEntity ageableMob = instance.get(nearestVisibleAdult); // CraftBukkit - type
if (mob.closerThan(ageableMob, followRange.getMaxValue() + 1) && !mob.closerThan(ageableMob, followRange.getMinValue())) {
+ // CraftBukkit start
+ org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(mob, ageableMob, org.bukkit.event.entity.EntityTargetEvent.TargetReason.FOLLOW_LEADER);
+ if (event.isCancelled()) {
+ return false;
+ }
+ if (event.getTarget() == null) {
+ nearestVisibleAdult.erase();
+ return true;
+ }
+ ageableMob = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle();
+ // CraftBukkit end
WalkTarget walkTarget1 = new WalkTarget(
new EntityTracker(ageableMob, false), speedModifier.apply(mob), followRange.getMinValue() - 1
);

View File

@@ -0,0 +1,40 @@
--- a/net/minecraft/world/entity/ai/behavior/Behavior.java
+++ b/net/minecraft/world/entity/ai/behavior/Behavior.java
@@ -14,6 +_,9 @@
private long endTimestamp;
private final int minDuration;
private final int maxDuration;
+ // Paper start - configurable behavior tick rate and timings
+ private final String configKey;
+ // Paper end - configurable behavior tick rate and timings
public Behavior(Map<MemoryModuleType<?>, MemoryStatus> entryCondition) {
this(entryCondition, 60);
@@ -27,6 +_,14 @@
this.minDuration = minDuration;
this.maxDuration = maxDuration;
this.entryCondition = entryCondition;
+ // Paper start - configurable behavior tick rate and timings
+ String key = io.papermc.paper.util.MappingEnvironment.reobf() ? io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()) : this.getClass().getName();
+ int lastSeparator = key.lastIndexOf('.');
+ if (lastSeparator != -1) {
+ key = key.substring(lastSeparator + 1);
+ }
+ this.configKey = key.toLowerCase(java.util.Locale.ROOT);
+ // Paper end - configurable behavior tick rate and timings
}
@Override
@@ -36,6 +_,12 @@
@Override
public final boolean tryStart(ServerLevel level, E owner, long gameTime) {
+ // Paper start - configurable behavior tick rate and timings
+ int tickRate = java.util.Objects.requireNonNullElse(level.paperConfig().tickRates.behavior.get(owner.getType(), this.configKey), -1);
+ if (tickRate > -1 && gameTime < this.endTimestamp + tickRate) {
+ return false;
+ }
+ // Paper end - configurable behavior tick rate and timings
if (this.hasRequiredMemories(owner) && this.checkExtraStartConditions(level, owner)) {
this.status = Behavior.Status.RUNNING;
int i = this.minDuration + level.getRandom().nextInt(this.maxDuration + 1 - this.minDuration);

View File

@@ -0,0 +1,24 @@
--- a/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java
+++ b/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java
@@ -80,6 +_,7 @@
}
public static void throwItem(LivingEntity entity, ItemStack stack, Vec3 offset, Vec3 speedMultiplier, float yOffset) {
+ if (stack.isEmpty()) return; // CraftBukkit - SPIGOT-4940: no empty loot
double d = entity.getEyeY() - yOffset;
ItemEntity itemEntity = new ItemEntity(entity.level(), entity.getX(), d, entity.getZ(), stack);
itemEntity.setThrower(entity);
@@ -87,6 +_,13 @@
vec3 = vec3.normalize().multiply(speedMultiplier.x, speedMultiplier.y, speedMultiplier.z);
itemEntity.setDeltaMovement(vec3);
itemEntity.setDefaultPickUpDelay();
+ // CraftBukkit start
+ org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(entity.getBukkitEntity(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity());
+ itemEntity.level().getCraftServer().getPluginManager().callEvent(event);
+ if (event.isCancelled()) {
+ return;
+ }
+ // CraftBukkit end
entity.level().addFreshEntity(itemEntity);
}

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/entity/ai/behavior/GateBehavior.java
+++ b/net/minecraft/world/entity/ai/behavior/GateBehavior.java
@@ -18,7 +_,7 @@
private final Set<MemoryModuleType<?>> exitErasedMemories;
private final GateBehavior.OrderPolicy orderPolicy;
private final GateBehavior.RunningPolicy runningPolicy;
- private final ShufflingList<BehaviorControl<? super E>> behaviors = new ShufflingList<>();
+ private final ShufflingList<BehaviorControl<? super E>> behaviors = new ShufflingList<>(false); // Paper - Fix Concurrency issue in ShufflingList during worldgen
private Behavior.Status status = Behavior.Status.STOPPED;
public GateBehavior(

View File

@@ -0,0 +1,24 @@
--- a/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java
+++ b/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java
@@ -35,6 +_,21 @@
&& itemEntity.closerThan(entity, maxDistToWalk)
&& entity.level().getWorldBorder().isWithinBounds(itemEntity.blockPosition())
&& entity.canPickUpLoot()) {
+ // CraftBukkit start
+ if (entity instanceof net.minecraft.world.entity.animal.allay.Allay) {
+ org.bukkit.event.entity.EntityTargetEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetEvent(entity, itemEntity, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY);
+
+ if (event.isCancelled()) {
+ return false;
+ }
+ if (!(event.getTarget() instanceof org.bukkit.craftbukkit.entity.CraftItem)) { // Paper - only erase allay memory on non-item targets
+ nearestVisibleWantedItem.erase();
+ return false; // Paper - only erase allay memory on non-item targets
+ }
+
+ itemEntity = (ItemEntity) ((org.bukkit.craftbukkit.entity.CraftEntity) event.getTarget()).getHandle();
+ }
+ // CraftBukkit end
WalkTarget walkTarget1 = new WalkTarget(new EntityTracker(itemEntity, false), speedModifier, 0);
lookTarget.set(new EntityTracker(itemEntity, true));
walkTarget.set(walkTarget1);

View File

@@ -0,0 +1,24 @@
--- a/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
+++ b/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
@@ -110,7 +_,9 @@
Block block = blockState.getBlock();
Block block1 = level.getBlockState(this.aboveFarmlandPos.below()).getBlock();
if (block instanceof CropBlock && ((CropBlock)block).isMaxAge(blockState)) {
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(owner, this.aboveFarmlandPos, blockState.getFluidState().createLegacyBlock())) { // CraftBukkit // Paper - fix wrong block state
level.destroyBlock(this.aboveFarmlandPos, true, owner);
+ } // CraftBukkit
}
if (blockState.isAir() && block1 instanceof FarmBlock && owner.hasFarmSeeds()) {
@@ -121,9 +_,11 @@
boolean flag = false;
if (!item.isEmpty() && item.is(ItemTags.VILLAGER_PLANTABLE_SEEDS) && item.getItem() instanceof BlockItem blockItem) {
BlockState blockState1 = blockItem.getBlock().defaultBlockState();
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(owner, this.aboveFarmlandPos, blockState1)) { // CraftBukkit
level.setBlockAndUpdate(this.aboveFarmlandPos, blockState1);
level.gameEvent(GameEvent.BLOCK_PLACE, this.aboveFarmlandPos, GameEvent.Context.of(owner, blockState1));
flag = true;
+ } // CraftBukkit
}
if (flag) {

View File

@@ -0,0 +1,16 @@
--- a/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
+++ b/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
@@ -58,6 +_,13 @@
if (blockState.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock)) {
DoorBlock doorBlock = (DoorBlock)blockState.getBlock();
if (!doorBlock.isOpen(blockState)) {
+ // CraftBukkit start - entities opening doors
+ org.bukkit.event.entity.EntityInteractEvent event = new org.bukkit.event.entity.EntityInteractEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(entity.level(), blockPos));
+ entity.level().getCraftServer().getPluginManager().callEvent(event);
+ if (event.isCancelled()) {
+ return false;
+ }
+ // CraftBukkit end
doorBlock.setOpen(entity, level, blockState, blockPos, true);
}

View File

@@ -0,0 +1,27 @@
--- a/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java
+++ b/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java
@@ -10,6 +_,7 @@
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
@@ -75,6 +_,16 @@
.flatMap(
nearestVisibleLivingEntities -> nearestVisibleLivingEntities.findClosest(livingEntity -> this.ramTargeting.test(level, entity, livingEntity))
)
+ // CraftBukkit start
+ .map((entityliving) -> {
+ org.bukkit.event.entity.EntityTargetEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(entity, entityliving, (entityliving instanceof ServerPlayer) ? org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY);
+ if (event.isCancelled() || event.getTarget() == null) {
+ return null;
+ }
+ entityliving = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle();
+ return entityliving;
+ })
+ // CraftBukkit end
.ifPresent(entity1 -> this.chooseRamPosition(entity, entity1));
}

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/entity/ai/behavior/RamTarget.java
+++ b/net/minecraft/world/entity/ai/behavior/RamTarget.java
@@ -89,7 +_,7 @@
float f = 0.25F * (i - i1);
float f1 = Mth.clamp(owner.getSpeed() * 1.65F, 0.2F, 3.0F) + f;
float f2 = livingEntity.isDamageSourceBlocked(level.damageSources().mobAttack(owner)) ? 0.5F : 1.0F;
- livingEntity.knockback(f2 * f1 * this.getKnockbackForce.applyAsDouble(owner), this.ramDirection.x(), this.ramDirection.z());
+ livingEntity.knockback(f2 * f1 * this.getKnockbackForce.applyAsDouble(owner), this.ramDirection.x(), this.ramDirection.z(), owner, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
this.finishRam(level, owner);
level.playSound(null, owner, this.getImpactSound.apply(owner), SoundSource.NEUTRAL, 1.0F, 1.0F);
} else if (this.hasRammedHornBreakingBlock(level, owner)) {

View File

@@ -0,0 +1,18 @@
--- a/net/minecraft/world/entity/ai/behavior/ResetProfession.java
+++ b/net/minecraft/world/entity/ai/behavior/ResetProfession.java
@@ -18,7 +_,14 @@
&& villagerData.getProfession() != VillagerProfession.NITWIT
&& villager.getVillagerXp() == 0
&& villagerData.getLevel() <= 1) {
- villager.setVillagerData(villager.getVillagerData().setProfession(VillagerProfession.NONE));
+ // CraftBukkit start
+ org.bukkit.event.entity.VillagerCareerChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callVillagerCareerChangeEvent(villager, org.bukkit.craftbukkit.entity.CraftVillager.CraftProfession.minecraftToBukkit(VillagerProfession.NONE), org.bukkit.event.entity.VillagerCareerChangeEvent.ChangeReason.LOSING_JOB);
+ if (event.isCancelled()) {
+ return false;
+ }
+
+ villager.setVillagerData(villager.getVillagerData().setProfession(org.bukkit.craftbukkit.entity.CraftVillager.CraftProfession.bukkitToMinecraft(event.getProfession())));
+ // CraftBukkit end
villager.refreshBrain(level);
return true;
} else {

View File

@@ -0,0 +1,44 @@
--- a/net/minecraft/world/entity/ai/behavior/ShufflingList.java
+++ b/net/minecraft/world/entity/ai/behavior/ShufflingList.java
@@ -16,12 +_,25 @@
public class ShufflingList<U> implements Iterable<U> {
protected final List<ShufflingList.WeightedEntry<U>> entries;
private final RandomSource random = RandomSource.create();
+ private final boolean isUnsafe; // Paper - Fix Concurrency issue in ShufflingList during worldgen
public ShufflingList() {
+ // Paper start - Fix Concurrency issue in ShufflingList during worldgen
+ this(true);
+ }
+ public ShufflingList(boolean isUnsafe) {
+ this.isUnsafe = isUnsafe;
+ // Paper end - Fix Concurrency issue in ShufflingList during worldgen
this.entries = Lists.newArrayList();
}
private ShufflingList(List<ShufflingList.WeightedEntry<U>> entries) {
+ // Paper start - Fix Concurrency issue in ShufflingList during worldgen
+ this(entries, true);
+ }
+ private ShufflingList(List<ShufflingList.WeightedEntry<U>> entries, boolean isUnsafe) {
+ this.isUnsafe = isUnsafe;
+ // Paper end - Fix Concurrency issue in ShufflingList during worldgen
this.entries = Lists.newArrayList(entries);
}
@@ -35,9 +_,12 @@
}
public ShufflingList<U> shuffle() {
- this.entries.forEach(entry -> entry.setRandom(this.random.nextFloat()));
- this.entries.sort(Comparator.comparingDouble(ShufflingList.WeightedEntry::getRandWeight));
- return this;
+ // Paper start - Fix Concurrency issue in ShufflingList during worldgen
+ List<ShufflingList.WeightedEntry<U>> list = this.isUnsafe ? Lists.newArrayList(this.entries) : this.entries;
+ list.forEach(entry -> entry.setRandom(this.random.nextFloat()));
+ list.sort(Comparator.comparingDouble(ShufflingList.WeightedEntry::getRandWeight));
+ return this.isUnsafe ? new ShufflingList<>(list, this.isUnsafe) : this;
+ // Paper end - Fix Concurrency issue in ShufflingList during worldgen
}
public Stream<U> stream() {

View File

@@ -0,0 +1,12 @@
--- a/net/minecraft/world/entity/ai/behavior/SleepInBed.java
+++ b/net/minecraft/world/entity/ai/behavior/SleepInBed.java
@@ -42,7 +_,8 @@
}
}
- BlockState blockState = level.getBlockState(globalPos.pos());
+ BlockState blockState = level.getBlockStateIfLoaded(globalPos.pos()); // Paper - Prevent sync chunk loads when villagers try to find beds
+ if (blockState == null) { return false; } // Paper - Prevent sync chunk loads when villagers try to find beds
return globalPos.pos().closerToCenterThan(owner.position(), 2.0) && blockState.is(BlockTags.BEDS) && !blockState.getValue(BedBlock.OCCUPIED);
}
}

View File

@@ -0,0 +1,20 @@
--- a/net/minecraft/world/entity/ai/behavior/StartAttacking.java
+++ b/net/minecraft/world/entity/ai/behavior/StartAttacking.java
@@ -27,6 +_,17 @@
if (!entity.canAttack(livingEntity)) {
return false;
} else {
+ // CraftBukkit start
+ org.bukkit.event.entity.EntityTargetEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(entity, livingEntity, (livingEntity instanceof net.minecraft.server.level.ServerPlayer) ? org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY);
+ if (event.isCancelled()) {
+ return false;
+ }
+ if (event.getTarget() == null) {
+ memoryAccessor.erase();
+ return true;
+ }
+ livingEntity = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle();
+ // CraftBukkit end
memoryAccessor.set(livingEntity);
memoryAccessor1.erase();
return true;

View File

@@ -0,0 +1,33 @@
--- a/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java
+++ b/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java
@@ -40,6 +_,30 @@
&& !canStopAttacking.test(level, livingEntity)) {
return true;
} else {
+ // Paper start - better track target change reason
+ final org.bukkit.event.entity.EntityTargetEvent.TargetReason reason;
+ if (!entity.canAttack(livingEntity)) {
+ reason = org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_INVALID;
+ } else if (canGrowTiredOfTryingToReachTarget && StopAttackingIfTargetInvalid.isTiredOfTryingToReachTarget(entity, instance.tryGet(memoryAccessor1))) {
+ reason = org.bukkit.event.entity.EntityTargetEvent.TargetReason.FORGOT_TARGET;
+ } else if (!livingEntity.isAlive()) {
+ reason = org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_DIED;
+ } else if (livingEntity.level() != entity.level()) {
+ reason = org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_OTHER_LEVEL;
+ } else {
+ reason = org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_INVALID;
+ }
+ // Paper end
+ // CraftBukkit start
+ org.bukkit.event.entity.EntityTargetEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(entity, null, reason); // Paper
+ if (event.isCancelled()) {
+ return false;
+ }
+ if (event.getTarget() != null) {
+ entity.getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle());
+ return true;
+ }
+ // CraftBukkit end
onStopAttacking.accept(level, entity, livingEntity);
memoryAccessor.erase();
return true;

View File

@@ -0,0 +1,15 @@
--- a/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java
+++ b/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java
@@ -33,6 +_,12 @@
BlockPos blockPos2 = blockPos1.above();
if (level.getBlockState(blockPos2).isAir()) {
BlockState blockState = spawnBlock.defaultBlockState();
+ // CraftBukkit start
+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, blockPos2, blockState)) {
+ isPregnant.erase();
+ return true;
+ }
+ // CraftBukkit end
level.setBlock(blockPos2, blockState, 3);
level.gameEvent(GameEvent.BLOCK_PLACE, blockPos2, GameEvent.Context.of(entity, blockState));
level.playSound(null, entity, SoundEvents.FROG_LAY_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F);

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java
+++ b/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java
@@ -42,7 +_,7 @@
Pair.of(1, new MoveToTargetSink()),
Pair.of(2, PoiCompetitorScan.create()),
Pair.of(3, new LookAndFollowTradingPlayerSink(speedModifier)),
- Pair.of(5, GoToWantedItem.create(speedModifier, false, 4)),
+ Pair.of(5, GoToWantedItem.create(villager -> !villager.isSleeping(), speedModifier, false, 4)), // Paper - Fix MC-157464
Pair.of(
6,
AcquirePoi.create(

View File

@@ -0,0 +1,23 @@
--- a/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
+++ b/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
@@ -111,11 +_,17 @@
if (breedOffspring == null) {
return Optional.empty();
} else {
- parent.setAge(6000);
- partner.setAge(6000);
breedOffspring.setAge(-24000);
breedOffspring.moveTo(parent.getX(), parent.getY(), parent.getZ(), 0.0F, 0.0F);
- level.addFreshEntityWithPassengers(breedOffspring);
+ // CraftBukkit start - call EntityBreedEvent
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(breedOffspring, parent, partner, null, null, 0).isCancelled()) {
+ return Optional.empty();
+ }
+ // Move age setting down
+ parent.setAge(6000);
+ partner.setAge(6000);
+ level.addFreshEntityWithPassengers(breedOffspring, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING);
+ // CraftBukkit end
level.broadcastEntityEvent(breedOffspring, (byte)12);
return Optional.of(breedOffspring);
}

View File

@@ -0,0 +1,12 @@
--- a/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java
+++ b/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java
@@ -86,7 +_,9 @@
inventory.removeItemType(Items.WHEAT, i3);
ItemStack itemStack = inventory.addItem(new ItemStack(Items.BREAD, min));
if (!itemStack.isEmpty()) {
+ villager.forceDrops = true; // Paper - Add missing forceDrop toggle
villager.spawnAtLocation(level, itemStack, 0.5F);
+ villager.forceDrops = false; // Paper - Add missing forceDrop toggle
}
}
}