Files
MiraiMC/patches/server/0072-lithium-ai.nearby_entity_tracking-and-ai.nearby_enti.patch
2022-07-21 19:36:05 +02:00

1061 lines
50 KiB
Diff

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<Class<?>> MOVEMENT_NOTIFYING_ENTITY_CLASSES;
+ public static volatile Reference2IntOpenHashMap<Class<? extends EntityAccess>> 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<? extends EntityAccess> entityClass) {
+ int notificationMask = CLASS_2_NOTIFY_MASK.getInt(entityClass);
+ if (notificationMask == -1) {
+ notificationMask = calculateNotificationMask(entityClass);
+ }
+ return notificationMask;
+ }
+
+ private static int calculateNotificationMask(Class<? extends EntityAccess> 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<Class<? extends EntityAccess>> 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<? extends EntityAccess> 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<? extends EntityAccess> 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<? extends Entity> 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 <T> 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 <T> void onSectionEnteredRange(Object entityTrackingSection, ClassInstanceMultiMap<T> collection) {
+ for (Entity entity : collection.find(this.getEntityClass())) {
+ this.onEntityEnteredRange(entity);
+ }
+ }
+
+ default <T> void onSectionLeftRange(Object entityTrackingSection, ClassInstanceMultiMap<T> 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<NearbyEntityListener> 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<T extends LivingEntity> implements NearbyEntityListener {
+ private final Class<T> clazz;
+ private final LivingEntity self;
+
+ private final Reference2LongOpenHashMap<T> nearbyEntities = new Reference2LongOpenHashMap<>(0);
+ private long counter;
+ private final Range6Int chunkBoxRadius;
+
+ public NearbyEntityTracker(Class<T> 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<? extends Entity> 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<? extends Player> 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<E extends EntityAccess, S> {
+ final WorldSectionBox trackedWorldSections;
+ final Class<S> clazz;
+ private final int trackedClass;
+ ArrayList<EntitySection<Entity>> sortedSections;
+ boolean[] sectionVisible;
+ private int timesRegistered;
+ private ArrayList<long[]> sectionChangeCounters;
+
+ private long maxChangeTime;
+
+ public SectionedEntityMovementTracker(WorldSectionBox interactionChunks, Class<S> 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<long[]> 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<Entity> 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<Entity> 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<Entity> cache = world.entityManager.getCache();
+ cache.remove(this);
+
+ ArrayList<EntitySection<Entity>> sections = this.sortedSections;
+ for (int i = sections.size() - 1; i >= 0; i--) {
+ EntitySection<Entity> 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..178fc249777c9997e9586ffe099e54c9b1e1322a
--- /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 - 4.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) + 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 fb8fb1332b88427b7340fdeeff2024455f73f502..9d453f71b85be15b5889636569f93cc66efd6a52 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -154,9 +154,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 {
+ // 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<T extends LivingEntity> extends Goal {
protected final PathfinderMob mob;
@@ -26,6 +32,7 @@ public class AvoidEntityGoal<T extends LivingEntity> extends Goal {
protected final Predicate<LivingEntity> avoidPredicate;
protected final Predicate<LivingEntity> predicateOnAvoidEntity;
private final TargetingConditions avoidEntityTargeting;
+ private NearbyEntityTracker<T> nearbyTracker; // JettPack
public AvoidEntityGoal(PathfinderMob mob, Class<T> fleeFromType, float distance, double slowSpeed, double fastSpeed) {
this(mob, fleeFromType, (livingEntity) -> {
@@ -44,6 +51,13 @@ public class AvoidEntityGoal<T extends LivingEntity> 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<T> classToFleeFrom, float fleeDistance, double fleeSlowSpeed, double fleeFastSpeed, Predicate<LivingEntity> inclusionSelector) {
@@ -54,9 +68,7 @@ public class AvoidEntityGoal<T extends LivingEntity> 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 8b189d7587303263efa1790066e5a83edd45f9d7..352662385884e0c818961171b2c7f3ca5df00942 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<? extends LivingEntity> lookAtType;
protected final TargetingConditions lookAtContext;
+ private NearbyEntityTracker<? extends LivingEntity> nearbyTracker; // JettPack
public LookAtPlayerGoal(Mob mob, Class<? extends LivingEntity> targetType, float range) {
this(mob, targetType, range, 0.02F);
@@ -43,6 +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
}
@@ -56,11 +71,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 13ebaf6aede402f5f702aae6c1e44445b00cd9bb..4bca54ab894b71f433d9f5318ed8363bea046983 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 com.ishland.vmp.common.general.collections.ITypeFilterableList; // Mirai
import it.unimi.dsi.fastutil.objects.ObjectArrayList; // Mirai
import net.minecraft.world.level.entity.EntityAccess; // Mirai
import net.minecraft.world.level.entity.EntityTypeTest; // Mirai
+// 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<T extends EntityAccess> {
private static final Logger LOGGER = LogUtils.getLogger();
@@ -21,6 +28,69 @@ public class EntitySection<T extends EntityAccess> {
public int itemCount;
public int inventoryEntityCount;
// Paper end
+ // JettPack start - lithium: ai.nearby_entity_tracking
+ private final ReferenceOpenHashSet<NearbyEntityListener> nearbyEntityListeners = new ReferenceOpenHashSet<>(0);
+ private final ReferenceOpenHashSet<SectionedEntityMovementTracker<?, ?>> 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<T> entityClass, Visibility status) {
this.chunkStatus = status;
@@ -36,6 +106,16 @@ public class EntitySection<T extends EntityAccess> {
}
// 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 +126,13 @@ public class EntitySection<T extends EntityAccess> {
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 +194,7 @@ public class EntitySection<T extends EntityAccess> {
// Mirai end
public boolean isEmpty() {
+ if (!this.nearbyEntityListeners.isEmpty() || !this.sectionVisibilityListeners.isEmpty()) return false; // JettPack
return this.storage.isEmpty();
}
@@ -119,6 +207,33 @@ public class EntitySection<T extends EntityAccess> {
}
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 ad12d9660cd57d147859a0e3e123c5b87c8e9748..df076e32d98813f94d68f9fee9afc90e4eeb11fd 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<T extends EntityAccess> {
private final Class<T> entityClass;
@@ -103,6 +107,20 @@ public class EntitySectionStorage<T extends EntityAccess> {
}
// Mirai end
+ // JettPack start - lithium: ai.nearby_entity_tracking
+ private final Object2ReferenceOpenHashMap<SectionedEntityMovementTracker<?, ?>, SectionedEntityMovementTracker<?, ?>> sectionEntityMovementTrackers = new Object2ReferenceOpenHashMap<>();
+
+ public void remove(SectionedEntityMovementTracker<?, ?> tracker) {
+ this.sectionEntityMovementTrackers.remove(tracker);
+ }
+
+ public <S extends SectionedEntityMovementTracker<?, ?>> 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);
@@ -130,7 +148,11 @@ public class EntitySectionStorage<T extends EntityAccess> {
}
public EntitySection<T> getOrCreateSection(long sectionPos) {
- return this.sections.computeIfAbsent(sectionPos, this::createSection);
+ // JettPack start - lithium: ai.nearby_entity_tracking
+ EntitySection<T> 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 30c4974b4019d67cffabd3e686c782659def3ba6..93d71c17c9904b92c81c4f93bc35a493803280ef 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<T extends EntityAccess> implements AutoCloseable {
@@ -61,6 +65,12 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
this.entityGetter = new LevelEntityGetterAdapter<>(this.visibleEntityStorage, this.sectionStorage);
}
+ // JettPack start - lithium: ai.nearby_entity_tracking
+ public EntitySectionStorage<T> getCache() {
+ return this.sectionStorage;
+ }
+ // JettPack end
+
// CraftBukkit start - add method to get all entities in chunk
public List<Entity> 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<T extends EntityAccess> 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<T extends EntityAccess> implements A
private final T entity;
private long currentSectionKey;
private EntitySection<T> 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<T extends EntityAccess> 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
}
@@ -608,6 +654,18 @@ public class PersistentEntitySectionManager<T extends EntityAccess> 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 {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), reason});
}