diff --git a/PATCHES.md b/PATCHES.md index 2caa99f3..7911896a 100644 --- a/PATCHES.md +++ b/PATCHES.md @@ -430,6 +430,7 @@ # Patches | server | Zombie horse naturally spawn | William Blake Galbreath | | | server | add config for logging login location | Simon Gardling | | | server | dont load chunks for physics | Aikar | | +| server | lithium AI | JellySquid | Hugo Planque | | server | lithium DataTrackerMixin | JellySquid | tr7zw | | server | lithium HashedList | JellySquid | | | server | lithium MixinBox | JellySquid | | diff --git a/patches/removed/server/lithium-ai-tracker.patch b/patches/removed/server/lithium-ai-tracker.patch new file mode 100644 index 00000000..ebbf4c22 --- /dev/null +++ b/patches/removed/server/lithium-ai-tracker.patch @@ -0,0 +1,720 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JellySquid +Date: Sun, 24 Jan 2021 12:17:19 +0100 +Subject: [PATCH] lithium AI + +Co-authored-by: Hugo Planque + +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..23ea99d0ec8622eadadc2073022e59c4aac8dc3a +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/EntityTrackerEngine.java +@@ -0,0 +1,254 @@ ++package me.jellysquid.mods.lithium.common.entity.tracker; ++ ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityListener; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.core.SectionPosition; ++import net.minecraft.world.level.levelgen.structure.StructureBoundingBox; ++ ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Set; ++ ++/** ++ * Tracks the entities within a world and provides 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. ++ */ ++public class EntityTrackerEngine { ++ private final Long2ObjectOpenHashMap sections; ++ private final Reference2ReferenceOpenHashMap> sectionsByEntity; ++ ++ ++ public EntityTrackerEngine() { ++ this.sections = new Long2ObjectOpenHashMap<>(); ++ this.sectionsByEntity = new Reference2ReferenceOpenHashMap<>(); ++ } ++ ++ /** ++ * Called when an entity is added to the world. ++ */ ++ public void onEntityAdded(int x, int y, int z, EntityLiving entity) { ++ if (this.addEntity(x, y, z, entity)) { ++ this.addListener(x, y, z, entity.getListener()); ++ } ++ } ++ ++ /** ++ * Called when an entity is removed from the world. ++ */ ++ public void onEntityRemoved(int x, int y, int z, EntityLiving entity) { ++ if (this.removeEntity(x, y, z, entity)) { ++ this.removeListener(entity.getListener()); ++ } ++ } ++ ++ private boolean addEntity(int x, int y, int z, EntityLiving entity) { ++ return this.getOrCreateList(x, y, z).addTrackedEntity(entity); ++ } ++ ++ private boolean removeEntity(int x, int y, int z, EntityLiving entity) { ++ TrackedEntityList list = this.getList(x, y, z); ++ ++ if (list == null) { ++ return false; ++ } ++ ++ return list.removeTrackedEntity(entity); ++ } ++ ++ private void addListener(int x, int y, int z, NearbyEntityListener listener) { ++ int r = listener.getChunkRange(); ++ ++ if (r == 0) { ++ return; ++ } ++ ++ if (this.sectionsByEntity.containsKey(listener)) { ++ ++ throw new IllegalStateException(errorMessageAlreadyListening(this.sectionsByEntity, listener, SectionPosition.a(x, y, z))); ++ } ++ ++ int yMin = Math.max(0, y - r); ++ int yMax = Math.min(y + r, 15); ++ ++ List all = new ArrayList<>((2 * r + 1) * (yMax - yMin + 1) * (2 * r + 1)); ++ ++ for (int x2 = x - r; x2 <= x + r; x2++) { ++ for (int y2 = yMin; y2 <= yMax; y2++) { ++ for (int z2 = z - r; z2 <= z + r; z2++) { ++ TrackedEntityList list = this.getOrCreateList(x2, y2, z2); ++ list.addListener(listener); ++ ++ all.add(list); ++ } ++ } ++ } ++ ++ this.sectionsByEntity.put(listener, all); ++ } ++ ++ private void removeListener(NearbyEntityListener listener) { ++ int r = listener.getChunkRange(); ++ ++ if (r == 0) { ++ return; ++ } ++ ++ List all = this.sectionsByEntity.remove(listener); ++ ++ if (all != null) { ++ for (TrackedEntityList list : all) { ++ list.removeListener(listener); ++ } ++ } else { ++ throw new IllegalArgumentException("Entity listener not tracked:" + listener.toString()); ++ } ++ } ++ ++ // Faster implementation which avoids removing from/adding to every list twice on an entity move event ++ private void moveListener(int aX, int aY, int aZ, int bX, int bY, int bZ, NearbyEntityListener listener) { ++ int radius = listener.getChunkRange(); ++ ++ if (radius == 0) { ++ return; ++ } ++ ++ StructureBoundingBox before = new StructureBoundingBox(aX - radius, aY - radius, aZ - radius, aX + radius, aY + radius, aZ + radius); ++ StructureBoundingBox after = new StructureBoundingBox(aX - radius, aY - radius, aZ - radius, bX + radius, bY + radius, bZ + radius); ++ ++ StructureBoundingBox merged = new StructureBoundingBox(before); ++ merged.c(after); ++ ++ BlockPosition.MutableBlockPosition pos = new BlockPosition.MutableBlockPosition(); ++ ++ for (int x = merged.a; x <= merged.d; x++) { ++ for (int y = merged.b; y <= merged.e; y++) { ++ for (int z = merged.c; z <= merged.f; z++) { ++ pos.setValues(x, y, z); ++ ++ boolean leaving = before.hasPoint(pos); ++ boolean entering = after.hasPoint(pos); ++ ++ // Nothing to change ++ if (leaving == entering) { ++ continue; ++ } ++ ++ if (leaving) { ++ // The listener has left the chunk ++ TrackedEntityList list = this.getList(x, y, z); ++ ++ if (list == null) { ++ throw new IllegalStateException("Expected there to be a listener list while moving entity but there was none"); ++ } ++ ++ list.removeListener(listener); ++ } else { ++ // The listener has entered the chunk ++ TrackedEntityList list = this.getOrCreateList(x, y, z); ++ list.addListener(listener); ++ } ++ } ++ } ++ } ++ } ++ ++ private TrackedEntityList getOrCreateList(int x, int y, int z) { ++ return this.sections.computeIfAbsent(encode(x, y, z), TrackedEntityList::new); ++ } ++ ++ private TrackedEntityList getList(int x, int y, int z) { ++ return this.sections.get(encode(x, y, z)); ++ } ++ ++ private static long encode(int x, int y, int z) { ++ return SectionPosition.asLong(x, y, z); ++ } ++ ++ private static SectionPosition decode(long xyz) { ++ return SectionPosition.a(xyz); ++ } ++ ++ private class TrackedEntityList { ++ private final Set entities = new ReferenceOpenHashSet<>(); ++ private final Set listeners = new ReferenceOpenHashSet<>(); ++ ++ private final long key; ++ ++ private TrackedEntityList(long key) { ++ this.key = key; ++ } ++ ++ public void addListener(NearbyEntityListener listener) { ++ for (EntityLiving entity : this.entities) { ++ listener.onEntityEnteredRange(entity); ++ } ++ ++ this.listeners.add(listener); ++ } ++ ++ public void removeListener(NearbyEntityListener listener) { ++ if (this.listeners.remove(listener)) { ++ for (EntityLiving entity : this.entities) { ++ listener.onEntityLeftRange(entity); ++ } ++ ++ this.checkEmpty(); ++ } ++ } ++ ++ public boolean addTrackedEntity(EntityLiving entity) { ++ for (NearbyEntityListener listener : this.listeners) { ++ listener.onEntityEnteredRange(entity); ++ } ++ ++ return this.entities.add(entity); ++ } ++ ++ public boolean removeTrackedEntity(EntityLiving entity) { ++ boolean ret = this.entities.remove(entity); ++ ++ if (ret) { ++ for (NearbyEntityListener listener : this.listeners) { ++ listener.onEntityLeftRange(entity); ++ } ++ ++ this.checkEmpty(); ++ } ++ ++ return ret; ++ } ++ ++ private void checkEmpty() { ++ if (this.entities.isEmpty() && this.listeners.isEmpty()) { ++ EntityTrackerEngine.this.sections.remove(this.key); ++ } ++ } ++ } ++ ++ ++ private static String errorMessageAlreadyListening(Reference2ReferenceOpenHashMap> sectionsByEntity, NearbyEntityListener listener, SectionPosition newLocation) { ++ StringBuilder builder = new StringBuilder(); ++ builder.append("Adding Entity listener a second time: ").append(listener.toString()); ++ builder.append("\n"); ++ builder.append(" wants to listen at: ").append(newLocation.toString()); ++ builder.append(" with cube radius: ").append(listener.getChunkRange()); ++ builder.append("\n"); ++ builder.append(" but was already listening at chunk sections: "); ++ String[] comma = new String[]{""}; ++ if (sectionsByEntity.get(listener) == null) { ++ builder.append("null"); ++ } else { ++ sectionsByEntity.get(listener).forEach(a -> { ++ builder.append(comma[0]); ++ builder.append(decode(a.key).toString()); ++ comma[0] = ", "; ++ }); ++ } ++ return builder.toString(); ++ } ++} +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..f84366ad98333c2b17b838883d9a3889572bba63 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityListener.java +@@ -0,0 +1,25 @@ ++package me.jellysquid.mods.lithium.common.entity.tracker.nearby; ++ ++import net.minecraft.world.entity.EntityLiving; ++ ++/** ++ * The main interface used to receive events from the ++ * {@link me.jellysquid.mods.lithium.common.entity.tracker.EntityTrackerEngine} of a world. ++ */ ++public interface NearbyEntityListener { ++ /** ++ * Returns the range (in chunks) of this listener. This must never change during the lifetime of the listener. ++ * TODO: Allow entity listeners to change the radius they receive updates within ++ */ ++ int getChunkRange(); ++ ++ /** ++ * Called by the entity tracker when an entity enters the range of this listener. ++ */ ++ void onEntityEnteredRange(EntityLiving entity); ++ ++ /** ++ * Called by the entity tracker when an entity leaves the range of this listener or is removed from the world. ++ */ ++ void onEntityLeftRange(EntityLiving entity); ++} +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..a56b23f0fbc1f2e31a79aa8b47635fecdf2490c9 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityListenerMulti.java +@@ -0,0 +1,59 @@ ++package me.jellysquid.mods.lithium.common.entity.tracker.nearby; ++ ++import net.minecraft.world.entity.EntityLiving; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++/** ++ * Allows for multiple listeners on an entity to be grouped under one logical listener. No guarantees are made about the ++ * order of which each sub-listener will be notified. ++ */ ++public class NearbyEntityListenerMulti implements NearbyEntityListener { ++ private final List listeners = new ArrayList<>(); ++ ++ public void addListener(NearbyEntityListener listener) { ++ this.listeners.add(listener); ++ } ++ ++ public void removeListener(NearbyEntityListener listener) { ++ this.listeners.remove(listener); ++ } ++ ++ @Override ++ public int getChunkRange() { ++ int range = 0; ++ ++ for (NearbyEntityListener listener : this.listeners) { ++ range = Math.max(range, listener.getChunkRange()); ++ } ++ ++ return range; ++ } ++ ++ @Override ++ public void onEntityEnteredRange(EntityLiving entity) { ++ for (NearbyEntityListener listener : this.listeners) { ++ listener.onEntityEnteredRange(entity); ++ } ++ } ++ ++ @Override ++ public void onEntityLeftRange(EntityLiving 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..01f9a626e761dd8cc26216e316e3a39362dc463d +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityTracker.java +@@ -0,0 +1,94 @@ ++package me.jellysquid.mods.lithium.common.entity.tracker.nearby; ++ ++import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; ++import net.minecraft.world.phys.AxisAlignedBB; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.util.MathHelper; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.world.entity.ai.targeting.PathfinderTargetCondition; ++ ++import java.util.Set; ++ ++/** ++ * Maintains a collection of all entities within the range of this listener. This allows AI goals to quickly ++ * assess nearby entities which match the provided class. ++ */ ++public class NearbyEntityTracker implements NearbyEntityListener { ++ private final Class clazz; ++ private final EntityLiving self; ++ ++ private final int rangeC; ++ private final float rangeSq; ++ ++ private final Set nearby = new ReferenceOpenHashSet<>(); ++ ++ public NearbyEntityTracker(Class clazz, EntityLiving self, float range) { ++ this.clazz = clazz; ++ this.self = self; ++ this.rangeSq = range * range; ++ this.rangeC = Math.max((MathHelper.f(range) + 15) >> 4, 1); ++ } ++ ++ @Override ++ public int getChunkRange() { ++ return this.rangeC; ++ } ++ ++ @SuppressWarnings("unchecked") ++ @Override ++ public void onEntityEnteredRange(EntityLiving entity) { ++ if (!this.clazz.isInstance(entity)) { ++ return; ++ } ++ ++ this.nearby.add((T) entity); ++ } ++ ++ @SuppressWarnings("unchecked") ++ @Override ++ public void onEntityLeftRange(EntityLiving entity) { ++ if (this.nearby.isEmpty() || !this.clazz.isInstance(entity)) { ++ return; ++ } ++ ++ this.nearby.remove((T) 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 ++ * @return the closest Entity that meets all requirements (distance, box intersection, predicate, type T) ++ */ ++ public T getClosestEntity(AxisAlignedBB box, PathfinderTargetCondition targetPredicate) { ++ double x = this.self.locX(); ++ double y = this.self.locY(); ++ double z = this.self.locZ(); ++ ++ T nearest = null; ++ double nearestDistance = Double.POSITIVE_INFINITY; ++ ++ for (T entity : this.nearby) { ++ double distance = entity.getDistanceSquared(x, y, z); ++ ++ if (distance < nearestDistance && (box == null || box.intersects(entity.getBoundingBox())) && targetPredicate.test(this.self, entity)) { ++ nearest = entity; ++ nearestDistance = distance; ++ } ++ } ++ ++ if (nearestDistance <= this.rangeSq) { ++ return nearest; ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public String toString() { ++ return super.toString() + " for entity class: " + this.clazz.getName() + ", in rangeSq: " + this.rangeSq + ", around entity: " + this.self.toString() + " with NBT: " + this.self.save(new NBTTagCompound()); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index d7f95bd32842fdee0ce53fc97d31ffb3577cdc78..e6f85b07f731859c8b8c380afad699ccc1d6c3e8 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -177,6 +177,7 @@ import org.bukkit.event.world.TimeSkipEvent; + // CraftBukkit end + import it.unimi.dsi.fastutil.ints.IntArrayList; // Tuinity + import net.gegy1000.tictacs.NonBlockingWorldAccess; // Yatopia ++import me.jellysquid.mods.lithium.common.entity.tracker.EntityTrackerEngine; // Yatopia + + public class WorldServer extends World implements GeneratorAccessSeed, NonBlockingWorldAccess { // Yatopia + +@@ -2031,6 +2032,16 @@ public class WorldServer extends World implements GeneratorAccessSeed, NonBlocki + while (iterator.hasNext()) { + Entity entity = (Entity) iterator.next(); + ++ if (entity instanceof EntityLiving) { // Yatopia start - Port lithium ++ ++ int chunkX = MathHelper.floor(entity.locX()) >> 4; ++ int chunkY = MathHelper.clamp(MathHelper.floor(entity.locY()) >> 4, 0, 15); ++ int chunkZ = MathHelper.floor(entity.locZ()) >> 4; ++ ++ EntityTrackerEngine tracker = this.getEntityTracker(); ++ tracker.onEntityRemoved(chunkX, chunkY, chunkZ, (EntityLiving) entity); ++ } // Yatopia End ++ + if (!(entity instanceof EntityPlayer)) { + if (false && this.tickingEntities) { // Tuinity + throw (IllegalStateException) SystemUtils.c((Throwable) (new IllegalStateException("Removing entity while ticking!"))); +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index cd1f94e5c1c923ee9d8dd451406ac2bee360e9c3..7befe4263a2d046922438e1a9853f2d8290ee230 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -177,6 +177,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + private CraftEntity bukkitEntity; + + public PlayerChunkMap.EntityTracker tracker; // Paper package private -> public ++ public PlayerChunkMap.EntityTracker getTracker() { return tracker; } // Yatopia + public boolean collisionLoadChunks = false; // Paper + public Throwable addedToWorldStack; // Paper - entity debug + public CraftEntity getBukkitEntity() { +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index a057be22f488f76b926c81dc5c63e7f9c3fb54a1..a897ad3de8c18be8bb3c320950a7421477040e8a 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -141,9 +141,11 @@ import org.bukkit.event.player.PlayerItemConsumeEvent; + // CraftBukkit end + + import net.gegy1000.tictacs.NonBlockingWorldAccess; // Yatopia ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityListenerMulti; // Yatopia + + public abstract class EntityLiving extends Entity { + ++ private NearbyEntityListenerMulti tracker; // Yatopia - Port lithium + private static final UUID b = UUID.fromString("662A6B8D-DA3E-4C1C-8813-96EA6097278D"); + private static final UUID c = UUID.fromString("87f46a96-686f-4796-b035-22e16ee9e038"); + private static final AttributeModifier d = new AttributeModifier(EntityLiving.b, "Sprinting speed boost", 0.30000001192092896D, AttributeModifier.Operation.MULTIPLY_TOTAL); +@@ -277,8 +279,14 @@ public abstract class EntityLiving extends Entity { + DynamicOpsNBT dynamicopsnbt = DynamicOpsNBT.a; + + this.bg = this.a(new Dynamic(dynamicopsnbt, dynamicopsnbt.createMap((Map) ImmutableMap.of(dynamicopsnbt.createString("memories"), dynamicopsnbt.emptyMap())))); ++ this.tracker = new NearbyEntityListenerMulti(); // Yatopia - Port lithium + } + ++ // Yatopia start - Port lithium ++ public NearbyEntityListenerMulti getListener() { ++ return this.tracker; ++ } ++ // Yatopia end + protected void initAttributes() {} // Purpur + + public BehaviorController getBehaviorController() { +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalAvoidTarget.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalAvoidTarget.java +index 35502bd2f7d9cebf5cfe1060e300a5032dbe6a5d..eea1a396f06e8feaa5637ba4e589a13169f514da 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalAvoidTarget.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalAvoidTarget.java +@@ -1,5 +1,7 @@ + package net.minecraft.world.entity.ai.goal; + ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityTracker; ++ + import java.util.EnumSet; + import java.util.function.Predicate; + import net.minecraft.world.entity.Entity; +@@ -25,6 +27,7 @@ public class PathfinderGoalAvoidTarget extends Pathfinde + protected final Predicate g; + protected final Predicate h; + private final PathfinderTargetCondition k; ++ private NearbyEntityTracker tracker; // Yatopia - Port Lithium + + public PathfinderGoalAvoidTarget(EntityCreature entitycreature, Class oclass, float f, double d0, double d1) { + this(entitycreature, oclass, entityliving -> true, f, d0, d1, IEntitySelector.e::test); // Purpur - decompile fix +@@ -41,6 +44,10 @@ public class PathfinderGoalAvoidTarget extends Pathfinde + this.e = entitycreature.getNavigation(); + this.a(EnumSet.of(PathfinderGoal.Type.MOVE)); + this.k = (new PathfinderTargetCondition()).a((double) f).a(predicate1.and(predicate)); ++ // Yatopia start - Port Lithium ++ this.tracker = new NearbyEntityTracker<>(oclass, entitycreature, f); ++ entitycreature.getListener().addListener(this.tracker); ++ // Yatopia end + } + + public PathfinderGoalAvoidTarget(EntityCreature entitycreature, Class oclass, float f, double d0, double d1, Predicate predicate) { +@@ -51,7 +58,7 @@ public class PathfinderGoalAvoidTarget extends Pathfinde + + @Override + public boolean a() { +- this.b = this.a.world.b(this.f, this.k, this.a, this.a.locX(), this.a.locY(), this.a.locZ(), this.a.getBoundingBox().grow((double) this.c, 3.0D, (double) this.c)); ++ this.b = this.tracker.getClosestEntity(this.a.getBoundingBox().grow((double) this.c, 3.0D, (double) this.c), this.k); // Yatopia - Port lithium + if (this.b == null) { + return false; + } else { +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalLookAtPlayer.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalLookAtPlayer.java +index 3bcbad5e298cf05c1b41476a08a3a69cb7fdf79f..8b41274aa388a758bd8acf9637e2a19caf744c57 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalLookAtPlayer.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalLookAtPlayer.java +@@ -1,5 +1,6 @@ + package net.minecraft.world.entity.ai.goal; + ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityTracker; + import java.util.EnumSet; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityInsentient; +@@ -10,6 +11,7 @@ import net.minecraft.world.entity.player.EntityHuman; + + public class PathfinderGoalLookAtPlayer extends PathfinderGoal { + ++ private NearbyEntityTracker tracker; // Yatopia - Port lithium + protected final EntityInsentient a; + protected Entity b; + protected final float c; +@@ -27,7 +29,7 @@ public class PathfinderGoalLookAtPlayer extends PathfinderGoal { + this.e = oclass; + this.c = f; + this.d = f1; +- this.a(EnumSet.of(PathfinderGoal.Type.LOOK)); ++ this.a(EnumSet.of(Type.LOOK)); // Yatopia - Port lithium + if (oclass == EntityHuman.class) { + this.f = (new PathfinderTargetCondition()).a((double) f).b().a().d().a((entityliving) -> { + return IEntitySelector.b(entityinsentient).test(entityliving); +@@ -35,7 +37,10 @@ public class PathfinderGoalLookAtPlayer extends PathfinderGoal { + } else { + this.f = (new PathfinderTargetCondition()).a((double) f).b().a().d(); + } +- ++ // Yatopia start - Port lithium ++ this.tracker = new NearbyEntityTracker<>(oclass, entityinsentient, f); ++ entityinsentient.getListener().addListener(this.tracker); ++ // Yatopia end + } + + @Override +@@ -48,9 +53,9 @@ public class PathfinderGoalLookAtPlayer extends PathfinderGoal { + } + + if (this.e == EntityHuman.class) { +- this.b = this.a.world.a(this.f, this.a, this.a.locX(), this.a.getHeadY(), this.a.locZ()); ++ this.b = this.tracker.getClosestEntity(null, this.f); // Yatopia - Port lithium + } else { +- this.b = this.a.world.b(this.e, this.f, this.a, this.a.locX(), this.a.getHeadY(), this.a.locZ(), this.a.getBoundingBox().grow((double) this.c, 3.0D, (double) this.c)); ++ this.b = this.tracker.getClosestEntity(this.a.getBoundingBox().grow((double) this.c, 3.0D, (double) this.c), this.f); // Yatopia - Port lithium + } + + return this.b != null; +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index f5badbe0dee5c40cf83a5d2993d27ed70ddd2c85..db2b1863b7f46be53839fb3e86870745fb7567fd 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -97,6 +97,7 @@ import org.bukkit.event.block.BlockPhysicsEvent; + // CraftBukkit end + + import net.gegy1000.tictacs.NonBlockingWorldAccess; // Yatopia ++import me.jellysquid.mods.lithium.common.entity.tracker.EntityTrackerEngine; // Yatopia + + public abstract class World implements GeneratorAccess, AutoCloseable, NonBlockingWorldAccess { // Yatopia + +@@ -291,6 +292,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable, NonBlocki + } + + // Tuinity end - optimise checkDespawn ++ private EntityTrackerEngine tracker; // Yatopia - Port lithium + + protected World(WorldDataMutable worlddatamutable, ResourceKey resourcekey, final DimensionManager dimensionmanager, Supplier supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env, java.util.concurrent.Executor executor) { // Paper + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.WorldDataServer) worlddatamutable).getName()); // Spigot +@@ -368,8 +370,14 @@ public abstract class World implements GeneratorAccess, AutoCloseable, NonBlocki + this.keepSpawnInMemory = this.paperConfig.keepSpawnInMemory; // Paper + this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); + this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); ++ this.tracker = new EntityTrackerEngine(); // Yatopia - Port lithium + } + ++ // Yatopia start - Port lithium ++ public EntityTrackerEngine getEntityTracker() { ++ return this.tracker; ++ } // Yatopia end ++ + // Paper start + // ret true if no collision + public final boolean checkEntityCollision(IBlockData data, Entity source, VoxelShapeCollision voxelshapedcollision, +@@ -536,6 +544,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable, NonBlocki + return b(blockposition.getY()); + } + ++ public static boolean isOutOfBuildLimitVertically(int y){ return b(y); } // Yatopia - OBFHELPER + public static boolean b(int i) { + return i < 0 || i >= 256; + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index 2e8eb0bb8fb4f7ce6b92fe01a81327da30e614ae..34af81b75e7927cccc0d4aea1b80ab677ca31795 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -68,6 +68,7 @@ import net.minecraft.world.level.material.FluidTypes; + import net.minecraft.world.phys.AxisAlignedBB; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import net.minecraft.world.entity.EntityLiving; // Yatopia + + public class Chunk implements IChunkAccess { + +@@ -863,6 +864,11 @@ public class Chunk implements IChunkAccess { + if (!this.entitySlices[i].remove(entity)) { // Tuinity - optimise hard colliding entities // Tuinity - entities by class // Tuinity + return; + } ++ // Yatopia start - Port lithium ++ if (entity instanceof EntityLiving) { ++ this.world.getEntityTracker().onEntityAdded(entity.chunkX, entity.chunkY, entity.chunkZ, (EntityLiving) entity); ++ } ++ // Yatopia end + if (entity instanceof EntityItem) { + itemCounts[i]--; + } else if (entity instanceof IInventory) { +@@ -872,6 +878,11 @@ public class Chunk implements IChunkAccess { + this.markDirty(); // Paper + // Paper end + this.entities.remove(entity); // Paper ++ // Yatopia start - Port lithium ++ if (entity instanceof EntityLiving) { ++ this.world.getEntityTracker().onEntityRemoved(entity.chunkX, entity.chunkY, entity.chunkZ, (EntityLiving) entity); ++ } ++ // Yatopia end + } + + public final int getHighestBlockY(HeightMap.Type heightmap_type, int i, int j) { return this.getHighestBlock(heightmap_type, i, j) + 1; } // Paper - sort of an obfhelper, but without -1 +diff --git a/src/main/java/net/pl3x/purpur/controller/ControllerLookWASD.java b/src/main/java/net/pl3x/purpur/controller/ControllerLookWASD.java +index 292821f039d99a03ba4daeb3a941616ef5f6287e..9b7110a805b81906512acc3771217a56a102db67 100644 +--- a/src/main/java/net/pl3x/purpur/controller/ControllerLookWASD.java ++++ b/src/main/java/net/pl3x/purpur/controller/ControllerLookWASD.java +@@ -42,7 +42,7 @@ public class ControllerLookWASD extends ControllerLook { + entity.setHeadRotation(entity.yaw); + entity.pitch = normalizePitch(pitch + pitchOffset); + +- entity.tracker.broadcast(new PacketPlayOutEntity ++ entity.getTracker().broadcast(new PacketPlayOutEntity // Yatopia + .PacketPlayOutRelEntityMoveLook(entity.getId(), + (short) 0, (short) 0, (short) 0, + (byte) MathHelper.d(entity.yaw * 256.0F / 360.0F), diff --git a/patches/removed/server/0065-lithium-AI.patch b/patches/server/0065-lithium-AI.patch similarity index 68% rename from patches/removed/server/0065-lithium-AI.patch rename to patches/server/0065-lithium-AI.patch index 812ed1d1..a6b0a4cf 100644 --- a/patches/removed/server/0065-lithium-AI.patch +++ b/patches/server/0065-lithium-AI.patch @@ -219,463 +219,6 @@ index 0000000000000000000000000000000000000000..4427f2171671896c978908b1c3d72b3f + return blockState.a(TagsBlock.FIRE) || blockState.a(Blocks.LAVA) || blockState.a(Blocks.MAGMA_BLOCK) || BlockCampfire.g(blockState); + } +} -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..23ea99d0ec8622eadadc2073022e59c4aac8dc3a ---- /dev/null -+++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/EntityTrackerEngine.java -@@ -0,0 +1,254 @@ -+package me.jellysquid.mods.lithium.common.entity.tracker; -+ -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityListener; -+import net.minecraft.core.BlockPosition; -+import net.minecraft.world.entity.EntityLiving; -+import net.minecraft.core.SectionPosition; -+import net.minecraft.world.level.levelgen.structure.StructureBoundingBox; -+ -+import java.util.ArrayList; -+import java.util.List; -+import java.util.Set; -+ -+/** -+ * Tracks the entities within a world and provides 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. -+ */ -+public class EntityTrackerEngine { -+ private final Long2ObjectOpenHashMap sections; -+ private final Reference2ReferenceOpenHashMap> sectionsByEntity; -+ -+ -+ public EntityTrackerEngine() { -+ this.sections = new Long2ObjectOpenHashMap<>(); -+ this.sectionsByEntity = new Reference2ReferenceOpenHashMap<>(); -+ } -+ -+ /** -+ * Called when an entity is added to the world. -+ */ -+ public void onEntityAdded(int x, int y, int z, EntityLiving entity) { -+ if (this.addEntity(x, y, z, entity)) { -+ this.addListener(x, y, z, entity.getListener()); -+ } -+ } -+ -+ /** -+ * Called when an entity is removed from the world. -+ */ -+ public void onEntityRemoved(int x, int y, int z, EntityLiving entity) { -+ if (this.removeEntity(x, y, z, entity)) { -+ this.removeListener(entity.getListener()); -+ } -+ } -+ -+ private boolean addEntity(int x, int y, int z, EntityLiving entity) { -+ return this.getOrCreateList(x, y, z).addTrackedEntity(entity); -+ } -+ -+ private boolean removeEntity(int x, int y, int z, EntityLiving entity) { -+ TrackedEntityList list = this.getList(x, y, z); -+ -+ if (list == null) { -+ return false; -+ } -+ -+ return list.removeTrackedEntity(entity); -+ } -+ -+ private void addListener(int x, int y, int z, NearbyEntityListener listener) { -+ int r = listener.getChunkRange(); -+ -+ if (r == 0) { -+ return; -+ } -+ -+ if (this.sectionsByEntity.containsKey(listener)) { -+ -+ throw new IllegalStateException(errorMessageAlreadyListening(this.sectionsByEntity, listener, SectionPosition.a(x, y, z))); -+ } -+ -+ int yMin = Math.max(0, y - r); -+ int yMax = Math.min(y + r, 15); -+ -+ List all = new ArrayList<>((2 * r + 1) * (yMax - yMin + 1) * (2 * r + 1)); -+ -+ for (int x2 = x - r; x2 <= x + r; x2++) { -+ for (int y2 = yMin; y2 <= yMax; y2++) { -+ for (int z2 = z - r; z2 <= z + r; z2++) { -+ TrackedEntityList list = this.getOrCreateList(x2, y2, z2); -+ list.addListener(listener); -+ -+ all.add(list); -+ } -+ } -+ } -+ -+ this.sectionsByEntity.put(listener, all); -+ } -+ -+ private void removeListener(NearbyEntityListener listener) { -+ int r = listener.getChunkRange(); -+ -+ if (r == 0) { -+ return; -+ } -+ -+ List all = this.sectionsByEntity.remove(listener); -+ -+ if (all != null) { -+ for (TrackedEntityList list : all) { -+ list.removeListener(listener); -+ } -+ } else { -+ throw new IllegalArgumentException("Entity listener not tracked:" + listener.toString()); -+ } -+ } -+ -+ // Faster implementation which avoids removing from/adding to every list twice on an entity move event -+ private void moveListener(int aX, int aY, int aZ, int bX, int bY, int bZ, NearbyEntityListener listener) { -+ int radius = listener.getChunkRange(); -+ -+ if (radius == 0) { -+ return; -+ } -+ -+ StructureBoundingBox before = new StructureBoundingBox(aX - radius, aY - radius, aZ - radius, aX + radius, aY + radius, aZ + radius); -+ StructureBoundingBox after = new StructureBoundingBox(aX - radius, aY - radius, aZ - radius, bX + radius, bY + radius, bZ + radius); -+ -+ StructureBoundingBox merged = new StructureBoundingBox(before); -+ merged.c(after); -+ -+ BlockPosition.MutableBlockPosition pos = new BlockPosition.MutableBlockPosition(); -+ -+ for (int x = merged.a; x <= merged.d; x++) { -+ for (int y = merged.b; y <= merged.e; y++) { -+ for (int z = merged.c; z <= merged.f; z++) { -+ pos.setValues(x, y, z); -+ -+ boolean leaving = before.hasPoint(pos); -+ boolean entering = after.hasPoint(pos); -+ -+ // Nothing to change -+ if (leaving == entering) { -+ continue; -+ } -+ -+ if (leaving) { -+ // The listener has left the chunk -+ TrackedEntityList list = this.getList(x, y, z); -+ -+ if (list == null) { -+ throw new IllegalStateException("Expected there to be a listener list while moving entity but there was none"); -+ } -+ -+ list.removeListener(listener); -+ } else { -+ // The listener has entered the chunk -+ TrackedEntityList list = this.getOrCreateList(x, y, z); -+ list.addListener(listener); -+ } -+ } -+ } -+ } -+ } -+ -+ private TrackedEntityList getOrCreateList(int x, int y, int z) { -+ return this.sections.computeIfAbsent(encode(x, y, z), TrackedEntityList::new); -+ } -+ -+ private TrackedEntityList getList(int x, int y, int z) { -+ return this.sections.get(encode(x, y, z)); -+ } -+ -+ private static long encode(int x, int y, int z) { -+ return SectionPosition.asLong(x, y, z); -+ } -+ -+ private static SectionPosition decode(long xyz) { -+ return SectionPosition.a(xyz); -+ } -+ -+ private class TrackedEntityList { -+ private final Set entities = new ReferenceOpenHashSet<>(); -+ private final Set listeners = new ReferenceOpenHashSet<>(); -+ -+ private final long key; -+ -+ private TrackedEntityList(long key) { -+ this.key = key; -+ } -+ -+ public void addListener(NearbyEntityListener listener) { -+ for (EntityLiving entity : this.entities) { -+ listener.onEntityEnteredRange(entity); -+ } -+ -+ this.listeners.add(listener); -+ } -+ -+ public void removeListener(NearbyEntityListener listener) { -+ if (this.listeners.remove(listener)) { -+ for (EntityLiving entity : this.entities) { -+ listener.onEntityLeftRange(entity); -+ } -+ -+ this.checkEmpty(); -+ } -+ } -+ -+ public boolean addTrackedEntity(EntityLiving entity) { -+ for (NearbyEntityListener listener : this.listeners) { -+ listener.onEntityEnteredRange(entity); -+ } -+ -+ return this.entities.add(entity); -+ } -+ -+ public boolean removeTrackedEntity(EntityLiving entity) { -+ boolean ret = this.entities.remove(entity); -+ -+ if (ret) { -+ for (NearbyEntityListener listener : this.listeners) { -+ listener.onEntityLeftRange(entity); -+ } -+ -+ this.checkEmpty(); -+ } -+ -+ return ret; -+ } -+ -+ private void checkEmpty() { -+ if (this.entities.isEmpty() && this.listeners.isEmpty()) { -+ EntityTrackerEngine.this.sections.remove(this.key); -+ } -+ } -+ } -+ -+ -+ private static String errorMessageAlreadyListening(Reference2ReferenceOpenHashMap> sectionsByEntity, NearbyEntityListener listener, SectionPosition newLocation) { -+ StringBuilder builder = new StringBuilder(); -+ builder.append("Adding Entity listener a second time: ").append(listener.toString()); -+ builder.append("\n"); -+ builder.append(" wants to listen at: ").append(newLocation.toString()); -+ builder.append(" with cube radius: ").append(listener.getChunkRange()); -+ builder.append("\n"); -+ builder.append(" but was already listening at chunk sections: "); -+ String[] comma = new String[]{""}; -+ if (sectionsByEntity.get(listener) == null) { -+ builder.append("null"); -+ } else { -+ sectionsByEntity.get(listener).forEach(a -> { -+ builder.append(comma[0]); -+ builder.append(decode(a.key).toString()); -+ comma[0] = ", "; -+ }); -+ } -+ return builder.toString(); -+ } -+} -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..f84366ad98333c2b17b838883d9a3889572bba63 ---- /dev/null -+++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityListener.java -@@ -0,0 +1,25 @@ -+package me.jellysquid.mods.lithium.common.entity.tracker.nearby; -+ -+import net.minecraft.world.entity.EntityLiving; -+ -+/** -+ * The main interface used to receive events from the -+ * {@link me.jellysquid.mods.lithium.common.entity.tracker.EntityTrackerEngine} of a world. -+ */ -+public interface NearbyEntityListener { -+ /** -+ * Returns the range (in chunks) of this listener. This must never change during the lifetime of the listener. -+ * TODO: Allow entity listeners to change the radius they receive updates within -+ */ -+ int getChunkRange(); -+ -+ /** -+ * Called by the entity tracker when an entity enters the range of this listener. -+ */ -+ void onEntityEnteredRange(EntityLiving entity); -+ -+ /** -+ * Called by the entity tracker when an entity leaves the range of this listener or is removed from the world. -+ */ -+ void onEntityLeftRange(EntityLiving entity); -+} -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..a56b23f0fbc1f2e31a79aa8b47635fecdf2490c9 ---- /dev/null -+++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityListenerMulti.java -@@ -0,0 +1,59 @@ -+package me.jellysquid.mods.lithium.common.entity.tracker.nearby; -+ -+import net.minecraft.world.entity.EntityLiving; -+ -+import java.util.ArrayList; -+import java.util.List; -+ -+/** -+ * Allows for multiple listeners on an entity to be grouped under one logical listener. No guarantees are made about the -+ * order of which each sub-listener will be notified. -+ */ -+public class NearbyEntityListenerMulti implements NearbyEntityListener { -+ private final List listeners = new ArrayList<>(); -+ -+ public void addListener(NearbyEntityListener listener) { -+ this.listeners.add(listener); -+ } -+ -+ public void removeListener(NearbyEntityListener listener) { -+ this.listeners.remove(listener); -+ } -+ -+ @Override -+ public int getChunkRange() { -+ int range = 0; -+ -+ for (NearbyEntityListener listener : this.listeners) { -+ range = Math.max(range, listener.getChunkRange()); -+ } -+ -+ return range; -+ } -+ -+ @Override -+ public void onEntityEnteredRange(EntityLiving entity) { -+ for (NearbyEntityListener listener : this.listeners) { -+ listener.onEntityEnteredRange(entity); -+ } -+ } -+ -+ @Override -+ public void onEntityLeftRange(EntityLiving 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..01f9a626e761dd8cc26216e316e3a39362dc463d ---- /dev/null -+++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityTracker.java -@@ -0,0 +1,94 @@ -+package me.jellysquid.mods.lithium.common.entity.tracker.nearby; -+ -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+import net.minecraft.world.phys.AxisAlignedBB; -+import net.minecraft.world.entity.EntityLiving; -+import net.minecraft.util.MathHelper; -+import net.minecraft.nbt.NBTTagCompound; -+import net.minecraft.world.entity.ai.targeting.PathfinderTargetCondition; -+ -+import java.util.Set; -+ -+/** -+ * Maintains a collection of all entities within the range of this listener. This allows AI goals to quickly -+ * assess nearby entities which match the provided class. -+ */ -+public class NearbyEntityTracker implements NearbyEntityListener { -+ private final Class clazz; -+ private final EntityLiving self; -+ -+ private final int rangeC; -+ private final float rangeSq; -+ -+ private final Set nearby = new ReferenceOpenHashSet<>(); -+ -+ public NearbyEntityTracker(Class clazz, EntityLiving self, float range) { -+ this.clazz = clazz; -+ this.self = self; -+ this.rangeSq = range * range; -+ this.rangeC = Math.max((MathHelper.f(range) + 15) >> 4, 1); -+ } -+ -+ @Override -+ public int getChunkRange() { -+ return this.rangeC; -+ } -+ -+ @SuppressWarnings("unchecked") -+ @Override -+ public void onEntityEnteredRange(EntityLiving entity) { -+ if (!this.clazz.isInstance(entity)) { -+ return; -+ } -+ -+ this.nearby.add((T) entity); -+ } -+ -+ @SuppressWarnings("unchecked") -+ @Override -+ public void onEntityLeftRange(EntityLiving entity) { -+ if (this.nearby.isEmpty() || !this.clazz.isInstance(entity)) { -+ return; -+ } -+ -+ this.nearby.remove((T) 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 -+ * @return the closest Entity that meets all requirements (distance, box intersection, predicate, type T) -+ */ -+ public T getClosestEntity(AxisAlignedBB box, PathfinderTargetCondition targetPredicate) { -+ double x = this.self.locX(); -+ double y = this.self.locY(); -+ double z = this.self.locZ(); -+ -+ T nearest = null; -+ double nearestDistance = Double.POSITIVE_INFINITY; -+ -+ for (T entity : this.nearby) { -+ double distance = entity.getDistanceSquared(x, y, z); -+ -+ if (distance < nearestDistance && (box == null || box.intersects(entity.getBoundingBox())) && targetPredicate.test(this.self, entity)) { -+ nearest = entity; -+ nearestDistance = distance; -+ } -+ } -+ -+ if (nearestDistance <= this.rangeSq) { -+ return nearest; -+ } -+ -+ return null; -+ } -+ -+ @Override -+ public String toString() { -+ return super.toString() + " for entity class: " + this.clazz.getName() + ", in rangeSq: " + this.rangeSq + ", around entity: " + this.self.toString() + " with NBT: " + this.self.save(new NBTTagCompound()); -+ } -+} diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/Collector.java b/src/main/java/me/jellysquid/mods/lithium/common/util/Collector.java new file mode 100644 index 0000000000000000000000000000000000000000..8323c05845cd652e5ea5dd4b71c388a1f7374bf6 @@ -864,78 +407,6 @@ index a206a729b3afa01bf591fa4da1e5c14902da4778..716f91246c4a45fd49af806afd1781f1 public static int d(long i) { return (int) (i << 22 >> 42); } -diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java -index d7f95bd32842fdee0ce53fc97d31ffb3577cdc78..e6f85b07f731859c8b8c380afad699ccc1d6c3e8 100644 ---- a/src/main/java/net/minecraft/server/level/WorldServer.java -+++ b/src/main/java/net/minecraft/server/level/WorldServer.java -@@ -177,6 +177,7 @@ import org.bukkit.event.world.TimeSkipEvent; - // CraftBukkit end - import it.unimi.dsi.fastutil.ints.IntArrayList; // Tuinity - import net.gegy1000.tictacs.NonBlockingWorldAccess; // Yatopia -+import me.jellysquid.mods.lithium.common.entity.tracker.EntityTrackerEngine; // Yatopia - - public class WorldServer extends World implements GeneratorAccessSeed, NonBlockingWorldAccess { // Yatopia - -@@ -2031,6 +2032,16 @@ public class WorldServer extends World implements GeneratorAccessSeed, NonBlocki - while (iterator.hasNext()) { - Entity entity = (Entity) iterator.next(); - -+ if (entity instanceof EntityLiving) { // Yatopia start - Port lithium -+ -+ int chunkX = MathHelper.floor(entity.locX()) >> 4; -+ int chunkY = MathHelper.clamp(MathHelper.floor(entity.locY()) >> 4, 0, 15); -+ int chunkZ = MathHelper.floor(entity.locZ()) >> 4; -+ -+ EntityTrackerEngine tracker = this.getEntityTracker(); -+ tracker.onEntityRemoved(chunkX, chunkY, chunkZ, (EntityLiving) entity); -+ } // Yatopia End -+ - if (!(entity instanceof EntityPlayer)) { - if (false && this.tickingEntities) { // Tuinity - throw (IllegalStateException) SystemUtils.c((Throwable) (new IllegalStateException("Removing entity while ticking!"))); -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index cd1f94e5c1c923ee9d8dd451406ac2bee360e9c3..7befe4263a2d046922438e1a9853f2d8290ee230 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -177,6 +177,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne - private CraftEntity bukkitEntity; - - public PlayerChunkMap.EntityTracker tracker; // Paper package private -> public -+ public PlayerChunkMap.EntityTracker getTracker() { return tracker; } // Yatopia - public boolean collisionLoadChunks = false; // Paper - public Throwable addedToWorldStack; // Paper - entity debug - public CraftEntity getBukkitEntity() { -diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java -index a057be22f488f76b926c81dc5c63e7f9c3fb54a1..a897ad3de8c18be8bb3c320950a7421477040e8a 100644 ---- a/src/main/java/net/minecraft/world/entity/EntityLiving.java -+++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java -@@ -141,9 +141,11 @@ import org.bukkit.event.player.PlayerItemConsumeEvent; - // CraftBukkit end - - import net.gegy1000.tictacs.NonBlockingWorldAccess; // Yatopia -+import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityListenerMulti; // Yatopia - - public abstract class EntityLiving extends Entity { - -+ private NearbyEntityListenerMulti tracker; // Yatopia - Port lithium - private static final UUID b = UUID.fromString("662A6B8D-DA3E-4C1C-8813-96EA6097278D"); - private static final UUID c = UUID.fromString("87f46a96-686f-4796-b035-22e16ee9e038"); - private static final AttributeModifier d = new AttributeModifier(EntityLiving.b, "Sprinting speed boost", 0.30000001192092896D, AttributeModifier.Operation.MULTIPLY_TOTAL); -@@ -277,8 +279,14 @@ public abstract class EntityLiving extends Entity { - DynamicOpsNBT dynamicopsnbt = DynamicOpsNBT.a; - - this.bg = this.a(new Dynamic(dynamicopsnbt, dynamicopsnbt.createMap((Map) ImmutableMap.of(dynamicopsnbt.createString("memories"), dynamicopsnbt.emptyMap())))); -+ this.tracker = new NearbyEntityListenerMulti(); // Yatopia - Port lithium - } - -+ // Yatopia start - Port lithium -+ public NearbyEntityListenerMulti getListener() { -+ return this.tracker; -+ } -+ // Yatopia end - protected void initAttributes() {} // Purpur - - public BehaviorController getBehaviorController() { diff --git a/src/main/java/net/minecraft/world/entity/ai/BehaviorController.java b/src/main/java/net/minecraft/world/entity/ai/BehaviorController.java index e38cdf81e3f3ba1a2ee86c3a202e5643dd9c0fd1..48524cdf7a1a5d4cff12b24b11dbea9935c85131 100644 --- a/src/main/java/net/minecraft/world/entity/ai/BehaviorController.java @@ -1177,98 +648,6 @@ index e2b5d6155bebdbf99b0850de7f9e1f5d342f9e2f..a3236e6359a2e72b4a41be4717780c20 + return new ListIterator<>(this.list.iterator()); + } +} // Yatopia End -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalAvoidTarget.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalAvoidTarget.java -index 35502bd2f7d9cebf5cfe1060e300a5032dbe6a5d..eea1a396f06e8feaa5637ba4e589a13169f514da 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalAvoidTarget.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalAvoidTarget.java -@@ -1,5 +1,7 @@ - package net.minecraft.world.entity.ai.goal; - -+import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityTracker; -+ - import java.util.EnumSet; - import java.util.function.Predicate; - import net.minecraft.world.entity.Entity; -@@ -25,6 +27,7 @@ public class PathfinderGoalAvoidTarget extends Pathfinde - protected final Predicate g; - protected final Predicate h; - private final PathfinderTargetCondition k; -+ private NearbyEntityTracker tracker; // Yatopia - Port Lithium - - public PathfinderGoalAvoidTarget(EntityCreature entitycreature, Class oclass, float f, double d0, double d1) { - this(entitycreature, oclass, entityliving -> true, f, d0, d1, IEntitySelector.e::test); // Purpur - decompile fix -@@ -41,6 +44,10 @@ public class PathfinderGoalAvoidTarget extends Pathfinde - this.e = entitycreature.getNavigation(); - this.a(EnumSet.of(PathfinderGoal.Type.MOVE)); - this.k = (new PathfinderTargetCondition()).a((double) f).a(predicate1.and(predicate)); -+ // Yatopia start - Port Lithium -+ this.tracker = new NearbyEntityTracker<>(oclass, entitycreature, f); -+ entitycreature.getListener().addListener(this.tracker); -+ // Yatopia end - } - - public PathfinderGoalAvoidTarget(EntityCreature entitycreature, Class oclass, float f, double d0, double d1, Predicate predicate) { -@@ -51,7 +58,7 @@ public class PathfinderGoalAvoidTarget extends Pathfinde - - @Override - public boolean a() { -- this.b = this.a.world.b(this.f, this.k, this.a, this.a.locX(), this.a.locY(), this.a.locZ(), this.a.getBoundingBox().grow((double) this.c, 3.0D, (double) this.c)); -+ this.b = this.tracker.getClosestEntity(this.a.getBoundingBox().grow((double) this.c, 3.0D, (double) this.c), this.k); // Yatopia - Port lithium - if (this.b == null) { - return false; - } else { -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalLookAtPlayer.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalLookAtPlayer.java -index 3bcbad5e298cf05c1b41476a08a3a69cb7fdf79f..8b41274aa388a758bd8acf9637e2a19caf744c57 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalLookAtPlayer.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalLookAtPlayer.java -@@ -1,5 +1,6 @@ - package net.minecraft.world.entity.ai.goal; - -+import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityTracker; - import java.util.EnumSet; - import net.minecraft.world.entity.Entity; - import net.minecraft.world.entity.EntityInsentient; -@@ -10,6 +11,7 @@ import net.minecraft.world.entity.player.EntityHuman; - - public class PathfinderGoalLookAtPlayer extends PathfinderGoal { - -+ private NearbyEntityTracker tracker; // Yatopia - Port lithium - protected final EntityInsentient a; - protected Entity b; - protected final float c; -@@ -27,7 +29,7 @@ public class PathfinderGoalLookAtPlayer extends PathfinderGoal { - this.e = oclass; - this.c = f; - this.d = f1; -- this.a(EnumSet.of(PathfinderGoal.Type.LOOK)); -+ this.a(EnumSet.of(Type.LOOK)); // Yatopia - Port lithium - if (oclass == EntityHuman.class) { - this.f = (new PathfinderTargetCondition()).a((double) f).b().a().d().a((entityliving) -> { - return IEntitySelector.b(entityinsentient).test(entityliving); -@@ -35,7 +37,10 @@ public class PathfinderGoalLookAtPlayer extends PathfinderGoal { - } else { - this.f = (new PathfinderTargetCondition()).a((double) f).b().a().d(); - } -- -+ // Yatopia start - Port lithium -+ this.tracker = new NearbyEntityTracker<>(oclass, entityinsentient, f); -+ entityinsentient.getListener().addListener(this.tracker); -+ // Yatopia end - } - - @Override -@@ -48,9 +53,9 @@ public class PathfinderGoalLookAtPlayer extends PathfinderGoal { - } - - if (this.e == EntityHuman.class) { -- this.b = this.a.world.a(this.f, this.a, this.a.locX(), this.a.getHeadY(), this.a.locZ()); -+ this.b = this.tracker.getClosestEntity(null, this.f); // Yatopia - Port lithium - } else { -- this.b = this.a.world.b(this.e, this.f, this.a, this.a.locX(), this.a.getHeadY(), this.a.locZ(), this.a.getBoundingBox().grow((double) this.c, 3.0D, (double) this.c)); -+ this.b = this.tracker.getClosestEntity(this.a.getBoundingBox().grow((double) this.c, 3.0D, (double) this.c), this.f); // Yatopia - Port lithium - } - - return this.b != null; diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java index 29cd71efe86eea2227f373c15c39dc530e9e8199..7dcd055b37bb7051127e10f5d191d23e0562b29e 100644 --- a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java @@ -1592,49 +971,6 @@ index f9d03b6d11ad5ffbfe5be072e8631f046bcd1646..52def6de2225cf5ed70c807b74fb5c2c public static ItemStack s() { ItemStack itemstack = new ItemStack(Items.WHITE_BANNER); NBTTagCompound nbttagcompound = itemstack.a("BlockEntityTag"); -diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java -index f5badbe0dee5c40cf83a5d2993d27ed70ddd2c85..db2b1863b7f46be53839fb3e86870745fb7567fd 100644 ---- a/src/main/java/net/minecraft/world/level/World.java -+++ b/src/main/java/net/minecraft/world/level/World.java -@@ -97,6 +97,7 @@ import org.bukkit.event.block.BlockPhysicsEvent; - // CraftBukkit end - - import net.gegy1000.tictacs.NonBlockingWorldAccess; // Yatopia -+import me.jellysquid.mods.lithium.common.entity.tracker.EntityTrackerEngine; // Yatopia - - public abstract class World implements GeneratorAccess, AutoCloseable, NonBlockingWorldAccess { // Yatopia - -@@ -291,6 +292,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable, NonBlocki - } - - // Tuinity end - optimise checkDespawn -+ private EntityTrackerEngine tracker; // Yatopia - Port lithium - - protected World(WorldDataMutable worlddatamutable, ResourceKey resourcekey, final DimensionManager dimensionmanager, Supplier supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env, java.util.concurrent.Executor executor) { // Paper - this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.WorldDataServer) worlddatamutable).getName()); // Spigot -@@ -368,8 +370,14 @@ public abstract class World implements GeneratorAccess, AutoCloseable, NonBlocki - this.keepSpawnInMemory = this.paperConfig.keepSpawnInMemory; // Paper - this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); - this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); -+ this.tracker = new EntityTrackerEngine(); // Yatopia - Port lithium - } - -+ // Yatopia start - Port lithium -+ public EntityTrackerEngine getEntityTracker() { -+ return this.tracker; -+ } // Yatopia end -+ - // Paper start - // ret true if no collision - public final boolean checkEntityCollision(IBlockData data, Entity source, VoxelShapeCollision voxelshapedcollision, -@@ -536,6 +544,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable, NonBlocki - return b(blockposition.getY()); - } - -+ public static boolean isOutOfBuildLimitVertically(int y){ return b(y); } // Yatopia - OBFHELPER - public static boolean b(int i) { - return i < 0 || i >= 256; - } diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java index fab55929f72c5784291b3bc87f7717ac24b7806f..20c2c05ab09ac3f2b2924f2b5bc938fdc5cdd32e 100644 --- a/src/main/java/net/minecraft/world/level/block/Block.java @@ -1722,42 +1058,6 @@ index b6de70c3630d96d0782a657c0389ce03839d8c43..611066964d11b2da7ab6dd59c6083c5c this.opacityIfCached = this.a == null || this.isConditionallyFullOpaque() ? -1 : this.a.getOpacity(); // Tuinity - cache opacity for light // Tuinity start - optimise culling shape cache for light if (this.a != null && this.a.getCullingShapeCache() != null) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java -index 2e8eb0bb8fb4f7ce6b92fe01a81327da30e614ae..34af81b75e7927cccc0d4aea1b80ab677ca31795 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java -@@ -68,6 +68,7 @@ import net.minecraft.world.level.material.FluidTypes; - import net.minecraft.world.phys.AxisAlignedBB; - import org.apache.logging.log4j.LogManager; - import org.apache.logging.log4j.Logger; -+import net.minecraft.world.entity.EntityLiving; // Yatopia - - public class Chunk implements IChunkAccess { - -@@ -863,6 +864,11 @@ public class Chunk implements IChunkAccess { - if (!this.entitySlices[i].remove(entity)) { // Tuinity - optimise hard colliding entities // Tuinity - entities by class // Tuinity - return; - } -+ // Yatopia start - Port lithium -+ if (entity instanceof EntityLiving) { -+ this.world.getEntityTracker().onEntityAdded(entity.chunkX, entity.chunkY, entity.chunkZ, (EntityLiving) entity); -+ } -+ // Yatopia end - if (entity instanceof EntityItem) { - itemCounts[i]--; - } else if (entity instanceof IInventory) { -@@ -872,6 +878,11 @@ public class Chunk implements IChunkAccess { - this.markDirty(); // Paper - // Paper end - this.entities.remove(entity); // Paper -+ // Yatopia start - Port lithium -+ if (entity instanceof EntityLiving) { -+ this.world.getEntityTracker().onEntityRemoved(entity.chunkX, entity.chunkY, entity.chunkZ, (EntityLiving) entity); -+ } -+ // Yatopia end - } - - public final int getHighestBlockY(HeightMap.Type heightmap_type, int i, int j) { return this.getHighestBlock(heightmap_type, i, j) + 1; } // Paper - sort of an obfhelper, but without -1 diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java index b5eb43174d2c2f34bb17bbcdb803aafe58989678..92f6301005031f4afab225e9fd01010eacd3ed26 100644 --- a/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java @@ -2139,16 +1439,3 @@ index 9d08094165cf18d99116b5c721fff888f3cb42e2..b95804e73050dc7eb9786ca4bb5ea095 } protected static PathType getStaticTypeSlow(IBlockAccess iblockaccess, BlockPosition blockposition, IBlockData iblockdata) { // Tuinity end - reduce pathfinder branches -diff --git a/src/main/java/net/pl3x/purpur/controller/ControllerLookWASD.java b/src/main/java/net/pl3x/purpur/controller/ControllerLookWASD.java -index 292821f039d99a03ba4daeb3a941616ef5f6287e..9b7110a805b81906512acc3771217a56a102db67 100644 ---- a/src/main/java/net/pl3x/purpur/controller/ControllerLookWASD.java -+++ b/src/main/java/net/pl3x/purpur/controller/ControllerLookWASD.java -@@ -42,7 +42,7 @@ public class ControllerLookWASD extends ControllerLook { - entity.setHeadRotation(entity.yaw); - entity.pitch = normalizePitch(pitch + pitchOffset); - -- entity.tracker.broadcast(new PacketPlayOutEntity -+ entity.getTracker().broadcast(new PacketPlayOutEntity // Yatopia - .PacketPlayOutRelEntityMoveLook(entity.getId(), - (short) 0, (short) 0, (short) 0, - (byte) MathHelper.d(entity.yaw * 256.0F / 360.0F), diff --git a/patches/server/0065-lithium-block.patch b/patches/server/0066-lithium-block.patch similarity index 100% rename from patches/server/0065-lithium-block.patch rename to patches/server/0066-lithium-block.patch diff --git a/patches/server/0066-lithium-entity.patch b/patches/server/0067-lithium-entity.patch similarity index 100% rename from patches/server/0066-lithium-entity.patch rename to patches/server/0067-lithium-entity.patch diff --git a/patches/server/0067-lithium-gen.patch b/patches/server/0068-lithium-gen.patch similarity index 100% rename from patches/server/0067-lithium-gen.patch rename to patches/server/0068-lithium-gen.patch diff --git a/patches/server/0068-lithium-shape.patch b/patches/server/0069-lithium-shape.patch similarity index 97% rename from patches/server/0068-lithium-shape.patch rename to patches/server/0069-lithium-shape.patch index 2ece7899..29d9d806 100644 --- a/patches/server/0068-lithium-shape.patch +++ b/patches/server/0069-lithium-shape.patch @@ -282,18 +282,18 @@ index cd1f94e5c1c923ee9d8dd451406ac2bee360e9c3..8735735eb116382b21b6751108629acb return this.isSneaking(); } diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java -index fab55929f72c5784291b3bc87f7717ac24b7806f..024b1c8939a93e2802ea53eefc49dda182412f28 100644 +index 20c2c05ab09ac3f2b2924f2b5bc938fdc5cdd32e..e0d2d2f617594674824cdff34cdcfadaa16ac291 100644 --- a/src/main/java/net/minecraft/world/level/block/Block.java +++ b/src/main/java/net/minecraft/world/level/block/Block.java -@@ -55,6 +55,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; - import net.minecraft.world.phys.shapes.VoxelShapes; +@@ -56,6 +56,7 @@ import net.minecraft.world.phys.shapes.VoxelShapes; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; + import me.jellysquid.mods.lithium.common.ai.pathing.PathNodeDefaults; +import me.jellysquid.mods.lithium.common.util.collections.Object2BooleanCacheTable; public class Block extends BlockBase implements IMaterial { -@@ -217,8 +218,14 @@ public class Block extends BlockBase implements IMaterial { +@@ -229,8 +230,14 @@ public class Block extends BlockBase implements IMaterial { return a(voxelshape1); } diff --git a/patches/server/0069-lithium-skip-ticking-block-entities-that-are-doing-n.patch b/patches/server/0070-lithium-skip-ticking-block-entities-that-are-doing-n.patch similarity index 100% rename from patches/server/0069-lithium-skip-ticking-block-entities-that-are-doing-n.patch rename to patches/server/0070-lithium-skip-ticking-block-entities-that-are-doing-n.patch diff --git a/patches/server/0070-Multithreaded-Entity-Tracker-fixup.patch b/patches/server/0071-Multithreaded-Entity-Tracker-fixup.patch similarity index 100% rename from patches/server/0070-Multithreaded-Entity-Tracker-fixup.patch rename to patches/server/0071-Multithreaded-Entity-Tracker-fixup.patch