diff --git a/leaf-server/minecraft-patches/features/0280-Paper-PR-Optimise-temptation-lookups.patch b/leaf-server/minecraft-patches/features/0280-Paper-PR-Optimise-temptation-lookups.patch new file mode 100644 index 00000000..4dc5e4a4 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0280-Paper-PR-Optimise-temptation-lookups.patch @@ -0,0 +1,484 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: okx-code +Date: Thu, 6 Feb 2025 19:05:00 +0100 +Subject: [PATCH] Paper PR: Optimise temptation lookups + +Both TemptGoal and TemptingSensor recheck their validity each tick. +For this, they iterate all online players, checking their main and +offhand items against item tags. +This logic quickly explodes as each brain/goal selector queries all +players. + +This patch attempts to optimise this behaviour by lazily caching the +results of the non-entity specific checks in a single BitSet. +Such cache is valid for a single tick but can be re-used by each tempt +goal or sensor sharing the same non-entity specific selection +predicates. + +Original license: GPLv3 +Original project: https://github.com/PaperMC/Paper +Paper pull request: https://github.com/PaperMC/Paper/pull/11942 + +diff --git a/io/papermc/paper/entity/temptation/GlobalTemptationLookup.java b/io/papermc/paper/entity/temptation/GlobalTemptationLookup.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5f5cdfc538ba9aa6666c019df6706015234d7bae +--- /dev/null ++++ b/io/papermc/paper/entity/temptation/GlobalTemptationLookup.java +@@ -0,0 +1,91 @@ ++package io.papermc.paper.entity.temptation; ++ ++import java.util.ArrayList; ++import java.util.BitSet; ++import java.util.List; ++import java.util.function.Predicate; ++import net.minecraft.tags.ItemTags; ++import net.minecraft.world.entity.animal.HappyGhast; ++import net.minecraft.world.entity.animal.armadillo.ArmadilloAi; ++import net.minecraft.world.entity.animal.axolotl.AxolotlAi; ++import net.minecraft.world.entity.animal.camel.CamelAi; ++import net.minecraft.world.entity.animal.frog.FrogAi; ++import net.minecraft.world.entity.animal.goat.GoatAi; ++import net.minecraft.world.entity.animal.sniffer.SnifferAi; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.Items; ++ ++/** ++ * The tempt state lookup holds onto cached temptation flags of players in the world. ++ */ ++public class GlobalTemptationLookup { ++ private static int registeredPredicateCounter = 0; ++ ++ public static final TemptationPredicate BEE_FOOD = register(stack -> stack.is(ItemTags.BEE_FOOD)); ++ public static final TemptationPredicate CHICKEN_FOOD = register(stack -> stack.is(ItemTags.CHICKEN_FOOD)); ++ public static final TemptationPredicate COW_FOOD = register(stack -> stack.is(ItemTags.COW_FOOD)); ++ public static final TemptationPredicate PANDA_FOOD = register(stack -> stack.is(ItemTags.PANDA_FOOD)); ++ public static final TemptationPredicate PIG_CARROT_ON_A_STICK = register(stack -> stack.is(Items.CARROT_ON_A_STICK)); ++ public static final TemptationPredicate PIG = register(stack -> stack.is(ItemTags.PIG_FOOD)); ++ public static final TemptationPredicate RABBIT_FOOD = register(stack -> stack.is(ItemTags.RABBIT_FOOD)); ++ public static final TemptationPredicate SHEEP_FOOD = register(stack -> stack.is(ItemTags.SHEEP_FOOD)); ++ public static final TemptationPredicate TURTLE_FOOD = register(stack -> stack.is(ItemTags.TURTLE_FOOD)); ++ public static final TemptationPredicate HORSE_FOOD = register(stack -> stack.is(ItemTags.HORSE_TEMPT_ITEMS)); ++ public static final TemptationPredicate LLAMA_TEMPT_ITEMS = register(stack -> stack.is(ItemTags.LLAMA_TEMPT_ITEMS)); ++ public static final TemptationPredicate STRIDER_TEMPT_ITEMS = register(stack -> stack.is(ItemTags.STRIDER_TEMPT_ITEMS)); ++ public static final TemptationPredicate CAT_FOOD = register(stack -> stack.is(ItemTags.CAT_FOOD)); ++ public static final TemptationPredicate OCELOT_FOOD = register(itemStack -> itemStack.is(ItemTags.OCELOT_FOOD)); ++ public static final TemptationPredicate AXOLOTL_TEMPTATIONS = register(AxolotlAi.getTemptations()); ++ public static final TemptationPredicate GOAT_TEMPTATIONS = register(GoatAi.getTemptations()); ++ public static final TemptationPredicate FROG_TEMPTATIONS = register(FrogAi.getTemptations()); ++ public static final TemptationPredicate CAMEL_TEMPTATIONS = register(CamelAi.getTemptations()); ++ public static final TemptationPredicate ARMADILLO_TEMPTATIONS = register(ArmadilloAi.getTemptations()); ++ public static final TemptationPredicate SNIFFER_TEMPTATIONS = register(SnifferAi.getTemptations()); ++ public static final TemptationPredicate HAPPY_GHAST_TEMPTATIONS = register(HappyGhast.IS_FOOD); ++ ++ public record TemptationPredicate(int index, Predicate predicate) implements Predicate { ++ ++ @Override ++ public boolean test(final ItemStack itemStack) { ++ return this.predicate.test(itemStack); ++ } ++ } ++ ++ public static int indexFor(final Predicate predicate) { ++ return predicate instanceof final TemptationPredicate temptationPredicate ? temptationPredicate.index() : -1; ++ } ++ ++ private static TemptationPredicate register(final Predicate predicate) { ++ final TemptationPredicate val = new TemptationPredicate(registeredPredicateCounter, predicate); ++ registeredPredicateCounter++; ++ return val; ++ } ++ ++ private final List precalculatedTemptItems = new ArrayList<>(); ++ private final BitSet calculatedThisTick = new BitSet(); ++ ++ { ++ for (int i = 0; i < registeredPredicateCounter; i++) { ++ this.precalculatedTemptItems.add(new BitSet()); ++ } ++ } ++ ++ public void reset() { ++ for (int i = 0; i < registeredPredicateCounter; i++) { ++ this.precalculatedTemptItems.get(i).clear(); ++ } ++ this.calculatedThisTick.clear(); ++ } ++ ++ public boolean isCalculated(final int index) { ++ return this.calculatedThisTick.get(index); ++ } ++ ++ public void setCalculated(final int index) { ++ this.calculatedThisTick.set(index); ++ } ++ ++ public BitSet getBitSet(final int index) { ++ return this.precalculatedTemptItems.get(index); ++ } ++} +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index a474a8bdda00c1dbea30f0981e38ea6188b84c7a..f839c1b040f51e536b575ed5906f85a0a374b37f 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -936,6 +936,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR ++ this.globalTemptationLookup.reset(); // Paper - optimise temptation lookups - reset global cache prior to next entity tick + + if (org.dreeam.leaf.config.modules.opt.OptimizeDespawn.enabled && tickRateManager.runsNormally()) { despawnMap.tick(this, this.entityTickList); } // Leaf - optimize despawn + this.entityTickList +@@ -3038,4 +3039,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.lagCompensationTick = (System.nanoTime() - MinecraftServer.SERVER_INIT) / (java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(50L)); + } + // Paper end - lag compensation ++ ++ // Paper start - optimise temptation lookups ++ private final io.papermc.paper.entity.temptation.GlobalTemptationLookup globalTemptationLookup = new io.papermc.paper.entity.temptation.GlobalTemptationLookup(); // Paper - Optimise TemptGoal ++ public final io.papermc.paper.entity.temptation.GlobalTemptationLookup getTemptGoalLookup() { ++ return globalTemptationLookup; ++ } ++ // Paper end - optimise temptation lookups + } +diff --git a/net/minecraft/world/entity/ai/goal/TemptGoal.java b/net/minecraft/world/entity/ai/goal/TemptGoal.java +index a805c9426630c2c46db9d0dd536f1d16769395d3..0c9a355344463ea85a3b724d22d53638b1e42394 100644 +--- a/net/minecraft/world/entity/ai/goal/TemptGoal.java ++++ b/net/minecraft/world/entity/ai/goal/TemptGoal.java +@@ -30,6 +30,7 @@ public class TemptGoal extends Goal { + private final Predicate items; + private final boolean canScare; + private final double stopDistance; ++ private final int globalTemptationLookupIndex; // Paper - optimise temptation checks + + public TemptGoal(PathfinderMob mob, double speedModifier, Predicate items, boolean canScare) { + this((Mob)mob, speedModifier, items, canScare, 2.5); +@@ -40,13 +41,14 @@ public class TemptGoal extends Goal { + } + + TemptGoal(Mob mob, double speedModifier, Predicate items, boolean canScare, double stopDistance) { ++ this.globalTemptationLookupIndex = io.papermc.paper.entity.temptation.GlobalTemptationLookup.indexFor(items); // Paper - optimise temptation checks + this.mob = mob; + this.speedModifier = speedModifier; + this.items = items; + this.canScare = canScare; + this.stopDistance = stopDistance; + this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK)); +- this.targetingConditions = TEMPT_TARGETING.copy().selector((entity, level) -> this.shouldFollow(entity)); ++ this.targetingConditions = globalTemptationLookupIndex >= 0 ? TEMPT_TARGETING.copy() : TEMPT_TARGETING.copy().selector((entity, level) -> this.shouldFollow(entity)); // Paper - optimise temptation checks - skip selector if we have a lookup index. + } + + @Override +@@ -55,8 +57,41 @@ public class TemptGoal extends Goal { + this.calmDown--; + return false; + } else { ++ // Paper start - optimise temptation lookups ++ final TargetingConditions rangeTargetingConditions = this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)); ++ ++ if (this.globalTemptationLookupIndex != -1) { ++ final net.minecraft.server.level.ServerLevel level = getServerLevel(this.mob); ++ final io.papermc.paper.entity.temptation.GlobalTemptationLookup lookup = level.getTemptGoalLookup(); ++ final java.util.BitSet lookupBitSet = lookup.getBitSet(this.globalTemptationLookupIndex); ++ final java.util.List players = level.players(); ++ // Check if the lookup needs to be computed this tick. Do so for all players if needed. ++ if (!lookup.isCalculated(this.globalTemptationLookupIndex)) { ++ for (int i = 0; i < players.size(); i++) { ++ lookupBitSet.set(i, shouldFollow(players.get(i))); ++ } ++ lookup.setCalculated(this.globalTemptationLookupIndex); ++ } ++ double d = -1.0; ++ net.minecraft.server.level.ServerPlayer nearestPlayer = null; ++ // Only iterate over players that passed #shouldFollow either in the prior computation or another goals canUse check. ++ for (int i = lookupBitSet.nextSetBit(0); i >= 0; i = lookupBitSet.nextSetBit(i + 1)) { ++ final net.minecraft.server.level.ServerPlayer player = players.get(i); ++ if (rangeTargetingConditions.test(level, this.mob, player)) { ++ final double d1 = player.distanceToSqr(this.mob.getX(), this.mob.getY(), this.mob.getZ()); ++ if (d == -1.0 || d1 < d) { ++ d = d1; ++ nearestPlayer = player; ++ } ++ } ++ } ++ this.player = nearestPlayer; ++ } else { ++ // Default case for non-optimized / non vanilla tempt goal predicates. ++ // Paper end - optimise temptation lookups + this.player = getServerLevel(this.mob) + .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); ++ } // Paper - optimise temptation lookups + // CraftBukkit start + if (this.player != null) { + org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT); +diff --git a/net/minecraft/world/entity/ai/sensing/SensorType.java b/net/minecraft/world/entity/ai/sensing/SensorType.java +index 817bcaa1c09d9bd0b9856d06a8969ef583de6778..884398702e7810490743bbe74aa42fac9c661147 100644 +--- a/net/minecraft/world/entity/ai/sensing/SensorType.java ++++ b/net/minecraft/world/entity/ai/sensing/SensorType.java +@@ -39,18 +39,18 @@ public class SensorType> { + public static final SensorType NEAREST_ADULT = register("nearest_adult", AdultSensor::new); + public static final SensorType NEAREST_ADULT_ANY_TYPE = register("nearest_adult_any_type", AdultSensorAnyType::new); + public static final SensorType AXOLOTL_ATTACKABLES = register("axolotl_attackables", AxolotlAttackablesSensor::new); +- public static final SensorType AXOLOTL_TEMPTATIONS = register("axolotl_temptations", () -> new TemptingSensor(AxolotlAi.getTemptations())); +- public static final SensorType GOAT_TEMPTATIONS = register("goat_temptations", () -> new TemptingSensor(GoatAi.getTemptations())); +- public static final SensorType FROG_TEMPTATIONS = register("frog_temptations", () -> new TemptingSensor(FrogAi.getTemptations())); +- public static final SensorType CAMEL_TEMPTATIONS = register("camel_temptations", () -> new TemptingSensor(CamelAi.getTemptations())); ++ public static final SensorType AXOLOTL_TEMPTATIONS = register("axolotl_temptations", () -> new TemptingSensor(io.papermc.paper.entity.temptation.GlobalTemptationLookup.AXOLOTL_TEMPTATIONS)); // Paper - optimise temptation lookups ++ public static final SensorType GOAT_TEMPTATIONS = register("goat_temptations", () -> new TemptingSensor(io.papermc.paper.entity.temptation.GlobalTemptationLookup.GOAT_TEMPTATIONS)); // Paper - optimise temptation lookups ++ public static final SensorType FROG_TEMPTATIONS = register("frog_temptations", () -> new TemptingSensor(io.papermc.paper.entity.temptation.GlobalTemptationLookup.FROG_TEMPTATIONS)); // Paper - optimise temptation lookups ++ public static final SensorType CAMEL_TEMPTATIONS = register("camel_temptations", () -> new TemptingSensor(io.papermc.paper.entity.temptation.GlobalTemptationLookup.CAMEL_TEMPTATIONS)); // Paper - optimise temptation lookups + public static final SensorType ARMADILLO_TEMPTATIONS = register( +- "armadillo_temptations", () -> new TemptingSensor(ArmadilloAi.getTemptations()) ++ "armadillo_temptations", () -> new TemptingSensor(io.papermc.paper.entity.temptation.GlobalTemptationLookup.ARMADILLO_TEMPTATIONS) // Paper - optimise temptation lookups + ); +- public static final SensorType HAPPY_GHAST_TEMPTATIONS = register("happy_ghast_temptations", () -> new TemptingSensor(HappyGhast.IS_FOOD)); ++ public static final SensorType HAPPY_GHAST_TEMPTATIONS = register("happy_ghast_temptations", () -> new TemptingSensor(io.papermc.paper.entity.temptation.GlobalTemptationLookup.HAPPY_GHAST_TEMPTATIONS)); // Paper - optimise temptation lookups + public static final SensorType FROG_ATTACKABLES = register("frog_attackables", FrogAttackablesSensor::new); + public static final SensorType IS_IN_WATER = register("is_in_water", IsInWaterSensor::new); + public static final SensorType WARDEN_ENTITY_SENSOR = register("warden_entity_sensor", WardenEntitySensor::new); +- public static final SensorType SNIFFER_TEMPTATIONS = register("sniffer_temptations", () -> new TemptingSensor(SnifferAi.getTemptations())); ++ public static final SensorType SNIFFER_TEMPTATIONS = register("sniffer_temptations", () -> new TemptingSensor(io.papermc.paper.entity.temptation.GlobalTemptationLookup.SNIFFER_TEMPTATIONS)); // Paper - optimise temptation lookups + public static final SensorType BREEZE_ATTACK_ENTITY_SENSOR = register( + "breeze_attack_entity_sensor", BreezeAttackEntitySensor::new + ); +diff --git a/net/minecraft/world/entity/ai/sensing/TemptingSensor.java b/net/minecraft/world/entity/ai/sensing/TemptingSensor.java +index 5919192968d7272bbf119f248def7e96a1ea359b..a88b644c7480bfa66526c697455771eb7f86ad67 100644 +--- a/net/minecraft/world/entity/ai/sensing/TemptingSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/TemptingSensor.java +@@ -19,8 +19,10 @@ import net.minecraft.world.item.ItemStack; + public class TemptingSensor extends Sensor { + private static final TargetingConditions TEMPT_TARGETING = TargetingConditions.forNonCombat().ignoreLineOfSight(); + private final Predicate temptations; ++ private final int globalTemptationLookupIndex; // Paper - optimise temptation lookups + + public TemptingSensor(Predicate temptations) { ++ this.globalTemptationLookupIndex = io.papermc.paper.entity.temptation.GlobalTemptationLookup.indexFor(temptations); // Paper - optimise temptation lookups + this.temptations = temptations; + } + +@@ -28,6 +30,38 @@ public class TemptingSensor extends Sensor { + protected void doTick(ServerLevel level, PathfinderMob entity) { + Brain brain = entity.getBrain(); + TargetingConditions targetingConditions = TEMPT_TARGETING.copy().range((float)entity.getAttributeValue(Attributes.TEMPT_RANGE)); ++ // Paper start - optimise temptation lookups - on update, ensure below diff filters correctly ++ Player targetPlayer; ++ if (this.globalTemptationLookupIndex != -1) { ++ final io.papermc.paper.entity.temptation.GlobalTemptationLookup lookup = level.getTemptGoalLookup(); ++ final java.util.BitSet lookupBitSet = lookup.getBitSet(this.globalTemptationLookupIndex); ++ final java.util.List players = level.players(); ++ // Check if the lookup needs to be computed this tick. Do so for all players if needed. ++ if (!lookup.isCalculated(this.globalTemptationLookupIndex)) { ++ for (int i = 0; i < players.size(); i++) { ++ final net.minecraft.server.level.ServerPlayer serverPlayer = players.get(i); ++ lookupBitSet.set(i, net.minecraft.world.entity.EntitySelector.NO_SPECTATORS.test(serverPlayer) && this.playerHoldingTemptation(serverPlayer)); // check on update ++ } ++ lookup.setCalculated(this.globalTemptationLookupIndex); ++ } ++ double d = -1.0; ++ net.minecraft.server.level.ServerPlayer nearestPlayer = null; ++ // Only iterate over players that passed #shouldFollow either in the prior computation or another goals canUse check. ++ for (int i = lookupBitSet.nextSetBit(0); i >= 0; i = lookupBitSet.nextSetBit(i + 1)) { ++ final net.minecraft.server.level.ServerPlayer player = players.get(i); ++ if (targetingConditions.test(level, entity, player) && !entity.hasPassenger(player)) { // check on update - consider non passengers ++ final double d1 = player.distanceToSqr(entity.getX(), entity.getY(), entity.getZ()); ++ if (d == -1.0 || d1 < d) { ++ d = d1; ++ nearestPlayer = player; ++ } ++ } ++ } ++ targetPlayer = nearestPlayer; ++ } else { ++ // Default case for non-optimized / non vanilla tempt goal predicates. Sorting the entire list is completely useless, but none of the vanilla logic uses this path now so ++ // less diff and easier for updates. ++ // Paper end - optimise temptation lookups + // Leaf start - Remove streams in TemptingSensor + List allPlayers = level.players(); + List list = new java.util.ArrayList<>(); +@@ -40,10 +74,12 @@ public class TemptingSensor extends Sensor { + } + } + // Leaf end - Remove streams in TemptingSensor +- +- if (!list.isEmpty()) { +- list.sort(Comparator.comparingDouble(entity::distanceToSqr)); // Leaf - Remove streams in TemptingSensor +- Player player = list.get(0); ++ // Paper start - optimise temptation lookups ++ targetPlayer = list.isEmpty() ? null : list.getFirst(); ++ } ++ if (targetPlayer != null) { ++ Player player = targetPlayer; ++ // Paper end - optimise temptation lookups + // CraftBukkit start + org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent( + entity, player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT +diff --git a/net/minecraft/world/entity/animal/AbstractCow.java b/net/minecraft/world/entity/animal/AbstractCow.java +index 61e7300bbf272398b2faebf5e537d9c2ddedc6d6..832a9a56fbf870e4242d4a59ab58ed43881fdccd 100644 +--- a/net/minecraft/world/entity/animal/AbstractCow.java ++++ b/net/minecraft/world/entity/animal/AbstractCow.java +@@ -40,7 +40,7 @@ public abstract class AbstractCow extends Animal { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(1, new PanicGoal(this, 2.0)); + this.goalSelector.addGoal(2, new BreedGoal(this, 1.0)); +- this.goalSelector.addGoal(3, new TemptGoal(this, 1.25, itemStack -> level().purpurConfig.cowFeedMushrooms > 0 && (itemStack.is(net.minecraft.world.level.block.Blocks.RED_MUSHROOM.asItem()) || itemStack.is(net.minecraft.world.level.block.Blocks.BROWN_MUSHROOM.asItem())) || itemStack.is(ItemTags.COW_FOOD), false)); // Purpur - Cows eat mushrooms ++ this.goalSelector.addGoal(3, new TemptGoal(this, 1.25, io.papermc.paper.entity.temptation.GlobalTemptationLookup.COW_FOOD, false)); // Paper - optimise temptation lookups + this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25)); + this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0)); + this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); +diff --git a/net/minecraft/world/entity/animal/Bee.java b/net/minecraft/world/entity/animal/Bee.java +index eadc64f497631c048cf6a7d72e6f692763c98c6c..fb3123dc9fb8a49d2ef85bcf622a59f3cd591a19 100644 +--- a/net/minecraft/world/entity/animal/Bee.java ++++ b/net/minecraft/world/entity/animal/Bee.java +@@ -240,7 +240,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + this.goalSelector.addGoal(0, new Bee.BeeAttackGoal(this, 1.4F, true)); + this.goalSelector.addGoal(1, new Bee.BeeEnterHiveGoal()); + this.goalSelector.addGoal(2, new BreedGoal(this, 1.0)); +- this.goalSelector.addGoal(3, new TemptGoal(this, 1.25, stack -> stack.is(ItemTags.BEE_FOOD), false)); ++ this.goalSelector.addGoal(3, new TemptGoal(this, 1.25, io.papermc.paper.entity.temptation.GlobalTemptationLookup.BEE_FOOD, false)); // Paper - optimise temptation lookups + this.goalSelector.addGoal(3, new Bee.ValidateHiveGoal()); + this.goalSelector.addGoal(3, new Bee.ValidateFlowerGoal()); + this.beePollinateGoal = new Bee.BeePollinateGoal(); +diff --git a/net/minecraft/world/entity/animal/Cat.java b/net/minecraft/world/entity/animal/Cat.java +index 14dfac12438b02ca9172a494a5730b5bf094aef1..4c0292f725845bb30782fbb6297f0e943945480c 100644 +--- a/net/minecraft/world/entity/animal/Cat.java ++++ b/net/minecraft/world/entity/animal/Cat.java +@@ -148,7 +148,7 @@ public class Cat extends TamableAnimal { + + @Override + protected void registerGoals() { +- this.temptGoal = new Cat.CatTemptGoal(this, 0.6, stack -> stack.is(ItemTags.CAT_FOOD), true); ++ this.temptGoal = new Cat.CatTemptGoal(this, 0.6, io.papermc.paper.entity.temptation.GlobalTemptationLookup.CAT_FOOD, true); // Paper - optimise temptation lookups + this.goalSelector.addGoal(1, new FloatGoal(this)); + this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(1, new TamableAnimal.TamableAnimalPanicGoal(1.5)); +diff --git a/net/minecraft/world/entity/animal/Chicken.java b/net/minecraft/world/entity/animal/Chicken.java +index f908d57776c533b6d42b8f3cd4da088a7ec03ed1..a5821ab8eab48fc2cd29eba857c9a624eb59290d 100644 +--- a/net/minecraft/world/entity/animal/Chicken.java ++++ b/net/minecraft/world/entity/animal/Chicken.java +@@ -129,7 +129,7 @@ public class Chicken extends Animal { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + //this.goalSelector.addGoal(1, new PanicGoal(this, 1.4)); // Purpur - Chickens can retaliate - moved down + this.goalSelector.addGoal(2, new BreedGoal(this, 1.0)); +- this.goalSelector.addGoal(3, new TemptGoal(this, 1.0, itemStack -> itemStack.is(ItemTags.CHICKEN_FOOD), false)); ++ this.goalSelector.addGoal(3, new TemptGoal(this, 1.0, io.papermc.paper.entity.temptation.GlobalTemptationLookup.CHICKEN_FOOD, false)); // Paper - optimise temptation lookups + this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.1)); + this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0)); + this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); +diff --git a/net/minecraft/world/entity/animal/Ocelot.java b/net/minecraft/world/entity/animal/Ocelot.java +index c83d47caf8572e7f1375829f7dcb1b3b4bbf9ed0..b30cdff64f6608f20dacf36d781b00a7ba55f093 100644 +--- a/net/minecraft/world/entity/animal/Ocelot.java ++++ b/net/minecraft/world/entity/animal/Ocelot.java +@@ -139,7 +139,7 @@ public class Ocelot extends Animal { + + @Override + protected void registerGoals() { +- this.temptGoal = new Ocelot.OcelotTemptGoal(this, 0.6, itemStack -> itemStack.is(ItemTags.OCELOT_FOOD), true); ++ this.temptGoal = new Ocelot.OcelotTemptGoal(this, 0.6, io.papermc.paper.entity.temptation.GlobalTemptationLookup.OCELOT_FOOD, true); // Paper - optimise temptation lookups + this.goalSelector.addGoal(1, new FloatGoal(this)); + this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(3, this.temptGoal); +diff --git a/net/minecraft/world/entity/animal/Panda.java b/net/minecraft/world/entity/animal/Panda.java +index c4cfcab5be629e83c0418bcb9ef1062bc8459af9..92069cde887889e1fcb35d597445d6102512756a 100644 +--- a/net/minecraft/world/entity/animal/Panda.java ++++ b/net/minecraft/world/entity/animal/Panda.java +@@ -319,7 +319,7 @@ public class Panda extends Animal { + this.goalSelector.addGoal(2, new Panda.PandaPanicGoal(this, 2.0)); + this.goalSelector.addGoal(2, new Panda.PandaBreedGoal(this, 1.0)); + this.goalSelector.addGoal(3, new Panda.PandaAttackGoal(this, 1.2F, true)); +- this.goalSelector.addGoal(4, new TemptGoal(this, 1.0, stack -> stack.is(ItemTags.PANDA_FOOD), false)); ++ this.goalSelector.addGoal(4, new TemptGoal(this, 1.0, io.papermc.paper.entity.temptation.GlobalTemptationLookup.PANDA_FOOD, false)); // Paper - optimise temptation lookups + this.goalSelector.addGoal(6, new Panda.PandaAvoidGoal<>(this, Player.class, 8.0F, 2.0, 2.0)); + this.goalSelector.addGoal(6, new Panda.PandaAvoidGoal<>(this, Monster.class, 4.0F, 2.0, 2.0)); + this.goalSelector.addGoal(7, new Panda.PandaSitGoal()); +diff --git a/net/minecraft/world/entity/animal/Pig.java b/net/minecraft/world/entity/animal/Pig.java +index fbda6b378f9fc8fe97f4282326f86ea9633a5c98..a5ad5c232c778baa76ce023daa497e8dce3aadf2 100644 +--- a/net/minecraft/world/entity/animal/Pig.java ++++ b/net/minecraft/world/entity/animal/Pig.java +@@ -118,8 +118,8 @@ public class Pig extends Animal implements ItemSteerable { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(1, new PanicGoal(this, 1.25)); + this.goalSelector.addGoal(3, new BreedGoal(this, 1.0)); +- this.goalSelector.addGoal(4, new TemptGoal(this, 1.2, itemStack -> itemStack.is(Items.CARROT_ON_A_STICK), false)); +- this.goalSelector.addGoal(4, new TemptGoal(this, 1.2, itemStack -> itemStack.is(ItemTags.PIG_FOOD), false)); ++ this.goalSelector.addGoal(4, new TemptGoal(this, 1.2, io.papermc.paper.entity.temptation.GlobalTemptationLookup.PIG_CARROT_ON_A_STICK, false)); // Paper - optimise temptation lookups ++ this.goalSelector.addGoal(4, new TemptGoal(this, 1.2, io.papermc.paper.entity.temptation.GlobalTemptationLookup.PIG, false)); // Paper - optimise temptation lookups + this.goalSelector.addGoal(5, new FollowParentGoal(this, 1.1)); + this.goalSelector.addGoal(6, new WaterAvoidingRandomStrollGoal(this, 1.0)); + this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 6.0F)); +diff --git a/net/minecraft/world/entity/animal/Rabbit.java b/net/minecraft/world/entity/animal/Rabbit.java +index c0a63a8d1a3c33d9d151aaf3131ea583f4f48588..311dea7e2231e00ff439220a47dd4449b85d0d50 100644 +--- a/net/minecraft/world/entity/animal/Rabbit.java ++++ b/net/minecraft/world/entity/animal/Rabbit.java +@@ -181,7 +181,7 @@ public class Rabbit extends Animal { + this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level())); + this.goalSelector.addGoal(1, new Rabbit.RabbitPanicGoal(this, 2.2)); + this.goalSelector.addGoal(2, new BreedGoal(this, 0.8)); +- this.goalSelector.addGoal(3, new TemptGoal(this, 1.0, stack -> stack.is(ItemTags.RABBIT_FOOD), false)); ++ this.goalSelector.addGoal(3, new TemptGoal(this, 1.0, io.papermc.paper.entity.temptation.GlobalTemptationLookup.RABBIT_FOOD, false)); // Paper - optimise temptation lookups + this.goalSelector.addGoal(4, new Rabbit.RabbitAvoidEntityGoal<>(this, Player.class, 8.0F, 2.2, 2.2)); + this.goalSelector.addGoal(4, new Rabbit.RabbitAvoidEntityGoal<>(this, Wolf.class, 10.0F, 2.2, 2.2)); + this.goalSelector.addGoal(4, new Rabbit.RabbitAvoidEntityGoal<>(this, Monster.class, 4.0F, 2.2, 2.2)); +diff --git a/net/minecraft/world/entity/animal/Turtle.java b/net/minecraft/world/entity/animal/Turtle.java +index 0065a43871605c8d2a8b29d1aed174aeed8be3b2..59800c502c46c82cedde0f67e3848d7ba02cc479 100644 +--- a/net/minecraft/world/entity/animal/Turtle.java ++++ b/net/minecraft/world/entity/animal/Turtle.java +@@ -195,7 +195,7 @@ public class Turtle extends Animal { + this.goalSelector.addGoal(0, new Turtle.TurtlePanicGoal(this, 1.2)); + this.goalSelector.addGoal(1, new Turtle.TurtleBreedGoal(this, 1.0)); + this.goalSelector.addGoal(1, new Turtle.TurtleLayEggGoal(this, 1.0)); +- this.goalSelector.addGoal(2, new TemptGoal(this, 1.1, itemStack -> itemStack.is(ItemTags.TURTLE_FOOD), false)); ++ this.goalSelector.addGoal(2, new TemptGoal(this, 1.1, io.papermc.paper.entity.temptation.GlobalTemptationLookup.TURTLE_FOOD, false)); // Paper - optimise temptation lookups + this.goalSelector.addGoal(3, new Turtle.TurtleGoToWaterGoal(this, 1.0)); + this.goalSelector.addGoal(4, new Turtle.TurtleGoHomeGoal(this, 1.0)); + this.goalSelector.addGoal(7, new Turtle.TurtleTravelGoal(this, 1.0)); +diff --git a/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/net/minecraft/world/entity/animal/horse/AbstractHorse.java +index d3b21d7c54349045c29df09602bda9d7a7eaec52..3ce7e02b91d40170e040554cd601167b66ba602a 100644 +--- a/net/minecraft/world/entity/animal/horse/AbstractHorse.java ++++ b/net/minecraft/world/entity/animal/horse/AbstractHorse.java +@@ -199,7 +199,7 @@ public abstract class AbstractHorse extends Animal implements HasCustomInventory + + protected void addBehaviourGoals() { + this.goalSelector.addGoal(0, new FloatGoal(this)); +- this.goalSelector.addGoal(3, new TemptGoal(this, 1.25, stack -> stack.is(ItemTags.HORSE_TEMPT_ITEMS), false)); ++ this.goalSelector.addGoal(3, new TemptGoal(this, 1.25, io.papermc.paper.entity.temptation.GlobalTemptationLookup.HORSE_FOOD, false)); // Paper - optimise temptation lookups + } + + @Override +diff --git a/net/minecraft/world/entity/animal/horse/Llama.java b/net/minecraft/world/entity/animal/horse/Llama.java +index 455a84ffa16152137409777e1fddbab5a21bd57d..36a0dcf58dd2e7c014c2d2924941a3773f3bd3a4 100644 +--- a/net/minecraft/world/entity/animal/horse/Llama.java ++++ b/net/minecraft/world/entity/animal/horse/Llama.java +@@ -222,7 +222,7 @@ public class Llama extends AbstractChestedHorse implements RangedAttackMob { + this.goalSelector.addGoal(3, new RangedAttackGoal(this, 1.25, 40, 20.0F)); + this.goalSelector.addGoal(3, new PanicGoal(this, 1.2)); + this.goalSelector.addGoal(4, new BreedGoal(this, 1.0)); +- this.goalSelector.addGoal(5, new TemptGoal(this, 1.25, itemStack -> itemStack.is(ItemTags.LLAMA_TEMPT_ITEMS), false)); ++ this.goalSelector.addGoal(5, new TemptGoal(this, 1.25, io.papermc.paper.entity.temptation.GlobalTemptationLookup.LLAMA_TEMPT_ITEMS, false)); // Paper - optimise temptation lookups + this.goalSelector.addGoal(6, new FollowParentGoal(this, 1.0)); + this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 0.7)); + this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 6.0F)); +diff --git a/net/minecraft/world/entity/animal/sheep/Sheep.java b/net/minecraft/world/entity/animal/sheep/Sheep.java +index d0b40c1186328b501e1847f29283023b81eeddc3..96a71acbed44df67c7d0da36b601c8e54159fd3f 100644 +--- a/net/minecraft/world/entity/animal/sheep/Sheep.java ++++ b/net/minecraft/world/entity/animal/sheep/Sheep.java +@@ -115,7 +115,7 @@ public class Sheep extends Animal implements Shearable { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(1, new PanicGoal(this, 1.25)); + this.goalSelector.addGoal(2, new BreedGoal(this, 1.0)); +- this.goalSelector.addGoal(3, new TemptGoal(this, 1.1, stack -> stack.is(ItemTags.SHEEP_FOOD), false)); ++ this.goalSelector.addGoal(3, new TemptGoal(this, 1.1, io.papermc.paper.entity.temptation.GlobalTemptationLookup.SHEEP_FOOD, false)); // Paper - optimise temptation lookups + this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.1)); + this.goalSelector.addGoal(5, this.eatBlockGoal); + this.goalSelector.addGoal(6, new WaterAvoidingRandomStrollGoal(this, 1.0)); +diff --git a/net/minecraft/world/entity/monster/Strider.java b/net/minecraft/world/entity/monster/Strider.java +index 4a4f3ed5dbc6b1934b2901c3a21597ba783b1806..8fcdc4452360a1909b788eda8fe2509b0b4f6de8 100644 +--- a/net/minecraft/world/entity/monster/Strider.java ++++ b/net/minecraft/world/entity/monster/Strider.java +@@ -179,7 +179,7 @@ public class Strider extends Animal implements ItemSteerable { + this.goalSelector.addGoal(1, new PanicGoal(this, 1.65)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(2, new BreedGoal(this, 1.0)); +- this.temptGoal = new TemptGoal(this, 1.4, itemStack -> itemStack.is(ItemTags.STRIDER_TEMPT_ITEMS), false); ++ this.temptGoal = new TemptGoal(this, 1.4, io.papermc.paper.entity.temptation.GlobalTemptationLookup.STRIDER_TEMPT_ITEMS, false); // Paper - optimise temptation lookups + this.goalSelector.addGoal(3, this.temptGoal); + this.goalSelector.addGoal(4, new Strider.StriderGoToLavaGoal(this, 1.0)); + this.goalSelector.addGoal(5, new FollowParentGoal(this, 1.0)); diff --git a/leaf-server/minecraft-patches/features/0281-fix-temptation-lookups.patch b/leaf-server/minecraft-patches/features/0281-fix-temptation-lookups.patch new file mode 100644 index 00000000..760b8989 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0281-fix-temptation-lookups.patch @@ -0,0 +1,182 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hayanesuru +Date: Fri, 15 Aug 2025 16:16:08 +0900 +Subject: [PATCH] fix temptation lookups + + +diff --git a/io/papermc/paper/entity/temptation/GlobalTemptationLookup.java b/io/papermc/paper/entity/temptation/GlobalTemptationLookup.java +index 5f5cdfc538ba9aa6666c019df6706015234d7bae..e7c0fd59b4b64b3e60887ddae63e2f1a4ddd752f 100644 +--- a/io/papermc/paper/entity/temptation/GlobalTemptationLookup.java ++++ b/io/papermc/paper/entity/temptation/GlobalTemptationLookup.java +@@ -1,9 +1,12 @@ + package io.papermc.paper.entity.temptation; + +-import java.util.ArrayList; + import java.util.BitSet; + import java.util.List; + import java.util.function.Predicate; ++ ++import it.unimi.dsi.fastutil.objects.ObjectArrays; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.tags.ItemTags; + import net.minecraft.world.entity.animal.HappyGhast; + import net.minecraft.world.entity.animal.armadillo.ArmadilloAi; +@@ -24,6 +27,7 @@ public class GlobalTemptationLookup { + public static final TemptationPredicate BEE_FOOD = register(stack -> stack.is(ItemTags.BEE_FOOD)); + public static final TemptationPredicate CHICKEN_FOOD = register(stack -> stack.is(ItemTags.CHICKEN_FOOD)); + public static final TemptationPredicate COW_FOOD = register(stack -> stack.is(ItemTags.COW_FOOD)); ++ public static final TemptationPredicate COW_FOOD_MUSHROOM = register(stack -> stack.is(Items.RED_MUSHROOM) || stack.is(Items.BROWN_MUSHROOM) || stack.is(ItemTags.COW_FOOD)); + public static final TemptationPredicate PANDA_FOOD = register(stack -> stack.is(ItemTags.PANDA_FOOD)); + public static final TemptationPredicate PIG_CARROT_ON_A_STICK = register(stack -> stack.is(Items.CARROT_ON_A_STICK)); + public static final TemptationPredicate PIG = register(stack -> stack.is(ItemTags.PIG_FOOD)); +@@ -61,20 +65,34 @@ public class GlobalTemptationLookup { + return val; + } + +- private final List precalculatedTemptItems = new ArrayList<>(); ++ private final BitSet[] precalculatedTemptItems; + private final BitSet calculatedThisTick = new BitSet(); ++ private static final ServerPlayer[] EMPTY_PLAYERS = {}; ++ private ServerPlayer[] players = EMPTY_PLAYERS; + + { +- for (int i = 0; i < registeredPredicateCounter; i++) { +- this.precalculatedTemptItems.add(new BitSet()); ++ precalculatedTemptItems = new BitSet[registeredPredicateCounter]; ++ for (int i = 0; i < precalculatedTemptItems.length; i++) { ++ this.precalculatedTemptItems[i] = new BitSet(); + } + } + +- public void reset() { ++ public void tick(final ServerLevel world) { + for (int i = 0; i < registeredPredicateCounter; i++) { +- this.precalculatedTemptItems.get(i).clear(); ++ this.precalculatedTemptItems[i].clear(); ++ this.calculatedThisTick.clear(); ++ } ++ ++ int j = 0; ++ final ServerPlayer[] array = world.players().toArray(EMPTY_PLAYERS); ++ for (int i = 0; i < array.length; i++) { ++ final ServerPlayer p = array[i]; ++ if (!p.isSpectator() && p.isAlive()) { ++ array[j] = p; ++ j++; ++ } + } +- this.calculatedThisTick.clear(); ++ this.players = ObjectArrays.setLength(array, j); + } + + public boolean isCalculated(final int index) { +@@ -86,6 +104,10 @@ public class GlobalTemptationLookup { + } + + public BitSet getBitSet(final int index) { +- return this.precalculatedTemptItems.get(index); ++ return this.precalculatedTemptItems[index]; ++ } ++ ++ public ServerPlayer[] players() { ++ return this.players; + } + } +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index f839c1b040f51e536b575ed5906f85a0a374b37f..03cedc642cccf9d92f5755bec367f53f084c81f6 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -936,7 +936,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR +- this.globalTemptationLookup.reset(); // Paper - optimise temptation lookups - reset global cache prior to next entity tick ++ this.globalTemptationLookup.tick(this); // Paper - optimise temptation lookups - reset global cache prior to next entity tick + + if (org.dreeam.leaf.config.modules.opt.OptimizeDespawn.enabled && tickRateManager.runsNormally()) { despawnMap.tick(this, this.entityTickList); } // Leaf - optimize despawn + this.entityTickList +diff --git a/net/minecraft/world/entity/ai/goal/TemptGoal.java b/net/minecraft/world/entity/ai/goal/TemptGoal.java +index 0c9a355344463ea85a3b724d22d53638b1e42394..7cb94eb75a1de0cef02d4953ee5896bd5e3c7abd 100644 +--- a/net/minecraft/world/entity/ai/goal/TemptGoal.java ++++ b/net/minecraft/world/entity/ai/goal/TemptGoal.java +@@ -64,21 +64,25 @@ public class TemptGoal extends Goal { + final net.minecraft.server.level.ServerLevel level = getServerLevel(this.mob); + final io.papermc.paper.entity.temptation.GlobalTemptationLookup lookup = level.getTemptGoalLookup(); + final java.util.BitSet lookupBitSet = lookup.getBitSet(this.globalTemptationLookupIndex); +- final java.util.List players = level.players(); ++ final net.minecraft.server.level.ServerPlayer[] players = lookup.players(); + // Check if the lookup needs to be computed this tick. Do so for all players if needed. + if (!lookup.isCalculated(this.globalTemptationLookupIndex)) { +- for (int i = 0; i < players.size(); i++) { +- lookupBitSet.set(i, shouldFollow(players.get(i))); ++ for (int i = 0; i < players.length; i++) { ++ lookupBitSet.set(i, shouldFollow(players[i])); + } + lookup.setCalculated(this.globalTemptationLookupIndex); + } + double d = -1.0; + net.minecraft.server.level.ServerPlayer nearestPlayer = null; + // Only iterate over players that passed #shouldFollow either in the prior computation or another goals canUse check. ++ final Mob mob = this.mob; ++ final double mobX = mob.getX(); ++ final double mobY = mob.getY(); ++ final double mobZ = mob.getZ(); + for (int i = lookupBitSet.nextSetBit(0); i >= 0; i = lookupBitSet.nextSetBit(i + 1)) { +- final net.minecraft.server.level.ServerPlayer player = players.get(i); +- if (rangeTargetingConditions.test(level, this.mob, player)) { +- final double d1 = player.distanceToSqr(this.mob.getX(), this.mob.getY(), this.mob.getZ()); ++ final net.minecraft.server.level.ServerPlayer player = players[i]; ++ if (rangeTargetingConditions.test(level, mob, player)) { ++ final double d1 = player.distanceToSqr(mobX, mobY, mobZ); + if (d == -1.0 || d1 < d) { + d = d1; + nearestPlayer = player; +diff --git a/net/minecraft/world/entity/ai/sensing/TemptingSensor.java b/net/minecraft/world/entity/ai/sensing/TemptingSensor.java +index a88b644c7480bfa66526c697455771eb7f86ad67..58c22828b62323ed99b7b1a9e3c1196aaf619786 100644 +--- a/net/minecraft/world/entity/ai/sensing/TemptingSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/TemptingSensor.java +@@ -35,11 +35,11 @@ public class TemptingSensor extends Sensor { + if (this.globalTemptationLookupIndex != -1) { + final io.papermc.paper.entity.temptation.GlobalTemptationLookup lookup = level.getTemptGoalLookup(); + final java.util.BitSet lookupBitSet = lookup.getBitSet(this.globalTemptationLookupIndex); +- final java.util.List players = level.players(); ++ final net.minecraft.server.level.ServerPlayer[] players = lookup.players(); + // Check if the lookup needs to be computed this tick. Do so for all players if needed. + if (!lookup.isCalculated(this.globalTemptationLookupIndex)) { +- for (int i = 0; i < players.size(); i++) { +- final net.minecraft.server.level.ServerPlayer serverPlayer = players.get(i); ++ for (int i = 0; i < players.length; i++) { ++ final net.minecraft.server.level.ServerPlayer serverPlayer = players[i]; + lookupBitSet.set(i, net.minecraft.world.entity.EntitySelector.NO_SPECTATORS.test(serverPlayer) && this.playerHoldingTemptation(serverPlayer)); // check on update + } + lookup.setCalculated(this.globalTemptationLookupIndex); +@@ -47,10 +47,13 @@ public class TemptingSensor extends Sensor { + double d = -1.0; + net.minecraft.server.level.ServerPlayer nearestPlayer = null; + // Only iterate over players that passed #shouldFollow either in the prior computation or another goals canUse check. ++ final double entityX = entity.getX(); ++ final double entityY = entity.getY(); ++ final double entityZ = entity.getZ(); + for (int i = lookupBitSet.nextSetBit(0); i >= 0; i = lookupBitSet.nextSetBit(i + 1)) { +- final net.minecraft.server.level.ServerPlayer player = players.get(i); ++ final net.minecraft.server.level.ServerPlayer player = players[i]; + if (targetingConditions.test(level, entity, player) && !entity.hasPassenger(player)) { // check on update - consider non passengers +- final double d1 = player.distanceToSqr(entity.getX(), entity.getY(), entity.getZ()); ++ final double d1 = player.distanceToSqr(entityX, entityY, entityZ); + if (d == -1.0 || d1 < d) { + d = d1; + nearestPlayer = player; +diff --git a/net/minecraft/world/entity/animal/AbstractCow.java b/net/minecraft/world/entity/animal/AbstractCow.java +index 832a9a56fbf870e4242d4a59ab58ed43881fdccd..48441154ff7cbfea714ba88ac31910d0419644a8 100644 +--- a/net/minecraft/world/entity/animal/AbstractCow.java ++++ b/net/minecraft/world/entity/animal/AbstractCow.java +@@ -40,7 +40,7 @@ public abstract class AbstractCow extends Animal { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(1, new PanicGoal(this, 2.0)); + this.goalSelector.addGoal(2, new BreedGoal(this, 1.0)); +- this.goalSelector.addGoal(3, new TemptGoal(this, 1.25, io.papermc.paper.entity.temptation.GlobalTemptationLookup.COW_FOOD, false)); // Paper - optimise temptation lookups ++ this.goalSelector.addGoal(3, new TemptGoal(this, 1.25, level().purpurConfig.cowFeedMushrooms > 0 ? io.papermc.paper.entity.temptation.GlobalTemptationLookup.COW_FOOD_MUSHROOM : io.papermc.paper.entity.temptation.GlobalTemptationLookup.COW_FOOD, false)); // Purpur - Cows eat mushrooms // Paper - optimise temptation lookups + this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25)); + this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0)); + this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F));