1061 lines
50 KiB
Diff
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});
|
|
}
|