diff --git a/patches/server/0108-lithium-collections.brain-and-ai.task.memory_change_.patch b/patches/server/0108-lithium-collections.brain-and-ai.task.memory_change_.patch new file mode 100644 index 0000000..4f2ca32 --- /dev/null +++ b/patches/server/0108-lithium-collections.brain-and-ai.task.memory_change_.patch @@ -0,0 +1,220 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Mon, 10 Jan 2022 18:07:44 -0500 +Subject: [PATCH] lithium: collections.brain and ai.task.memory_change_counting + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/entity/ai/Brain.java b/src/main/java/net/minecraft/world/entity/ai/Brain.java +index d1d576a616797d7658c117582435434743aeef58..8179f5bc2bcd5ddb46e790b6e906ea9a517ed5b0 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/Brain.java ++++ b/src/main/java/net/minecraft/world/entity/ai/Brain.java +@@ -40,13 +40,21 @@ import net.minecraft.world.entity.schedule.Schedule; + import org.apache.commons.lang3.mutable.MutableObject; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++// JettPack start ++import it.unimi.dsi.fastutil.objects.Reference2ReferenceLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; ++import java.util.ArrayList; ++// JettPack end + + public class Brain { + static final Logger LOGGER = LogManager.getLogger(); + private final Supplier>> codec; + private static final int SCHEDULE_UPDATE_DELAY = 20; +- private final Map, Optional>> memories = Maps.newHashMap(); +- private final Map>, Sensor> sensors = Maps.newLinkedHashMap(); ++ // JettPack start - lithium: collections.brain ++ private final Map, Optional>> memories = new Reference2ReferenceOpenHashMap<>(); ++ private final Map>, Sensor> sensors = new Reference2ReferenceLinkedOpenHashMap<>(); ++ private ArrayList>>> tasksSorted = new ArrayList<>(); ++ // JettPack end + private final Map>>> availableBehaviorsByPriority = Maps.newTreeMap(); + private Schedule schedule = Schedule.EMPTY; + private final Map, MemoryStatus>>> activityRequirements = Maps.newHashMap(); +@@ -59,6 +67,12 @@ public class Brain { + public static Brain.Provider provider(Collection> memoryModules, Collection>> sensors) { + return new Brain.Provider<>(memoryModules, sensors); + } ++ // JettPack start - ai.task.memory_change_counting ++ private long memoryModCount = 1; ++ public long getModCount() { ++ return memoryModCount; ++ } ++ // JettPack end + + public static Codec> codec(Collection> memoryModules, Collection>> sensors) { + final MutableObject>> mutableObject = new MutableObject<>(); +@@ -74,16 +88,16 @@ public class Brain { + } + + public DataResult> decode(DynamicOps dynamicOps, MapLike mapLike) { +- MutableObject>>> mutableObject = new MutableObject<>(DataResult.success(ImmutableList.builder())); ++ MutableObject>>> decode_mutableObject = new MutableObject<>(DataResult.success(ImmutableList.builder())); // JettPack - decomp fix + mapLike.entries().forEach((pair) -> { + DataResult> dataResult = Registry.MEMORY_MODULE_TYPE.byNameCodec().parse(dynamicOps, pair.getFirst()); + DataResult> dataResult2 = dataResult.flatMap((memoryModuleType) -> { + return this.captureRead(memoryModuleType, dynamicOps, (T)pair.getSecond()); + }); +- mutableObject.setValue(mutableObject.getValue().apply2(Builder::add, dataResult2)); ++ decode_mutableObject.setValue(decode_mutableObject.getValue().apply2(Builder::add, dataResult2)); // JettPack - decomp fix + }); +- ImmutableList> immutableList = mutableObject.getValue().resultOrPartial(Brain.LOGGER::error).map(Builder::build).orElseGet(ImmutableList::of); +- return DataResult.success(new Brain<>(memoryModules, sensors, immutableList, mutableObject::getValue)); ++ ImmutableList> immutableList = decode_mutableObject.getValue().resultOrPartial(Brain.LOGGER::error).map(Builder::build).orElseGet(ImmutableList::of); // JettPack - decomp fix ++ return DataResult.success(new Brain(memoryModules, sensors, immutableList, mutableObject::getValue)); // JettPack - decomp fix + } + + private DataResult> captureRead(MemoryModuleType memoryModuleType, DynamicOps dynamicOps, T object) { +@@ -126,6 +140,7 @@ public class Brain { + for(Brain.MemoryValue memoryValue : memoryEntries) { + memoryValue.setMemoryInternal(this); + } ++ this.tasksSorted = new ArrayList<>(this.availableBehaviorsByPriority.values()); // JettPack - lithium: collections.brain + + } + +@@ -135,7 +150,7 @@ public class Brain { + + Stream> memories() { + return this.memories.entrySet().stream().map((entry) -> { +- return Brain.MemoryValue.createUnchecked(entry.getKey(), entry.getValue()); ++ return Brain.MemoryValue.createUnchecked((MemoryModuleType)entry.getKey(), (Optional)entry.getValue()); // JettPack - decomp fix + }); + } + +@@ -164,14 +179,19 @@ public class Brain { + if (memory.isPresent() && this.isEmptyCollection(memory.get().getValue())) { + this.eraseMemory(type); + } else { +- this.memories.put(type, memory); ++ // JettPack start - lithium: ai.task.memory_change_counting ++ Object oldValue = this.memories.put(type, memory); ++ if (oldValue == null || ((Optional) oldValue).isPresent() != ((Optional) memory).isPresent()) { ++ this.memoryModCount++; ++ } ++ // JettPack end + } + } + + } + + public Optional getMemory(MemoryModuleType type) { +- return this.memories.get(type).map(ExpirableValue::getValue); ++ return ((Optional)this.memories.get(type)).map(object -> ((ExpirableValue)object).getValue()); // JettPack - decomp fix + } + + public long getTimeUntilExpiry(MemoryModuleType type) { +@@ -322,6 +342,7 @@ public class Brain { + + public void addActivity(Activity activity, ImmutableList>> indexedTasks) { + this.addActivityAndRemoveMemoriesWhenStopped(activity, indexedTasks, ImmutableSet.of(), Sets.newHashSet()); ++ this.tasksSorted = new ArrayList<>(this.availableBehaviorsByPriority.values()); // JettPack - lithium: collections.brain + } + + public void addActivityWithConditions(Activity activity, ImmutableList>> indexedTasks, Set, MemoryStatus>> requiredMemories) { +@@ -347,6 +368,7 @@ public class Brain { + @VisibleForTesting + public void removeAllBehaviors() { + this.availableBehaviorsByPriority.clear(); ++ this.tasksSorted = new ArrayList<>(this.availableBehaviorsByPriority.values()); // JettPack - lithium: collections.brain + } + + public boolean isActive(Activity activity) { +@@ -354,7 +376,7 @@ public class Brain { + } + + public Brain copyWithoutBehaviors() { +- Brain brain = new Brain<>(this.memories.keySet(), this.sensors.keySet(), ImmutableList.of(), this.codec); ++ Brain brain = new Brain(this.memories.keySet(), this.sensors. keySet(), ImmutableList.of(), this.codec); // JettPack - decomp fix + + for(Entry, Optional>> entry : this.memories.entrySet()) { + MemoryModuleType memoryModuleType = entry.getKey(); +@@ -405,7 +427,7 @@ public class Brain { + private void startEachNonRunningBehavior(ServerLevel world, E entity) { + long l = world.getGameTime(); + +- for(Map>> map : this.availableBehaviorsByPriority.values()) { ++ for(Map>> map : this.tasksSorted) { // JettPack - lithium: collections.brain + for(Entry>> entry : map.entrySet()) { + Activity activity = entry.getKey(); + if (this.activeActivities.contains(activity)) { +@@ -464,8 +486,8 @@ public class Brain { + private final MemoryModuleType type; + private final Optional> value; + +- static Brain.MemoryValue createUnchecked(MemoryModuleType type, Optional> data) { +- return new Brain.MemoryValue<>(type, data); ++ static Brain.MemoryValue createUnchecked(MemoryModuleType type, Optional> data) { // JettPack - decomp fix ++ return new Brain.MemoryValue(type, data); // JettPack - decomp fix + } + + MemoryValue(MemoryModuleType type, Optional> data) { +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java +index c24ff2ef1054523e58892c2b35080cffb6ab744a..2238e662f0c418548e51c3ec122106b7966921f1 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java +@@ -6,6 +6,13 @@ import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.ai.memory.MemoryModuleType; + import net.minecraft.world.entity.ai.memory.MemoryStatus; ++// JettPack start ++import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; ++import it.unimi.dsi.fastutil.objects.ObjectIterator; ++import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; ++import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; ++import net.minecraft.world.entity.ai.Brain; ++// JettPack end + + public abstract class Behavior { + private static final int DEFAULT_DURATION = 60; +@@ -27,10 +34,15 @@ public abstract class Behavior { + this(requiredMemoryState, runTime, runTime); + } + ++ // JettPack start - lithium: ai.task.memory_change_counting ++ private long cachedMemoryModCount; ++ private boolean cachedHasRequiredMemoryState; ++ // JettPack end ++ + public Behavior(Map, MemoryStatus> requiredMemoryState, int minRunTime, int maxRunTime) { + this.minDuration = minRunTime; + this.maxDuration = maxRunTime; +- this.entryCondition = requiredMemoryState; ++ this.entryCondition = new Reference2ObjectOpenHashMap<>(requiredMemoryState); // JettPack - lithium: ai.task.memory_change_counting + // Paper start - configurable behavior tick rate and timings + String key = io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()); + int lastSeparator = key.lastIndexOf('.'); +@@ -109,15 +121,24 @@ public abstract class Behavior { + } + + private boolean hasRequiredMemories(E entity) { +- for(Entry, MemoryStatus> entry : this.entryCondition.entrySet()) { +- MemoryModuleType memoryModuleType = entry.getKey(); +- MemoryStatus memoryStatus = entry.getValue(); +- if (!entity.getBrain().checkMemory(memoryModuleType, memoryStatus)) { +- return false; ++ // JettPack start - lithium: ai.task.memory_change_counting ++ Brain brain = entity.getBrain(); ++ long modCount = brain.getModCount(); ++ if (this.cachedMemoryModCount == modCount) { ++ return this.cachedHasRequiredMemoryState; ++ } ++ this.cachedMemoryModCount = modCount; ++ ++ ObjectIterator, MemoryStatus>> fastIterator = ((Reference2ObjectOpenHashMap, MemoryStatus>) this.entryCondition).reference2ObjectEntrySet().fastIterator(); ++ while (fastIterator.hasNext()) { ++ Reference2ObjectMap.Entry, MemoryStatus> entry = fastIterator.next(); ++ if (!brain.checkMemory(entry.getKey(), entry.getValue())) { ++ return this.cachedHasRequiredMemoryState = false; + } + } + +- return true; ++ return this.cachedHasRequiredMemoryState = true; ++ // JettPack end + } + + public static enum Status { diff --git a/patches/server/0109-lithium-ai.brain.patch b/patches/server/0109-lithium-ai.brain.patch new file mode 100644 index 0000000..9012c66 --- /dev/null +++ b/patches/server/0109-lithium-ai.brain.patch @@ -0,0 +1,230 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Tue, 11 Jan 2022 15:28:32 -0500 +Subject: [PATCH] lithium: ai.brain + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/collections/MaskedList.java b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/MaskedList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..06399c159c8b779bb65149704490671e0c04d4c2 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/MaskedList.java +@@ -0,0 +1,87 @@ ++package me.jellysquid.mods.lithium.common.util.collections; ++ ++import it.unimi.dsi.fastutil.objects.ObjectArrayList; ++ ++import java.util.*; ++import java.util.function.Consumer; ++ ++public class MaskedList extends AbstractList { ++ private final ObjectArrayList allElements; ++ private final BitSet visibleMask; ++ ++ public MaskedList(ObjectArrayList allElements) { ++ this.allElements = allElements; ++ this.visibleMask = new BitSet(); ++ } ++ ++ public void setVisible(E element, final boolean visible) { ++ int i = -1; ++ if (visible) { ++ do { ++ i = this.visibleMask.nextClearBit(i + 1); ++ } while (element != this.allElements.get(i)); ++ this.visibleMask.set(i); ++ } else { ++ do { ++ i = this.visibleMask.nextSetBit(i + 1); ++ } while (element != this.allElements.get(i)); ++ this.visibleMask.clear(i); ++ } ++ } ++ ++ @Override ++ public Iterator iterator() { ++ return new Iterator() { ++ int nextIndex = 0; ++ ++ @Override ++ public boolean hasNext() { ++ return MaskedList.this.visibleMask.nextSetBit(this.nextIndex) != -1; ++ } ++ ++ @Override ++ public E next() { ++ int index = MaskedList.this.visibleMask.nextSetBit(this.nextIndex); ++ this.nextIndex = index + 1; ++ return MaskedList.this.allElements.get(index); ++ } ++ }; ++ } ++ ++ @Override ++ public Spliterator spliterator() { ++ return new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED | Spliterator.NONNULL) { ++ int nextIndex = 0; ++ ++ @Override ++ public boolean tryAdvance(Consumer action) { ++ int index = MaskedList.this.visibleMask.nextSetBit(this.nextIndex); ++ if (index == -1) { ++ return false; ++ } ++ this.nextIndex = index + 1; ++ action.accept(MaskedList.this.allElements.get(index)); ++ return true; ++ } ++ }; ++ } ++ ++ @Override ++ public E get(int index) { ++ if (index < 0 || index >= this.size()) { ++ throw new IndexOutOfBoundsException(index); ++ } ++ ++ int i = 0; ++ while (index >= 0) { ++ index--; ++ i = this.visibleMask.nextSetBit(i + 1); ++ } ++ return this.allElements.get(i); ++ } ++ ++ @Override ++ public int size() { ++ return this.visibleMask.cardinality(); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/Brain.java b/src/main/java/net/minecraft/world/entity/ai/Brain.java +index 8179f5bc2bcd5ddb46e790b6e906ea9a517ed5b0..500a0e8505c181fa61d1b0c173bac03cd38d9616 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/Brain.java ++++ b/src/main/java/net/minecraft/world/entity/ai/Brain.java +@@ -44,6 +44,7 @@ import org.apache.logging.log4j.Logger; + import it.unimi.dsi.fastutil.objects.Reference2ReferenceLinkedOpenHashMap; + import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; + import java.util.ArrayList; ++import me.jellysquid.mods.lithium.common.util.collections.MaskedList; + // JettPack end + + public class Brain { +@@ -55,6 +56,7 @@ public class Brain { + private final Map>, Sensor> sensors = new Reference2ReferenceLinkedOpenHashMap<>(); + private ArrayList>>> tasksSorted = new ArrayList<>(); + // JettPack end ++ private MaskedList> flatTasks; // JettPack - lithium: ai.brain + private final Map>>> availableBehaviorsByPriority = Maps.newTreeMap(); + private Schedule schedule = Schedule.EMPTY; + private final Map, MemoryStatus>>> activityRequirements = Maps.newHashMap(); +@@ -74,6 +76,22 @@ public class Brain { + } + // JettPack end + ++ // JettPack start - ai.brain ++ private void initTaskList() { ++ ObjectArrayList> list = new ObjectArrayList<>(); ++ ++ for (Map>> map : this.availableBehaviorsByPriority.values()) { ++ for (Set> set : map.values()) { ++ for (Behavior task : set) { ++ //noinspection UseBulkOperation ++ list.add(task); ++ } ++ } ++ } ++ this.flatTasks = new MaskedList<>(list); ++ } ++ // JettPack end ++ + public static Codec> codec(Collection> memoryModules, Collection>> sensors) { + final MutableObject>> mutableObject = new MutableObject<>(); + mutableObject.setValue((new MapCodec>() { +@@ -141,7 +159,7 @@ public class Brain { + memoryValue.setMemoryInternal(this); + } + this.tasksSorted = new ArrayList<>(this.availableBehaviorsByPriority.values()); // JettPack - lithium: collections.brain +- ++ this.flatTasks = null; // JettPack - lithium: ai.brain + } + + public DataResult serializeStart(DynamicOps ops) { +@@ -244,19 +262,12 @@ public class Brain { + @Deprecated + @VisibleForDebug + public List> getRunningBehaviors() { +- List> list = new ObjectArrayList<>(); +- +- for(Map>> map : this.availableBehaviorsByPriority.values()) { +- for(Set> set : map.values()) { +- for(Behavior behavior : set) { +- if (behavior.getStatus() == Behavior.Status.RUNNING) { +- list.add(behavior); +- } +- } +- } ++ // JettPack start - lithium: ai.brain ++ if (this.flatTasks == null) { ++ this.initTaskList(); + } +- +- return list; ++ return this.flatTasks; ++ // JettPack end + } + + public void useDefaultActivity() { +@@ -369,6 +380,7 @@ public class Brain { + public void removeAllBehaviors() { + this.availableBehaviorsByPriority.clear(); + this.tasksSorted = new ArrayList<>(this.availableBehaviorsByPriority.values()); // JettPack - lithium: collections.brain ++ this.flatTasks = null; // JettPack - lithium: ai.brain + } + + public boolean isActive(Activity activity) { +@@ -419,7 +431,13 @@ public class Brain { + long l = entity.level.getGameTime(); + + for(Behavior behavior : this.getRunningBehaviors()) { ++ // JettPack start - lithium: ai.brain + behavior.doStop(world, entity, l); ++ if (this.flatTasks == null) { ++ this.initTaskList(); ++ } ++ this.flatTasks.setVisible(behavior, false); ++ // JettPack end + } + + } +@@ -434,6 +452,14 @@ public class Brain { + for(Behavior behavior : entry.getValue()) { + if (behavior.getStatus() == Behavior.Status.STOPPED) { + behavior.tryStart(world, entity, l); ++ // JettPack start - lithium: ai.brain ++ if (behavior.getStatus() == Behavior.Status.RUNNING) { ++ if (this.flatTasks == null) { ++ this.initTaskList(); ++ } ++ this.flatTasks.setVisible(behavior, true); ++ } ++ // JettPack end + } + } + } +@@ -447,6 +473,14 @@ public class Brain { + + for(Behavior behavior : this.getRunningBehaviors()) { + behavior.tickOrStop(world, entity, l); ++ // JettPack start - lithium: ai.brain ++ if (behavior.getStatus() != Behavior.Status.RUNNING) { ++ if (this.flatTasks == null) { ++ this.initTaskList(); ++ } ++ this.flatTasks.setVisible(behavior, false); ++ } ++ // JettPack end + } + + } diff --git a/patches/server/0110-lithium-ai.nearby_entity_tracking-and-ai.nearby_enti.patch b/patches/server/0110-lithium-ai.nearby_entity_tracking-and-ai.nearby_enti.patch new file mode 100644 index 0000000..e40b583 --- /dev/null +++ b/patches/server/0110-lithium-ai.nearby_entity_tracking-and-ai.nearby_enti.patch @@ -0,0 +1,1061 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Thu, 13 Jan 2022 00:47:37 -0500 +Subject: [PATCH] lithium: ai.nearby_entity_tracking and + ai.nearby_entity_tracking.goals + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/EntityTrackerEngine.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/EntityTrackerEngine.java +new file mode 100644 +index 0000000000000000000000000000000000000000..af1efa5f7abfcf9440818f7b13e97bb65a9b2323 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/EntityTrackerEngine.java +@@ -0,0 +1,55 @@ ++package me.jellysquid.mods.lithium.common.entity.tracker; ++ ++import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; ++import java.util.List; ++import net.minecraft.world.Container; ++import net.minecraft.world.entity.item.ItemEntity; ++import net.minecraft.world.level.entity.EntityAccess; ++ ++/** ++ * Helps to track in which entity sections entities of a certain type moved, appeared or disappeared by providing int ++ * masks for all Entity classes. ++ * Helps to track the entities within a world and provide notifications to listeners when a tracked entity enters or leaves a ++ * watched area. This removes the necessity to constantly poll the world for nearby entities each tick and generally ++ * provides a sizable boost to performance of hoppers. ++ */ ++public abstract class EntityTrackerEngine { ++ public static final List> MOVEMENT_NOTIFYING_ENTITY_CLASSES; ++ public static volatile Reference2IntOpenHashMap> CLASS_2_NOTIFY_MASK; ++ public static final int NUM_MOVEMENT_NOTIFYING_CLASSES; ++ ++ static { ++ MOVEMENT_NOTIFYING_ENTITY_CLASSES = List.of(ItemEntity.class, Container.class); ++ ++ CLASS_2_NOTIFY_MASK = new Reference2IntOpenHashMap<>(); ++ CLASS_2_NOTIFY_MASK.defaultReturnValue(-1); ++ NUM_MOVEMENT_NOTIFYING_CLASSES = MOVEMENT_NOTIFYING_ENTITY_CLASSES.size(); ++ } ++ ++ public static int getNotificationMask(Class entityClass) { ++ int notificationMask = CLASS_2_NOTIFY_MASK.getInt(entityClass); ++ if (notificationMask == -1) { ++ notificationMask = calculateNotificationMask(entityClass); ++ } ++ return notificationMask; ++ } ++ ++ private static int calculateNotificationMask(Class entityClass) { ++ int mask = 0; ++ for (int i = 0; i < MOVEMENT_NOTIFYING_ENTITY_CLASSES.size(); i++) { ++ Class superclass = MOVEMENT_NOTIFYING_ENTITY_CLASSES.get(i); ++ if (superclass.isAssignableFrom(entityClass)) { ++ mask |= 1 << i; ++ } ++ } ++ ++ //progress can be lost here, but it can only cost performance ++ //copy on write followed by publication in volatile field guarantees visibility of the final state ++ Reference2IntOpenHashMap> copy = CLASS_2_NOTIFY_MASK.clone(); ++ copy.put(entityClass, mask); ++ CLASS_2_NOTIFY_MASK = copy; ++ ++ return mask; ++ } ++ ++} +\ No newline at end of file +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityListener.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityListener.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2dd34c4028fba33f2449cde5f9228283a3cb69dc +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityListener.java +@@ -0,0 +1,94 @@ ++package me.jellysquid.mods.lithium.common.entity.tracker.nearby; ++ ++import me.jellysquid.mods.lithium.common.util.tuples.Range6Int; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.SectionPos; ++import net.minecraft.util.ClassInstanceMultiMap; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.entity.EntityAccess; ++import net.minecraft.world.level.entity.EntitySection; ++import net.minecraft.world.level.entity.EntitySectionStorage; ++import net.minecraft.world.level.levelgen.structure.BoundingBox; ++ ++/** ++ * The main interface used to receive events from the ++ * {@link me.jellysquid.mods.lithium.common.entity.tracker.EntityTrackerEngine} of a world. ++ */ ++public interface NearbyEntityListener { ++ Range6Int EMPTY_RANGE = new Range6Int(0, 0, 0, -1, -1, -1); ++ /** ++ * Calls the callbacks for the chunk coordinates that this listener is leaving and entering ++ */ ++ default void forEachChunkInRangeChange(EntitySectionStorage entityCache, SectionPos prevCenterPos, SectionPos newCenterPos) { ++ Range6Int chunkRange = this.getChunkRange(); ++ if (chunkRange == EMPTY_RANGE) { ++ return; ++ } ++ BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); ++ ++ BoundingBox after = newCenterPos == null ? null : new BoundingBox(newCenterPos.getX() - chunkRange.negativeX(), newCenterPos.getY() - chunkRange.negativeY(), newCenterPos.getZ() - chunkRange.negativeZ(), newCenterPos.getX() + chunkRange.positiveX(), newCenterPos.getY() + chunkRange.positiveY(), newCenterPos.getZ() + chunkRange.positiveZ()); ++ BoundingBox before = prevCenterPos == null ? null : new BoundingBox(prevCenterPos.getX() - chunkRange.negativeX(), prevCenterPos.getY() - chunkRange.negativeY(), prevCenterPos.getZ() - chunkRange.negativeZ(), prevCenterPos.getX() + chunkRange.positiveX(), prevCenterPos.getY() + chunkRange.positiveY(), prevCenterPos.getZ() + chunkRange.positiveZ()); ++ if (before != null) { ++ for (int x = before.minX(); x <= before.maxX(); x++) { ++ for (int y = before.minY(); y <= before.maxY(); y++) { ++ for (int z = before.minZ(); z <= before.maxZ(); z++) { ++ if (after == null || !after.isInside(pos.set(x, y, z))) { ++ long sectionPos = SectionPos.asLong(x, y, z); ++ EntitySection trackingSection = entityCache.getOrCreateSection(sectionPos); ++ trackingSection.removeListener(entityCache, this); ++ if (trackingSection.isEmpty()) { ++ entityCache.remove(sectionPos); ++ } ++ } ++ } ++ } ++ } ++ } ++ if (after != null) { ++ for (int x = after.minX(); x <= after.maxX(); x++) { ++ for (int y = after.minY(); y <= after.maxY(); y++) { ++ for (int z = after.minZ(); z <= after.maxZ(); z++) { ++ if (before == null || !before.isInside(pos.set(x, y, z))) { ++ entityCache.getOrCreateSection(SectionPos.asLong(x, y, z)).addListener(this); ++ } ++ } ++ } ++ } ++ } ++ } ++ Range6Int getChunkRange(); ++ ++ /** ++ * Called by the entity tracker when an entity enters the range of this listener. ++ */ ++ void onEntityEnteredRange(Entity entity); ++ ++ /** ++ * Called by the entity tracker when an entity leaves the range of this listener or is removed from the world. ++ */ ++ void onEntityLeftRange(Entity entity); ++ ++ default Class getEntityClass() { ++ return Entity.class; ++ } ++ ++ /** ++ * Method to add all entities in the iteration order of the chunk section. This order is relevant and necessary ++ * to keep vanilla parity. ++ * @param the type of the Entities in the collection ++ * @param entityTrackingSection the section the entities are in ++ * @param collection the collection of Entities that entered the range of this listener ++ */ ++ default void onSectionEnteredRange(Object entityTrackingSection, ClassInstanceMultiMap collection) { ++ for (Entity entity : collection.find(this.getEntityClass())) { ++ this.onEntityEnteredRange(entity); ++ } ++ } ++ ++ default void onSectionLeftRange(Object entityTrackingSection, ClassInstanceMultiMap collection) { ++ for (Entity entity : collection.find(this.getEntityClass())) { ++ this.onEntityLeftRange(entity); ++ } ++ } ++ ++} +\ No newline at end of file +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityListenerMulti.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityListenerMulti.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1d7a656e683987b80916abaf1dcb10f77b9a8a4a +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityListenerMulti.java +@@ -0,0 +1,83 @@ ++package me.jellysquid.mods.lithium.common.entity.tracker.nearby; ++ ++import me.jellysquid.mods.lithium.common.util.tuples.Range6Int; ++import net.minecraft.world.entity.Entity; ++import java.util.ArrayList; ++import java.util.List; ++ ++/** ++ * Allows for multiple listeners on an entity to be grouped under one logical listener. No guarantees are made about the ++ * order of which each sub-listener will be notified. ++ */ ++public class NearbyEntityListenerMulti implements NearbyEntityListener { ++ private final List listeners = new ArrayList<>(4); ++ private Range6Int range = null; ++ ++ public void addListener(NearbyEntityListener listener) { ++ if (this.range != null) { ++ throw new IllegalStateException("Cannot add sublisteners after listening range was set!"); ++ } ++ this.listeners.add(listener); ++ } ++ ++ public void removeListener(NearbyEntityListener listener) { ++ this.listeners.remove(listener); ++ } ++ ++ @Override ++ public Range6Int getChunkRange() { ++ if (this.range != null) { ++ return this.range; ++ } ++ return this.calculateRange(); ++ } ++ private Range6Int calculateRange() { ++ if (this.listeners.isEmpty()) { ++ return this.range = EMPTY_RANGE; ++ } ++ int positiveX = -1; ++ int positiveY = -1; ++ int positiveZ = -1; ++ int negativeX = 0; ++ int negativeY = 0; ++ int negativeZ = 0; ++ ++ for (NearbyEntityListener listener : this.listeners) { ++ Range6Int chunkRange = listener.getChunkRange(); ++ positiveX = Math.max(chunkRange.positiveX(), positiveX); ++ positiveY = Math.max(chunkRange.positiveY(), positiveY); ++ positiveZ = Math.max(chunkRange.positiveZ(), positiveZ); ++ negativeX = Math.max(chunkRange.negativeX(), negativeX); ++ negativeY = Math.max(chunkRange.negativeY(), negativeY); ++ negativeZ = Math.max(chunkRange.negativeZ(), negativeZ); ++ ++ } ++ return this.range = new Range6Int(positiveX, positiveY, positiveZ, negativeX, negativeY, negativeZ); ++ } ++ ++ @Override ++ public void onEntityEnteredRange(Entity entity) { ++ for (NearbyEntityListener listener : this.listeners) { ++ listener.onEntityEnteredRange(entity); ++ } ++ } ++ ++ @Override ++ public void onEntityLeftRange(Entity entity) { ++ for (NearbyEntityListener listener : this.listeners) { ++ listener.onEntityLeftRange(entity); ++ } ++ } ++ ++ @Override ++ public String toString() { ++ StringBuilder sublisteners = new StringBuilder(); ++ String comma = ""; ++ for (NearbyEntityListener listener : this.listeners) { ++ sublisteners.append(comma).append(listener.toString()); ++ comma = ","; //trick to drop the first comma ++ } ++ ++ return super.toString() + " with sublisteners: [" + sublisteners + "]"; ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityTracker.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityTracker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..08d0be81d25b8b35506a7c03942356d0f33c2d89 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityTracker.java +@@ -0,0 +1,140 @@ ++package me.jellysquid.mods.lithium.common.entity.tracker.nearby; ++ ++import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap; ++import me.jellysquid.mods.lithium.common.util.tuples.Range6Int; ++import net.minecraft.core.SectionPos; ++import net.minecraft.core.Vec3i; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.LivingEntity; ++import net.minecraft.world.entity.ai.targeting.TargetingConditions; ++import net.minecraft.world.entity.player.Player; ++import net.minecraft.world.phys.AABB; ++import java.util.List; ++ ++/** ++ * Maintains a collection of all entities within the range of this listener. This allows AI goals to quickly ++ * assess nearby entities which match the provided class. ++ */ ++public class NearbyEntityTracker implements NearbyEntityListener { ++ private final Class clazz; ++ private final LivingEntity self; ++ ++ private final Reference2LongOpenHashMap nearbyEntities = new Reference2LongOpenHashMap<>(0); ++ private long counter; ++ private final Range6Int chunkBoxRadius; ++ ++ public NearbyEntityTracker(Class clazz, LivingEntity self, Vec3i boxRadius) { ++ this.clazz = clazz; ++ this.self = self; ++ this.chunkBoxRadius = new Range6Int( ++ 1 + SectionPos.blockToSectionCoord(boxRadius.getX()), ++ 1 + SectionPos.blockToSectionCoord(boxRadius.getY()), ++ 1 + SectionPos.blockToSectionCoord(boxRadius.getZ()), ++ 1 + SectionPos.blockToSectionCoord(boxRadius.getX()), ++ 1 + SectionPos.blockToSectionCoord(boxRadius.getY()), ++ 1 + SectionPos.blockToSectionCoord(boxRadius.getZ()) ++ ); ++ } ++ ++ @Override ++ public Class getEntityClass() { ++ return this.clazz; ++ } ++ ++ @Override ++ public Range6Int getChunkRange() { ++ return this.chunkBoxRadius; ++ } ++ ++ @SuppressWarnings("unchecked") ++ @Override ++ public void onEntityEnteredRange(Entity entity) { ++ if (!this.clazz.isInstance(entity)) { ++ return; ++ } ++ ++ this.nearbyEntities.put((T) entity, this.counter++); ++ } ++ ++ @Override ++ public void onEntityLeftRange(Entity entity) { ++ if (this.nearbyEntities.isEmpty() || !this.clazz.isInstance(entity)) { ++ return; ++ } ++ ++ this.nearbyEntities.removeLong(entity); ++ } ++ ++ /** ++ * Gets the closest T (extends LivingEntity) to the center of this tracker that also intersects with the given box and meets the ++ * requirements of the targetPredicate. ++ * The result may be different from vanilla if there are multiple closest entities. ++ * ++ * @param box the box the entities have to intersect ++ * @param targetPredicate predicate the entity has to meet ++ * @param x ++ * @param y ++ * @param z ++ * @return the closest Entity that meets all requirements (distance, box intersection, predicate, type T) ++ */ ++ public T getClosestEntity(AABB box, TargetingConditions targetPredicate, double x, double y, double z) { ++ T nearest = null; ++ double nearestDistance = Double.POSITIVE_INFINITY; ++ ++ for (T entity : this.nearbyEntities.keySet()) { ++ double distance; ++ if ( ++ (box == null || box.intersects(entity.getBoundingBox())) && ++ (distance = entity.distanceToSqr(x, y, z)) <= nearestDistance && ++ targetPredicate.test(this.self, entity) ++ ) { ++ if (distance == nearestDistance) { ++ nearest = this.getFirst(nearest, entity); ++ } else { ++ nearest = entity; ++ } ++ nearestDistance = distance; ++ } ++ } ++ ++ return nearest; ++ } ++ ++ /** ++ * Gets the Entity that is processed first in vanilla. ++ * @param entity1 one Entity ++ * @param entity2 the other Entity ++ * @return the Entity that is first in vanilla ++ */ ++ private T getFirst(T entity1, T entity2) { ++ if (this.getEntityClass() == Player.class) { ++ //Get first in player list ++ List players = this.self.getCommandSenderWorld().players(); ++ return players.indexOf((Player)entity1) < players.indexOf((Player)entity2) ? entity1 : entity2; ++ } else { ++ //Get first sorted by chunk section pos as long, then sorted by first added to the chunk section ++ //First added to this tracker and first added to the chunk section is equivalent here, because ++ //this tracker always tracks complete sections and the entities are added in order ++ long pos1 = SectionPos.asLong(entity1.blockPosition()); ++ long pos2 = SectionPos.asLong(entity2.blockPosition()); ++ if (pos1 < pos2) { ++ return entity1; ++ } else if (pos2 < pos1) { ++ return entity2; ++ } else { ++ if (this.nearbyEntities.getLong(entity1) < this.nearbyEntities.getLong(entity2)) { ++ return entity1; ++ } else { ++ return entity2; ++ } ++ } ++ } ++ ++ } ++ ++ @Override ++ public String toString() { ++ return super.toString() + " for entity class: " + this.clazz.getName() + ", around entity: " + this.self.toString() + " with NBT: " + this.self.saveWithoutId(new CompoundTag()); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/SectionedEntityMovementTracker.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/SectionedEntityMovementTracker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c5a4bd43eb3d900a237f659f8ad4e1a3be05eb97 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/SectionedEntityMovementTracker.java +@@ -0,0 +1,143 @@ ++package me.jellysquid.mods.lithium.common.entity.tracker.nearby; ++ ++import it.unimi.dsi.fastutil.HashCommon; ++import me.jellysquid.mods.lithium.common.entity.tracker.EntityTrackerEngine; ++import me.jellysquid.mods.lithium.common.util.tuples.WorldSectionBox; ++import net.minecraft.core.SectionPos; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.entity.EntityAccess; ++import net.minecraft.world.level.entity.EntitySection; ++import net.minecraft.world.level.entity.EntitySectionStorage; ++import java.util.ArrayList; ++import net.minecraft.world.entity.Entity; ++ ++public abstract class SectionedEntityMovementTracker { ++ final WorldSectionBox trackedWorldSections; ++ final Class clazz; ++ private final int trackedClass; ++ ArrayList> sortedSections; ++ boolean[] sectionVisible; ++ private int timesRegistered; ++ private ArrayList sectionChangeCounters; ++ ++ private long maxChangeTime; ++ ++ public SectionedEntityMovementTracker(WorldSectionBox interactionChunks, Class clazz) { ++ this.clazz = clazz; ++ this.trackedWorldSections = interactionChunks; ++ this.trackedClass = EntityTrackerEngine.MOVEMENT_NOTIFYING_ENTITY_CLASSES.indexOf(clazz); ++ assert this.trackedClass != -1; ++ } ++ ++ @Override ++ public int hashCode() { ++ return HashCommon.mix(this.trackedWorldSections.hashCode()) ^ HashCommon.mix(this.trackedClass) ^ this.getClass().hashCode(); ++ } ++ ++ @Override ++ public boolean equals(Object obj) { ++ return obj.getClass() == this.getClass() && ++ this.clazz == ((SectionedEntityMovementTracker) obj).clazz && ++ this.trackedWorldSections.equals(((SectionedEntityMovementTracker) obj).trackedWorldSections); ++ } ++ ++ /** ++ * Method to quickly check whether any relevant entities moved inside the relevant entity sections after ++ * the last interaction attempt. ++ * ++ * @param lastCheckedTime time of the last interaction attempt ++ * @return whether any relevant entity moved in the tracked area ++ */ ++ public boolean isUnchangedSince(long lastCheckedTime) { ++ if (lastCheckedTime <= this.maxChangeTime) { ++ return false; ++ } ++ ArrayList sectionChangeCounters = this.sectionChangeCounters; ++ int trackedClass = this.trackedClass; ++ //noinspection ForLoopReplaceableByForEach ++ for (int i = 0, numCounters = sectionChangeCounters.size(); i < numCounters; i++) { ++ // >= instead of > is required here, as changes may occur in the same tick but after the last check ++ long sectionChangeTime = sectionChangeCounters.get(i)[trackedClass]; ++ if (lastCheckedTime <= sectionChangeTime) { ++ this.setChanged(sectionChangeTime); ++ return false; ++ } ++ } ++ return true; ++ } ++ ++ public void register(ServerLevel world) { ++ assert world == this.trackedWorldSections.world(); ++ ++ if (this.timesRegistered == 0) { ++ //noinspection unchecked ++ EntitySectionStorage cache = world.entityManager.getCache(); ++ ++ this.sectionChangeCounters = new ArrayList<>(); ++ WorldSectionBox trackedSections = this.trackedWorldSections; ++ int size = trackedSections.numSections(); ++ assert size > 0; ++ this.sortedSections = new ArrayList<>(size); ++ this.sectionVisible = new boolean[size]; ++ ++ //vanilla iteration order in SectionedEntityCache is xzy ++ //WorldSectionBox upper coordinates are exclusive ++ for (int x = trackedSections.chunkX1(); x < trackedSections.chunkX2(); x++) { ++ for (int z = trackedSections.chunkZ1(); z < trackedSections.chunkZ2(); z++) { ++ for (int y = trackedSections.chunkY1(); y < trackedSections.chunkY2(); y++) { ++ EntitySection section = cache.getOrCreateSection(SectionPos.asLong(x, y, z)); ++ this.sortedSections.add(section); ++ section.addListener(this); ++ } ++ } ++ } ++ this.setChanged(world.getGameTime()); ++ } ++ ++ this.timesRegistered++; ++ } ++ ++ public void unRegister(ServerLevel world) { ++ assert world == this.trackedWorldSections.world(); ++ if (--this.timesRegistered > 0) { ++ return; ++ } ++ assert this.timesRegistered == 0; ++ //noinspection unchecked ++ EntitySectionStorage cache = world.entityManager.getCache(); ++ cache.remove(this); ++ ++ ArrayList> sections = this.sortedSections; ++ for (int i = sections.size() - 1; i >= 0; i--) { ++ EntitySection section = sections.get(i); ++ section.removeListener(cache, this); ++ } ++ this.setChanged(world.getGameTime()); ++ } ++ ++ /** ++ * Register an entity section to this listener, so this listener can look for changes in the section. ++ */ ++ public void onSectionEnteredRange(EntitySection section) { ++ this.setChanged(this.trackedWorldSections.world().getGameTime()); ++ //noinspection SuspiciousMethodCalls ++ this.sectionVisible[this.sortedSections.lastIndexOf(section)] = true; ++ this.sectionChangeCounters.add(section.getMovementTimestampArray()); ++ } ++ ++ public void onSectionLeftRange(EntitySection section) { ++ this.setChanged(this.trackedWorldSections.world().getGameTime()); ++ //noinspection SuspiciousMethodCalls ++ this.sectionVisible[this.sortedSections.indexOf(section)] = false; ++ this.sectionChangeCounters.remove(section.getMovementTimestampArray()); ++ } ++ ++ /** ++ * Method that marks that new entities might have appeared or moved in the tracked chunk sections. ++ */ ++ private void setChanged(long atTime) { ++ if (atTime > this.maxChangeTime) { ++ this.maxChangeTime = atTime; ++ } ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/tuples/Range6Int.java b/src/main/java/me/jellysquid/mods/lithium/common/util/tuples/Range6Int.java +new file mode 100644 +index 0000000000000000000000000000000000000000..673d4b02a4baf779efa64c1d7b4fc284ec0887f9 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/util/tuples/Range6Int.java +@@ -0,0 +1,4 @@ ++package me.jellysquid.mods.lithium.common.util.tuples; ++ ++public record Range6Int(int negativeX, int negativeY, int negativeZ, int positiveX, int positiveY, int positiveZ) { ++} +\ No newline at end of file +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/tuples/WorldSectionBox.java b/src/main/java/me/jellysquid/mods/lithium/common/util/tuples/WorldSectionBox.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ffc44c333f0dbaf07cc2c68ac390e0a7506a979e +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/util/tuples/WorldSectionBox.java +@@ -0,0 +1,24 @@ ++package me.jellysquid.mods.lithium.common.util.tuples; ++ ++import net.minecraft.world.level.Level; ++import net.minecraft.world.phys.AABB; ++import net.minecraft.core.SectionPos; ++ ++//Y values use coordinates, not indices (y=0 -> chunkY=0) ++//upper bounds are EXCLUSIVE ++public record WorldSectionBox(Level world, int chunkX1, int chunkY1, int chunkZ1, int chunkX2, int chunkY2, ++ int chunkZ2) { ++ public static WorldSectionBox entityAccessBox(Level world, AABB box) { ++ int minX = SectionPos.posToSectionCoord((double)box.minX - 2.0D); ++ int minY = SectionPos.posToSectionCoord((double)box.minY - 2.0D); ++ int minZ = SectionPos.posToSectionCoord((double)box.minZ - 2.0D); ++ int maxX = SectionPos.posToSectionCoord((double)box.maxX + 2.0D) + 1; ++ int maxY = SectionPos.posToSectionCoord((double)box.maxY + 2.0D) + 1; ++ int maxZ = SectionPos.posToSectionCoord((double)box.maxZ + 2.0D) + 1; ++ return new WorldSectionBox(world, minX, minY, minZ, maxX, maxY, maxZ); ++ } ++ ++ public int numSections() { ++ return (this.chunkX2 - this.chunkX1) * (this.chunkY2 - this.chunkY1) * (this.chunkZ2 - this.chunkZ1); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 72790d1288ff471b2270ec96a207dd65d81e514d..3009b111a25e901504e8845465574451196fc217 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -147,9 +147,29 @@ import org.bukkit.event.entity.EntityPoseChangeEvent; + import org.bukkit.event.player.PlayerTeleportEvent; + import org.bukkit.plugin.PluginManager; + // CraftBukkit end ++// JettPack start ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityListener; ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityListenerMulti; ++// JettPack end + + public abstract class Entity implements Nameable, EntityAccess, CommandSource, io.papermc.paper.util.KeyedObject { // Paper + ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ private NearbyEntityListenerMulti nearbyTracker = null; ++ ++ @Nullable ++ public NearbyEntityListenerMulti getListener() { ++ return this.nearbyTracker; ++ } ++ ++ public void addListener(NearbyEntityListener listener) { ++ if (this.nearbyTracker == null) { ++ this.nearbyTracker = new NearbyEntityListenerMulti(); ++ } ++ this.nearbyTracker.addListener(listener); ++ } ++ // JettPack end ++ + // CraftBukkit start + private static final int CURRENT_LEVEL = 2; + public boolean preserveMotion = true; // Paper - keep initial motion on first setPositionRotation +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java +index 66e5c2716684f54e15e931e33d09463c0df0fda3..4f5a0d57eba877ee90f35e134496a71ebb127b0c 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java +@@ -11,6 +11,12 @@ import net.minecraft.world.entity.ai.targeting.TargetingConditions; + import net.minecraft.world.entity.ai.util.DefaultRandomPos; + import net.minecraft.world.level.pathfinder.Path; + import net.minecraft.world.phys.Vec3; ++// JettPack start ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityTracker; ++import net.minecraft.world.entity.EntityDimensions; ++import net.minecraft.util.Mth; ++import net.minecraft.core.Vec3i; ++// JettPack end + + public class AvoidEntityGoal extends Goal { + protected final PathfinderMob mob; +@@ -26,6 +32,7 @@ public class AvoidEntityGoal extends Goal { + protected final Predicate avoidPredicate; + protected final Predicate predicateOnAvoidEntity; + private final TargetingConditions avoidEntityTargeting; ++ private NearbyEntityTracker nearbyTracker; // JettPack + + public AvoidEntityGoal(PathfinderMob mob, Class fleeFromType, float distance, double slowSpeed, double fastSpeed) { + this(mob, fleeFromType, (livingEntity) -> { +@@ -44,6 +51,13 @@ public class AvoidEntityGoal extends Goal { + this.pathNav = mob.getNavigation(); + this.setFlags(EnumSet.of(Goal.Flag.MOVE)); + this.avoidEntityTargeting = TargetingConditions.forCombat().range((double)distance).selector(inclusionSelector.and(extraInclusionSelector)); ++ // JettPack start - lithium: ai.nearby_entity_tracking.goals ++ EntityDimensions dimensions = this.mob.getType().getDimensions(); ++ double adjustedRange = dimensions.width * 0.5D + this.maxDist + 2D; ++ int horizontalRange = Mth.ceil(adjustedRange); ++ this.nearbyTracker = new NearbyEntityTracker<>(fleeFromType, mob, new Vec3i(horizontalRange, Mth.ceil(dimensions.height + 3 + 2), horizontalRange)); ++ mob.addListener(this.nearbyTracker); ++ // JettPack end + } + + public AvoidEntityGoal(PathfinderMob fleeingEntity, Class classToFleeFrom, float fleeDistance, double fleeSlowSpeed, double fleeFastSpeed, Predicate inclusionSelector) { +@@ -54,9 +68,7 @@ public class AvoidEntityGoal extends Goal { + + @Override + public boolean canUse() { +- this.toAvoid = this.mob.level.getNearestEntity(this.mob.level.getEntitiesOfClass(this.avoidClass, this.mob.getBoundingBox().inflate((double)this.maxDist, 3.0D, (double)this.maxDist), (livingEntity) -> { +- return true; +- }), this.avoidEntityTargeting, this.mob, this.mob.getX(), this.mob.getY(), this.mob.getZ()); ++ this.toAvoid = this.nearbyTracker.getClosestEntity(this.mob.getBoundingBox().inflate(this.maxDist, 3.0D, this.maxDist), this.avoidEntityTargeting, this.mob.getX(), this.mob.getY(), this.mob.getZ()); // JettPack - lithium: ai.nearby_entity_tracking.goals + if (this.toAvoid == null) { + return false; + } else { +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java +index 86eee67353d39b46820b1cb1008444a156ee6def..22252735b065f481797aa584f1a37c0eb07d8ffe 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java +@@ -8,6 +8,12 @@ import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.Mob; + import net.minecraft.world.entity.ai.targeting.TargetingConditions; + import net.minecraft.world.entity.player.Player; ++// JettPack start ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityTracker; ++import net.minecraft.core.Vec3i; ++import net.minecraft.util.Mth; ++import net.minecraft.world.entity.EntityDimensions; ++// JettPack end + + public class LookAtPlayerGoal extends Goal { + public static final float DEFAULT_PROBABILITY = 0.02F; +@@ -20,6 +26,7 @@ public class LookAtPlayerGoal extends Goal { + private final boolean onlyHorizontal; + protected final Class lookAtType; + protected final TargetingConditions lookAtContext; ++ private NearbyEntityTracker nearbyTracker; // JettPack + + public LookAtPlayerGoal(Mob mob, Class targetType, float range) { + this(mob, targetType, range, 0.02F); +@@ -43,7 +50,14 @@ public class LookAtPlayerGoal extends Goal { + } else { + this.lookAtContext = TargetingConditions.forNonCombat().range((double)range); + } ++ // JettPack start - lithium: ai.nearby_entity_tracking.goals ++ EntityDimensions dimensions = this.mob.getType().getDimensions(); ++ double adjustedRange = dimensions.width * 0.5D + this.lookDistance + 2D; ++ int horizontalRange = Mth.ceil(adjustedRange); ++ this.nearbyTracker = new NearbyEntityTracker<>(targetType, mob, new Vec3i(horizontalRange, Mth.ceil(dimensions.height + 3 + 2), horizontalRange)); + ++ mob.addListener(this.nearbyTracker); ++ // JettPack end + } + + @Override +@@ -56,11 +70,9 @@ public class LookAtPlayerGoal extends Goal { + } + + if (this.lookAtType == Player.class) { +- this.lookAt = this.mob.level.getNearestPlayer(this.lookAtContext, this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); ++ this.lookAt = this.nearbyTracker.getClosestEntity(null, this.lookAtContext, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); // JettPack - lithium: ai.nearby_entity_tracking.goals + } else { +- this.lookAt = this.mob.level.getNearestEntity(this.mob.level.getEntitiesOfClass(this.lookAtType, this.mob.getBoundingBox().inflate((double)this.lookDistance, 3.0D, (double)this.lookDistance), (livingEntity) -> { +- return true; +- }), this.lookAtContext, this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); ++ this.lookAt = this.nearbyTracker.getClosestEntity(this.mob.getBoundingBox().inflate(this.lookDistance, 3.0D, this.lookDistance), this.lookAtContext, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); // JettPack - lithium: ai.nearby_entity_tracking.goals + } + + return this.lookAt != null; +diff --git a/src/main/java/net/minecraft/world/level/entity/EntitySection.java b/src/main/java/net/minecraft/world/level/entity/EntitySection.java +index c9cb4fa8c96ca53a0692aee6ce0cf46a9466bec1..cd5576f1c39da04642211f265623e38aa14712f1 100644 +--- a/src/main/java/net/minecraft/world/level/entity/EntitySection.java ++++ b/src/main/java/net/minecraft/world/level/entity/EntitySection.java +@@ -12,6 +12,13 @@ import net.minecraft.world.level.entity.EntityTypeTest; // Mirai + import net.minecraft.world.phys.AABB; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++// JettPack start ++import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; ++import me.jellysquid.mods.lithium.common.entity.tracker.EntityTrackerEngine; ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityListener; ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.SectionedEntityMovementTracker; ++import net.minecraft.world.entity.Entity; ++// JettPack end + + public class EntitySection { + protected static final Logger LOGGER = LogManager.getLogger(); +@@ -22,6 +29,70 @@ public class EntitySection { + public int inventoryEntityCount; + // Paper end + ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ private final ReferenceOpenHashSet nearbyEntityListeners = new ReferenceOpenHashSet<>(0); ++ private final ReferenceOpenHashSet> sectionVisibilityListeners = new ReferenceOpenHashSet<>(0); ++ private final long[] lastEntityMovementByType = new long[EntityTrackerEngine.NUM_MOVEMENT_NOTIFYING_CLASSES]; ++ private long pos; ++ ++ public void addListener(NearbyEntityListener listener) { ++ this.nearbyEntityListeners.add(listener); ++ if (this.chunkStatus.isAccessible()) { ++ listener.onSectionEnteredRange(this, this.storage); ++ } ++ } ++ ++ public void removeListener(EntitySectionStorage sectionedEntityCache, NearbyEntityListener listener) { ++ boolean removed = this.nearbyEntityListeners.remove(listener); ++ if (this.chunkStatus.isAccessible() && removed) { ++ listener.onSectionLeftRange(this, this.storage); ++ } ++ if (this.isEmpty()) { ++ sectionedEntityCache.remove(this.pos); ++ } ++ } ++ ++ public void addListener(SectionedEntityMovementTracker listener) { ++ this.sectionVisibilityListeners.add(listener); ++ if (this.chunkStatus.isAccessible()) { ++ listener.onSectionEnteredRange(this); ++ } ++ } ++ ++ public void removeListener(EntitySectionStorage sectionedEntityCache, SectionedEntityMovementTracker listener) { ++ boolean removed = this.sectionVisibilityListeners.remove(listener); ++ if (this.chunkStatus.isAccessible() && removed) { ++ listener.onSectionLeftRange(this); ++ } ++ if (this.isEmpty()) { ++ sectionedEntityCache.remove(this.pos); ++ } ++ } ++ ++ public void updateMovementTimestamps(int notificationMask, long time) { ++ long[] lastEntityMovementByType = this.lastEntityMovementByType; ++ int size = lastEntityMovementByType.length; ++ int mask; ++ for (int i = Integer.numberOfTrailingZeros(notificationMask); i < size; ) { ++ lastEntityMovementByType[i] = time; ++ mask = 0xffff_fffe << i; ++ i = Integer.numberOfTrailingZeros(notificationMask & mask); ++ } ++ } ++ ++ public long[] getMovementTimestampArray() { ++ return this.lastEntityMovementByType; ++ } ++ ++ public void setPos(long chunkSectionPos) { ++ this.pos = chunkSectionPos; ++ } ++ ++ public long getPos() { ++ return this.pos; ++ } ++ // JettPack end ++ + public EntitySection(Class entityClass, Visibility status) { + this.chunkStatus = status; + this.storage = new ClassInstanceMultiMap<>(entityClass); +@@ -36,6 +107,16 @@ public class EntitySection { + } + // Paper end + this.storage.add(entity); ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ if (!this.chunkStatus.isAccessible() || this.nearbyEntityListeners.isEmpty()) { ++ return; ++ } ++ if (entity instanceof Entity entity1) { ++ for (NearbyEntityListener nearbyEntityListener : this.nearbyEntityListeners) { ++ nearbyEntityListener.onEntityEnteredRange(entity1); ++ } ++ } ++ // JettPack end + } + + public boolean remove(T entity) { +@@ -46,6 +127,13 @@ public class EntitySection { + this.inventoryEntityCount--; + } + // Paper end ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ if (this.chunkStatus.isAccessible() && !this.nearbyEntityListeners.isEmpty() && entity instanceof Entity entity1) { ++ for (NearbyEntityListener nearbyEntityListener : this.nearbyEntityListeners) { ++ nearbyEntityListener.onEntityLeftRange(entity1); ++ } ++ } ++ // JettPack end + return this.storage.remove(entity); + } + +@@ -107,6 +195,7 @@ public class EntitySection { + // Mirai end + + public boolean isEmpty() { ++ if (!this.nearbyEntityListeners.isEmpty() || !this.sectionVisibilityListeners.isEmpty()) return false; // JettPack + return this.storage.isEmpty(); + } + +@@ -119,6 +208,33 @@ public class EntitySection { + } + + public Visibility updateChunkStatus(Visibility status) { ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ if (this.chunkStatus.isAccessible() != status.isAccessible()) { ++ if (!status.isAccessible()) { ++ if (!this.nearbyEntityListeners.isEmpty()) { ++ for (NearbyEntityListener nearbyEntityListener : this.nearbyEntityListeners) { ++ nearbyEntityListener.onSectionLeftRange(this, this.storage); ++ } ++ } ++ if (!this.sectionVisibilityListeners.isEmpty()) { ++ for (SectionedEntityMovementTracker listener : this.sectionVisibilityListeners) { ++ listener.onSectionLeftRange(this); ++ } ++ } ++ } else { ++ if (!this.nearbyEntityListeners.isEmpty()) { ++ for (NearbyEntityListener nearbyEntityListener : this.nearbyEntityListeners) { ++ nearbyEntityListener.onSectionEnteredRange(this, this.storage); ++ } ++ } ++ if (!this.sectionVisibilityListeners.isEmpty()) { ++ for (SectionedEntityMovementTracker listener : this.sectionVisibilityListeners) { ++ listener.onSectionEnteredRange(this); ++ } ++ } ++ } ++ } ++ // JettPack end + Visibility visibility = this.chunkStatus; + this.chunkStatus = status; + return visibility; +diff --git a/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java b/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java +index aae1f6d5d0e47363042657d755f6bfeea2420347..2ed8ab49b4a37876416aa974bd49915b9f0e3fa0 100644 +--- a/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java +@@ -20,6 +20,10 @@ import net.minecraft.core.SectionPos; + import net.minecraft.util.VisibleForDebug; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.phys.AABB; ++// JettPack start ++import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.SectionedEntityMovementTracker; ++// JettPack end + + public class EntitySectionStorage { + private final Class entityClass; +@@ -71,6 +75,20 @@ public class EntitySectionStorage { + } + // JettPack end + ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ private final Object2ReferenceOpenHashMap, SectionedEntityMovementTracker> sectionEntityMovementTrackers = new Object2ReferenceOpenHashMap<>(); ++ ++ public void remove(SectionedEntityMovementTracker tracker) { ++ this.sectionEntityMovementTrackers.remove(tracker); ++ } ++ ++ public > S deduplicate(S tracker) { ++ //noinspection unchecked ++ S storedTracker = (S) this.sectionEntityMovementTrackers.putIfAbsent(tracker, tracker); ++ return storedTracker == null ? tracker : storedTracker; ++ } ++ // JettPack end ++ + public LongStream getExistingSectionPositionsInChunk(long chunkPos) { + int i = ChunkPos.getX(chunkPos); + int j = ChunkPos.getZ(chunkPos); +@@ -98,7 +116,11 @@ public class EntitySectionStorage { + } + + public EntitySection getOrCreateSection(long sectionPos) { +- return this.sections.computeIfAbsent(sectionPos, this::createSection); ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ EntitySection section = this.sections.computeIfAbsent(sectionPos, this::createSection); ++ section.setPos(sectionPos); ++ return section; ++ // JettPack end + } + + @Nullable +diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +index a3acd40f24aae3c9809b76ce6f3e1b2d20acc5b1..0b5eaecb223107100ae11d4863698e1f6dc41b16 100644 +--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java ++++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +@@ -35,6 +35,10 @@ import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.chunk.storage.EntityStorage; + import org.bukkit.craftbukkit.event.CraftEventFactory; + // CraftBukkit end ++// JettPack start ++import me.jellysquid.mods.lithium.common.entity.tracker.EntityTrackerEngine; ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityListenerMulti; ++// JettPack end + + public class PersistentEntitySectionManager implements AutoCloseable { + +@@ -61,6 +65,12 @@ public class PersistentEntitySectionManager implements A + this.entityGetter = new LevelEntityGetterAdapter<>(this.visibleEntityStorage, this.sectionStorage); + } + ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ public EntitySectionStorage getCache() { ++ return this.sectionStorage; ++ } ++ // JettPack end ++ + // CraftBukkit start - add method to get all entities in chunk + public List getEntities(ChunkPos chunkCoordIntPair) { + return this.sectionStorage.getExistingSectionsInChunk(chunkCoordIntPair.toLong()).flatMap(EntitySection::getEntities).map(entity -> (Entity) entity).collect(Collectors.toList()); +@@ -177,6 +187,16 @@ public class PersistentEntitySectionManager implements A + entitysection.add(entity); + this.entitySliceManager.addEntity((Entity)entity); // Paper + entity.setLevelCallback(new PersistentEntitySectionManager.Callback(entity, i, entitysection)); ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ NearbyEntityListenerMulti listener = ((Entity)entity).getListener(); ++ if (listener != null) { ++ listener.forEachChunkInRangeChange( ++ this.sectionStorage, ++ null, ++ SectionPos.of(entity.blockPosition()) ++ ); ++ } ++ // JettPack end + if (!existing) { + this.callbacks.onCreated(entity); + } +@@ -519,12 +539,25 @@ public class PersistentEntitySectionManager implements A + private final T entity; + private long currentSectionKey; + private EntitySection currentSection; ++ private int notificationMask; // JettPack + + Callback(EntityAccess entityaccess, long i, EntitySection entitysection) { + this.entity = (T) entityaccess; // CraftBukkit - decompile error + this.currentSectionKey = i; + this.currentSection = entitysection; ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ this.notificationMask = EntityTrackerEngine.getNotificationMask(this.entity.getClass()); ++ this.notifyMovementListeners(); ++ // JettPack end ++ } ++ ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ private void notifyMovementListeners() { ++ if (this.notificationMask != 0) { ++ this.currentSection.updateMovementTimestamps(this.notificationMask, ((Entity) this.entity).getCommandSenderWorld().getGameTime()); ++ } + } ++ // JettPack end + + @Override + public void onMove() { +@@ -566,7 +599,20 @@ public class PersistentEntitySectionManager implements A + } + // Paper end + this.updateStatus(visibility, entitysection.getStatus()); ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ NearbyEntityListenerMulti listener = ((Entity)this.entity).getListener(); ++ if (listener != null) ++ { ++ listener.forEachChunkInRangeChange( ++ PersistentEntitySectionManager.this.entitySliceManager.world.entityManager.getCache(), ++ SectionPos.of(this.currentSectionKey), ++ SectionPos.of(newSectionPos) ++ ); ++ } ++ this.notifyMovementListeners(); ++ // JettPack end + } ++ this.notifyMovementListeners(); // JettPack + + } + +@@ -599,6 +645,18 @@ public class PersistentEntitySectionManager implements A + @Override + public void onRemove(Entity.RemovalReason reason) { + org.spigotmc.AsyncCatcher.catchOp("Entity remove"); // Paper ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ NearbyEntityListenerMulti listener = ((Entity)this.entity).getListener(); ++ if (listener != null) { ++ //noinspection unchecked ++ listener.forEachChunkInRangeChange( ++ PersistentEntitySectionManager.this.entitySliceManager.world.entityManager.getCache(), ++ SectionPos.of(this.currentSectionKey), ++ null ++ ); ++ } ++ this.notifyMovementListeners(); ++ // JettPack end + if (!this.currentSection.remove(this.entity)) { + PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (destroying due to {})", this.entity, SectionPos.of(this.currentSectionKey), reason); + } diff --git a/patches/server/0111-some-entity-micro-opts.patch b/patches/server/0111-some-entity-micro-opts.patch new file mode 100644 index 0000000..4581aab --- /dev/null +++ b/patches/server/0111-some-entity-micro-opts.patch @@ -0,0 +1,82 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Simon Gardling +Date: Fri, 14 Jan 2022 12:00:42 -0500 +Subject: [PATCH] some entity micro opts + +Original code by Titaniumtown, licensed under GNU General Public License v3.0 +You can find the original code on https://gitlab.com/Titaniumtown/JettPack + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index d8baa239c8cec5a67e30e4756ef37d47f4e6e367..5927315477992dd32b6cdc283d0e84dc500ebf37 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1773,10 +1773,16 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i + } + } + ++ // JettPack start - allow passing BlockPos to getBrightness + public float getBrightness() { +- return this.level.hasChunkAt(this.getBlockX(), this.getBlockZ()) ? this.level.getBrightness(new BlockPos(this.getX(), this.getEyeY(), this.getZ())) : 0.0F; ++ return this.getBrightness(new BlockPos(this.getX(), this.getEyeY(), this.getZ())); + } + ++ public float getBrightness(BlockPos pos) { ++ return this.level.hasChunkAt(this.getBlockX(), this.getBlockZ()) ? this.level.getBrightness(pos) : 0.0F; ++ } ++ // JettPack end ++ + public void absMoveTo(double x, double y, double z, float yaw, float pitch) { + this.absMoveTo(x, y, z); + this.setYRot(yaw % 360.0F); +@@ -4142,6 +4148,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i + return this.getZ((2.0D * this.random.nextDouble() - 1.0D) * widthScale); + } + ++ // JettPack start ++ public final int getPositionHashCode() { ++ return this.position.hashCode(); ++ } ++ // JettPack end ++ + public final void setPosRaw(double x, double y, double z) { + // Paper start + this.setPosRaw(x, y, z, false); +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 0e2b3d889953fcc2ff012d923d12cd9afc109776..759bef4e466eeea020c09f653ad971b3c9640e7b 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1617,15 +1617,31 @@ public abstract class Mob extends LivingEntity { + + } + ++ // JettPack start - cache eye blockpos ++ private BlockPos cached_eye_blockpos; ++ private int cached_position_hashcode; ++ // JettPack end + public boolean isSunBurnTick() { + if (this.level.isDay() && !this.level.isClientSide) { +- float f = this.getBrightness(); +- BlockPos blockposition = new BlockPos(this.getX(), this.getEyeY(), this.getZ()); ++ // JettPack start - optimizations and cache eye blockpos ++ int positionHashCode = this.getPositionHashCode(); ++ if (this.cached_position_hashcode != positionHashCode) { ++ this.cached_eye_blockpos = new BlockPos(this.getX(), this.getEyeY(), this.getZ()); ++ this.cached_position_hashcode = positionHashCode; ++ } ++ ++ float f = this.getBrightness(cached_eye_blockpos); // Pass BlockPos to getBrightness ++ ++ // Check brightness first ++ if (f <= 0.5F) return false; ++ if (this.random.nextFloat() * 30.0F >= (f - 0.4F) * 2.0F) return false; ++ + boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow; + +- if (f > 0.5F && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && !flag && this.level.canSeeSky(blockposition)) { ++ if (!flag && this.level.canSeeSky(this.cached_eye_blockpos)) { // JettPack - move brightness checks up + return true; + } ++ // JettPack end + } + + return false;