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/ai/pathing/PathNodeCache.java b/src/main/java/me/jellysquid/mods/lithium/common/ai/pathing/PathNodeCache.java new file mode 100644 index 0000000000000000000000000000000000000000..b97dc93ba3708811cd8a1409aa5992a548586ed0 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/lithium/common/ai/pathing/PathNodeCache.java @@ -0,0 +1,92 @@ +package me.jellysquid.mods.lithium.common.ai.pathing; + +import it.unimi.dsi.fastutil.objects.Reference2BooleanMap; +import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap; +import net.minecraft.world.level.chunk.ChunkSection; +import net.minecraft.world.level.block.state.IBlockData; +import net.minecraft.world.level.pathfinder.PathType; + +public class PathNodeCache { + /** + * A transient hash table of chunk sections and whether or not they contain dangerous block types. Used as a cache + * to avoid scanning for many neighbors when we know the chunk is free of dangers. This is only safe to use when + * we know the world is not going to be modified while it is active. + */ + private static final Reference2BooleanMap chunkNeighborDangerCache = new Reference2BooleanOpenHashMap<>(); + + /** + * True if the chunk danger cache is enabled and can be used. + */ + private static boolean dangerCacheEnabled = false; + + /** + * The previous chunk section that was queried for neighboring dangers. + */ + private static ChunkSection prevQueriedNeighborSectionKey; + + /** + * The result of the previous query belonging to {@link PathNodeCache#prevQueriedNeighborSectionKey}. + */ + private static boolean prevQueriedNeighborSectionResult; + + /** + * Enables the chunk danger cache. This should be called immediately before a controlled path-finding code path + * begins so that we can accelerate nearby danger checks. + */ + public static void enableChunkCache() { + dangerCacheEnabled = true; + } + + /** + * Disables and clears the chunk danger cache. This should be called immediately before path-finding ends so that + * block updates are reflected for future path-finding tasks. + */ + public static void disableChunkCache() { + dangerCacheEnabled = false; + chunkNeighborDangerCache.clear(); + + prevQueriedNeighborSectionKey = null; + prevQueriedNeighborSectionResult = false; + } + + private static boolean isChunkSectionDangerousNeighbor(ChunkSection section) { + return section.getBlocks() + .contains(state -> getNeighborPathNodeType(state) != PathType.OPEN); + } + + public static PathType getPathNodeType(IBlockData state) { + return state.getPathNodeType(); + } + + public static PathType getNeighborPathNodeType(IBlockData state) { + return state.getNeighborPathNodeType(); + } + + /** + * Returns whether or not a chunk section is free of dangers. This makes use of a caching layer to greatly + * accelerate neighbor danger checks when path-finding. + * + * @param section The chunk section to test for dangers + * @return True if this neighboring section is free of any dangers, otherwise false if it could + * potentially contain dangers + */ + public static boolean isSectionSafeAsNeighbor(ChunkSection section) { + // Empty sections can never contribute a danger + if (ChunkSection.isEmpty(section)) { + return true; + } + + // If the caching code path is disabled, the section must be assumed to potentially contain dangers + if (!dangerCacheEnabled) { + return false; + } + + if (prevQueriedNeighborSectionKey != section) { + prevQueriedNeighborSectionKey = section; + prevQueriedNeighborSectionResult = !chunkNeighborDangerCache.computeBooleanIfAbsent(section, + PathNodeCache::isChunkSectionDangerousNeighbor); + } + + return prevQueriedNeighborSectionResult; + } +} diff --git a/src/main/java/me/jellysquid/mods/lithium/common/ai/pathing/PathNodeDefaults.java b/src/main/java/me/jellysquid/mods/lithium/common/ai/pathing/PathNodeDefaults.java new file mode 100644 index 0000000000000000000000000000000000000000..4427f2171671896c978908b1c3d72b3f64f16a0b --- /dev/null +++ b/src/main/java/me/jellysquid/mods/lithium/common/ai/pathing/PathNodeDefaults.java @@ -0,0 +1,110 @@ +package me.jellysquid.mods.lithium.common.ai.pathing; + +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.BlockCampfire; +import net.minecraft.world.level.block.BlockDoor; +import net.minecraft.world.level.block.BlockFenceGate; +import net.minecraft.world.level.block.BlockLeaves; +import net.minecraft.world.level.block.BlockMinecartTrackAbstract; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.block.state.IBlockData; +import net.minecraft.world.level.material.Material; +import net.minecraft.world.level.pathfinder.PathType; +import net.minecraft.tags.TagsBlock; +import net.minecraft.tags.TagsFluid; + +public class PathNodeDefaults { + public static PathType getNeighborNodeType(IBlockData state) { + if (state.isAir()) { + return PathType.OPEN; + } + + // [VanillaCopy] LandPathNodeMaker#getNodeTypeFromNeighbors + // Determine what kind of obstacle type this neighbor is + if (state.a(Blocks.CACTUS)) { + return PathType.DANGER_CACTUS; + } else if (state.a(Blocks.SWEET_BERRY_BUSH)) { + return PathType.DANGER_OTHER; + } else if (isFireDangerSource(state)) { + return PathType.DANGER_FIRE; + } else if (state.getFluid().a(TagsFluid.WATER)) { + return PathType.WATER_BORDER; + } else { + return PathType.OPEN; + } + } + + public static PathType getNodeType(IBlockData state) { + if (state.isAir()) { + return PathType.OPEN; + } + + Block block = state.getBlock(); + Material material = state.getMaterial(); + + if (state.hasTag(TagsBlock.TRAPDOORS) || state.a(Blocks.LILY_PAD)) { + return PathType.TRAPDOOR; + } + + if (state.a(Blocks.CACTUS)) { + return PathType.DAMAGE_CACTUS; + } + + if (state.a(Blocks.SWEET_BERRY_BUSH) || state.a(Blocks.STONECUTTER)) { + return PathType.DAMAGE_OTHER; + } + + if (state.a(Blocks.HONEY_BLOCK)) { + return PathType.STICKY_HONEY; + } + + if (state.a(Blocks.COCOA)) { + return PathType.COCOA; + } + + if (isFireDangerSource(state)) { + return PathType.DAMAGE_FIRE; + } + + if (BlockDoor.l(state) && !state.get(BlockDoor.OPEN)) { + return PathType.DOOR_WOOD_CLOSED; + } + + if ((block instanceof BlockDoor) && (material == Material.ORE) && !state.get(BlockDoor.OPEN)) { + return PathType.DOOR_IRON_CLOSED; + } + + if ((block instanceof BlockDoor) && state.get(BlockDoor.OPEN)) { + return PathType.DOOR_OPEN; + } + + if (block instanceof BlockMinecartTrackAbstract) { + return PathType.RAIL; + } + + if (block instanceof BlockLeaves) { + return PathType.LEAVES; + } + + if (block.a(TagsBlock.FENCES) || block.a(TagsBlock.WALLS) || ((block instanceof BlockFenceGate) && !state.get(BlockFenceGate.OPEN))) { + return PathType.FENCE; + } + + // Retrieve the fluid state from the block state to avoid a second lookup + Fluid fluid = state.getFluid(); + if (fluid == null) { + return PathType.OPEN; + } else if (fluid.a(TagsFluid.WATER)) { + return PathType.WATER; + } else if (fluid.a(TagsFluid.LAVA)) { + return PathType.LAVA; + } + + return PathType.OPEN; + } + + private static boolean isFireDangerSource(IBlockData blockState) { + 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 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/lithium/common/util/Collector.java @@ -0,0 +1,11 @@ +package me.jellysquid.mods.lithium.common.util; + +public interface Collector { + /** + * Collects the passed object and performs additional processing on it, returning a flag as to whether or not + * collection should continue. + * + * @return True if collection should continue, otherwise false. + */ + boolean collect(T obj); +} \ No newline at end of file diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/collections/ListeningLong2ObjectOpenHashMap.java b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/ListeningLong2ObjectOpenHashMap.java new file mode 100644 index 0000000000000000000000000000000000000000..a94a40dcc3502d29950e5e387d658232a0bf5552 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/ListeningLong2ObjectOpenHashMap.java @@ -0,0 +1,46 @@ +package me.jellysquid.mods.lithium.common.util.collections; + +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; + +/** + * An extension for {@link Long2ObjectOpenHashMap} which allows callbacks to be installed for when an item is added to + * or removed from the map. + */ +public class ListeningLong2ObjectOpenHashMap extends Long2ObjectOpenHashMap { + private final Callback addCallback, removeCallback; + + public ListeningLong2ObjectOpenHashMap(Callback addCallback, Callback removeCallback) { + this.addCallback = addCallback; + this.removeCallback = removeCallback; + } + + @Override + public V put(long k, V v) { + V ret = super.put(k, v); + + if (ret != v) { + if (ret != null) { + this.removeCallback.apply(k, v); + } + + this.addCallback.apply(k, v); + } + + return ret; + } + + @Override + public V remove(long k) { + V ret = super.remove(k); + + if (ret != null) { + this.removeCallback.apply(k, ret); + } + + return ret; + } + + public interface Callback { + void apply(long key, V value); + } +} diff --git a/src/main/java/me/jellysquid/mods/lithium/common/world/WorldHelper.java b/src/main/java/me/jellysquid/mods/lithium/common/world/WorldHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..5959d66582342f614bdadb2a1ef163e4fff25341 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/lithium/common/world/WorldHelper.java @@ -0,0 +1,18 @@ +package me.jellysquid.mods.lithium.common.world; + +import net.minecraft.core.BlockPosition; + +public class WorldHelper { + + public static boolean areNeighborsWithinSameChunk(BlockPosition pos) { + int localX = pos.getX() & 15; + int localY = pos.getY() & 15; + int localZ = pos.getZ() & 15; + + return localX > 0 && localY > 0 && localZ > 0 && localX < 15 && localY < 15 && localZ < 15; + } + + public static boolean areAllNeighborsOutOfBounds(BlockPosition pos) { + return pos.getY() < -1 || pos.getY() > 256; + } +} diff --git a/src/main/java/me/jellysquid/mods/lithium/common/world/interests/PointOfInterestCollectors.java b/src/main/java/me/jellysquid/mods/lithium/common/world/interests/PointOfInterestCollectors.java new file mode 100644 index 0000000000000000000000000000000000000000..069b204764d04b126ac8ef30eae8f7e1234badf5 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/lithium/common/world/interests/PointOfInterestCollectors.java @@ -0,0 +1,28 @@ +package me.jellysquid.mods.lithium.common.world.interests; + +import me.jellysquid.mods.lithium.common.util.Collector; +import net.minecraft.core.BlockPosition; +import net.minecraft.world.entity.ai.village.poi.VillagePlace; +import net.minecraft.world.entity.ai.village.poi.VillagePlaceRecord; +import net.minecraft.world.entity.ai.village.poi.VillagePlaceSection; +import net.minecraft.world.entity.ai.village.poi.VillagePlaceType; + +import java.util.function.Predicate; + +public class PointOfInterestCollectors { + public static Collector collectAllWithinRadius(BlockPosition pos, double radius, Collector out) { + double radiusSq = radius * radius; + + return (point) -> { + if (point.getPosition().distanceSquared(pos) <= radiusSq) { + return out.collect(point); + } + + return true; + }; + } + + public static Collector collectAllMatching(Predicate predicate, VillagePlace.Occupancy status, Collector out) { + return set -> set.get(predicate, status, out); + } +} \ No newline at end of file diff --git a/src/main/java/me/jellysquid/mods/lithium/common/world/interests/PointOfInterestTypeHelper.java b/src/main/java/me/jellysquid/mods/lithium/common/world/interests/PointOfInterestTypeHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..06d7c8a5ae23ec8b55661b4129c7c88657d1b9bf --- /dev/null +++ b/src/main/java/me/jellysquid/mods/lithium/common/world/interests/PointOfInterestTypeHelper.java @@ -0,0 +1,22 @@ +package me.jellysquid.mods.lithium.common.world.interests; + +import it.unimi.dsi.fastutil.objects.ObjectSet; +import net.minecraft.world.level.chunk.ChunkSection; +import net.minecraft.world.level.block.state.IBlockData; + +public class PointOfInterestTypeHelper { + private static ObjectSet TYPES; + + public static void init(ObjectSet types) { + if (TYPES != null) { + throw new IllegalStateException("Already initialized"); + } + + TYPES = types; + } + + public static boolean shouldScan(ChunkSection section) { + return section.hasAny(TYPES::contains); + } + +} \ No newline at end of file diff --git a/src/main/java/net/minecraft/core/SectionPosition.java b/src/main/java/net/minecraft/core/SectionPosition.java index a206a729b3afa01bf591fa4da1e5c14902da4778..716f91246c4a45fd49af806afd1781f11ff3b346 100644 --- a/src/main/java/net/minecraft/core/SectionPosition.java +++ b/src/main/java/net/minecraft/core/SectionPosition.java @@ -22,6 +22,7 @@ public class SectionPosition extends BaseBlockPosition { return new SectionPosition(blockposition.getX() >> 4, blockposition.getY() >> 4, blockposition.getZ() >> 4); // Paper } + public static SectionPosition from(ChunkCoordIntPair chunkCoordIntPair, int i){ return a(chunkCoordIntPair, i); } // Yatopia - OBFHELPER public static SectionPosition a(ChunkCoordIntPair chunkcoordintpair, int i) { return new SectionPosition(chunkcoordintpair.x, i, chunkcoordintpair.z); } @@ -94,14 +95,17 @@ public class SectionPosition extends BaseBlockPosition { return i << 4; } + public static int unpackX(long i){ return b(i); } // Yatopia - OBFHELPER public static int b(long i) { return (int) (i << 0 >> 42); } + public static int unpackY(long i){ return c(i); } // Yatopia - OBFHELPER public static int c(long i) { return (int) (i << 44 >> 44); } + public static int unpackZ(long i){ return d(i); } // Yatopia - OBFHELPER 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 +++ b/src/main/java/net/minecraft/world/entity/ai/BehaviorController.java @@ -223,6 +223,7 @@ public class BehaviorController { }).isPresent(); } + public boolean isMemoryInState(MemoryModuleType memorymoduletype, MemoryStatus memorystatus){ return a(memorymoduletype, memorystatus); } // Yatopia - OBFHELPER public boolean a(MemoryModuleType memorymoduletype, MemoryStatus memorystatus) { Optional> optional = (Optional) this.memories.get(memorymoduletype); diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java index ed3a3593b417131837341784b09cb3f9f76a44be..922f7bf57e65b0bab18b9044ff6467a2660aa992 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java @@ -1,5 +1,8 @@ package net.minecraft.world.entity.ai.behavior; +import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; +import it.unimi.dsi.fastutil.objects.Reference2ObjectMaps; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; @@ -10,7 +13,7 @@ import net.minecraft.world.entity.ai.memory.MemoryStatus; public abstract class Behavior { - protected final Map, MemoryStatus> a; + protected final Map, MemoryStatus> a; protected final Map, MemoryStatus> getRequiredMemoryStates(){ return a; } // Yatopia - OBFHELPER private Behavior.Status b; public final Behavior.Status getStatus() { return this.b; } // Tuinity - OBFHELPER private long c; co.aikar.timings.Timing timing; // Origami - behavior timing @@ -29,7 +32,7 @@ public abstract class Behavior { this.b = Behavior.Status.STOPPED; this.d = i; this.e = j; - this.a = map; + this.a = new Reference2ObjectOpenHashMap<>(map); // Yatopia - Port lithium String key = getClass().getSimpleName(); // Yatopia Compatible Fix // Origami start - behavior timing timing = co.aikar.timings.WorldTimingsHandler.getBehaviorTimings(key); @@ -40,6 +43,7 @@ public abstract class Behavior { return this.b; } + public final boolean tryStarting(WorldServer worldserver, E e0, long i){ return e(worldserver, e0, i); } // Yatopia - OBFHELPER public final boolean e(WorldServer worldserver, E e0, long i) { if (this.a(e0) && this.a(worldserver, e0)) { this.b = Behavior.Status.RUNNING; @@ -92,23 +96,17 @@ public abstract class Behavior { } private boolean a(E e0) { - Iterator iterator = this.a.entrySet().iterator(); + // Yatopia start - port lithium + Iterable, MemoryStatus>> iterable = + Reference2ObjectMaps.fastIterable((Reference2ObjectOpenHashMap, MemoryStatus>)this.getRequiredMemoryStates()); - MemoryModuleType memorymoduletype; - MemoryStatus memorystatus; - - do { - if (!iterator.hasNext()) { - return true; + for (Reference2ObjectMap.Entry, MemoryStatus> entry : iterable) { + if (!e0.getBehaviorController().isMemoryInState(entry.getKey(), entry.getValue())) { + return false; } + } - Entry, MemoryStatus> entry = (Entry) iterator.next(); - - memorymoduletype = (MemoryModuleType) entry.getKey(); - memorystatus = (MemoryStatus) entry.getValue(); - } while (e0.getBehaviorController().a(memorymoduletype, memorystatus)); - - return false; + return true; // Yatopia end } public static enum Status { diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorGate.java b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorGate.java index c46cdffe3d877bff70b843766c8189eae06148ff..9cf43a86b573ad2c553956c081ae40820cbc7d5e 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorGate.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorGate.java @@ -31,17 +31,15 @@ public class BehaviorGate extends Behavior { @Override protected boolean b(WorldServer worldserver, E e0, long i) { - // Tuinity start - remove streams - List>> list = this.getList().getList(); - for (int index = 0, len = list.size(); index < len; ++index) { - Behavior behavior = list.get(index).getValue(); + // Tuinity start - remove streams // Yatopia start - Port lithium + for (Behavior behavior : this.getList()) { if (behavior.getStatus() == Status.RUNNING && behavior.b(worldserver, e0, i)) { // copied from removed code, make sure to update return true; } } return false; - // Tuinity end - remove streams + // Tuinity end - remove streams // Yatopia start - Port lithium } @Override @@ -57,23 +55,19 @@ public class BehaviorGate extends Behavior { @Override protected void d(WorldServer worldserver, E e0, long i) { - // Tuinity start - remove streams - List>> list = this.getList().getList(); - for (int index = 0, len = list.size(); index < len; ++index) { - Behavior behavior = list.get(index).getValue(); + // Tuinity start - remove streams // Yatopia start - Port lithium + for (Behavior behavior : this.getList()) { if (behavior.getStatus() == Behavior.Status.RUNNING) { behavior.f(worldserver, e0, i); // copied from removed code, make sure to update } } - // Tuinity end - remove streams + // Tuinity end - remove streams // Yatopia end } @Override protected void c(WorldServer worldserver, E e0, long i) { - // Tuinity start - remove streams - List>> list = this.getList().getList(); - for (int index = 0, len = list.size(); index < len; ++index) { - Behavior behavior = list.get(index).getValue(); + // Tuinity start - remove streams// Yatopia start - Port lithium + for (Behavior behavior : this.getList()) { if (behavior.getStatus() == Behavior.Status.RUNNING) { behavior.g(worldserver, e0, i); // copied from removed code, make sure to update } @@ -81,7 +75,9 @@ public class BehaviorGate extends Behavior { // Tuinity end - remove streams BehaviorController behaviorcontroller = e0.getBehaviorController(); - this.b.forEach(behaviorcontroller::removeMemory); // Paper - decomp fix + for(MemoryModuleType moduleType : this.b){ + behaviorcontroller.removeMemory(moduleType); + } // Yatopia end } @Override @@ -98,29 +94,25 @@ public class BehaviorGate extends Behavior { RUN_ONE { @Override public void a(WeightedList> weightedlist, WorldServer worldserver, E e0, long i) { - // Tuinity start - remove streams - List>> list = weightedlist.getList(); - for (int index = 0, len = list.size(); index < len; ++index) { - Behavior behavior = list.get(index).getValue(); - if (behavior.getStatus() == Behavior.Status.STOPPED && behavior.e(worldserver, e0, i)) { // copied from removed code, make sure to update + // Tuinity start - remove streams // Yatopia start - port lithium + for (Behavior behavior : weightedlist) { + if (behavior.getStatus() == Behavior.Status.STOPPED && behavior.tryStarting(worldserver, e0, i)) { // copied from removed code, make sure to update break; } } - // Tuinity end - remove streams + // Tuinity end - remove streams // Yatopia end } }, TRY_ALL { @Override public void a(WeightedList> weightedlist, WorldServer worldserver, E e0, long i) { - // Tuinity start - remove streams - List>> list = weightedlist.getList(); - for (int index = 0, len = list.size(); index < len; ++index) { - Behavior behavior = list.get(index).getValue(); + // Tuinity start - remove streams // Yatopia start - port lithium + for (Behavior behavior : weightedlist) { if (behavior.getStatus() == Behavior.Status.STOPPED) { - behavior.e(worldserver, e0, i); // copied from removed code, make sure to update + behavior.tryStarting(worldserver, e0, i); // copied from removed code, make sure to update } } - // Tuinity end - remove streams + // Tuinity end - remove streams // Yatopia end } }; diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java b/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java index e2b5d6155bebdbf99b0850de7f9e1f5d342f9e2f..a3236e6359a2e72b4a41be4717780c20e2a31af3 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java @@ -8,11 +8,12 @@ import com.mojang.serialization.Dynamic; import com.mojang.serialization.DynamicOps; import java.util.Comparator; +import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.stream.Stream; -public class WeightedList { +public class WeightedList implements Iterable { // Yatopia - Port lithium protected final List> list; public final List> getList() { return this.list; } // Paper - decompile conflict // Tuinity - OBFHELPER private final Random b; @@ -116,4 +117,35 @@ public class WeightedList { }; } } -} + + + // Yatopia start - Port lithium + /** + * A wrapper type for an iterator over the entries of a {@link WeightedList} which de-references the contained + * values for consumers. + * + * @param The value type stored in each list entry + */ + class ListIterator implements Iterator { + private final Iterator> inner; + + public ListIterator(Iterator> inner) { + this.inner = inner; + } + + @Override + public boolean hasNext() { + return this.inner.hasNext(); + } + + @Override + public U next() { + return this.inner.next().getValue(); + } + } + + @Override + public Iterator iterator() { + 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 +++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java @@ -7,7 +7,12 @@ import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Tuinity import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; +import me.jellysquid.mods.lithium.common.util.Collector; +import me.jellysquid.mods.lithium.common.world.interests.PointOfInterestCollectors; +import me.jellysquid.mods.lithium.common.world.interests.PointOfInterestTypeHelper; + import java.io.File; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -193,7 +198,7 @@ public class VillagePlace extends RegionFileSection { public long count(Predicate predicate, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) { return a(predicate, blockposition, i, villageplace_occupancy); } // Purpur - OBFHELPER public long a(Predicate predicate, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) { - return this.cList(predicate, blockposition, i, villageplace_occupancy).size(); // Yatopia + return this.getAllWithinCircle(predicate, blockposition, i, villageplace_occupancy).size(); // Yatopia } public boolean a(VillagePlaceType villageplacetype, BlockPosition blockposition) { @@ -236,6 +241,35 @@ public class VillagePlace extends RegionFileSection { return ret; } + private List getAllWithinCircle(Predicate predicate, BlockPosition pos, int radius, Occupancy status) { + List points = new ArrayList<>(); + + this.collectWithinCircle(predicate, pos, radius, status, points::add); + + return points; + } + + private void collectWithinCircle(Predicate predicate, BlockPosition pos, int radius, Occupancy status, Collector collector) { + Collector filter = PointOfInterestCollectors.collectAllWithinRadius(pos, radius, collector); + Collector consumer = PointOfInterestCollectors.collectAllMatching(predicate, status, filter); + + int minChunkX = (pos.getX() - radius - 1) >> 4; + int minChunkZ = (pos.getZ() - radius - 1) >> 4; + + int maxChunkX = (pos.getX() + radius + 1) >> 4; + int maxChunkZ = (pos.getZ() + radius + 1) >> 4; + + // noinspection unchecked + + for (int x = minChunkX; x <= maxChunkX; x++) { + for (int z = minChunkZ; z <= maxChunkZ; z++) { + if (!this.collectWithinChunkColumn(x, z, consumer)) { + return; + } + } + } + } + public java.util.List cListPositions(Predicate predicate, Predicate recordFilter, BlockPosition pos, int i, VillagePlace.Occupancy occupancy) { int j = i * i; java.util.List ret = new java.util.ArrayList<>(); @@ -255,12 +289,12 @@ public class VillagePlace extends RegionFileSection { }); } + /** + * @reason Retrieve all points of interest in one operation + * @author JellySquid + */ public Stream a(Predicate predicate, ChunkCoordIntPair chunkcoordintpair, VillagePlace.Occupancy villageplace_occupancy) { - return IntStream.range(0, 16).boxed().map((integer) -> { - return this.d(SectionPosition.a(chunkcoordintpair, integer).s()); - }).filter(Optional::isPresent).flatMap((optional) -> { - return ((VillagePlaceSection) optional.get()).a(predicate, villageplace_occupancy); - }); + return this.getWithinChunkColumn(chunkcoordintpair.x, chunkcoordintpair.z).flatMap((set) -> set.get(predicate, villageplace_occupancy)); } // Yatopia start @@ -273,12 +307,16 @@ public class VillagePlace extends RegionFileSection { } return ret; } - // Yatopia end + + /** + * @reason Retrieve all points of interest in one operation + * @author JellySquid + */ public Stream a(Predicate predicate, Predicate predicate1, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) { return this.c(predicate, blockposition, i, villageplace_occupancy).map(VillagePlaceRecord::f).filter(predicate1); } - // Yatopia start + public java.util.List bList(Predicate predicate, Predicate posFilter, BlockPosition pos, int i, VillagePlace.Occupancy occupancy) { java.util.List ret = aList(predicate, posFilter, pos, i, occupancy); ret.sort(Comparator.comparingDouble((pos1) -> pos1.distanceSquared(pos))); @@ -404,24 +442,28 @@ public class VillagePlace extends RegionFileSection { this.updateDistanceTracking(i); // Tuinity - move to new distance tracking util } + /** + * @reason Avoid Stream API + * @author Jellysquid + */ public void a(ChunkCoordIntPair chunkcoordintpair, ChunkSection chunksection) { - SectionPosition sectionposition = SectionPosition.a(chunkcoordintpair, chunksection.getYPosition() >> 4); + SectionPosition sectionPos = SectionPosition.from(chunkcoordintpair, chunksection.getYPosition() >> 4); - SystemUtils.a(this.d(sectionposition.s()), (villageplacesection) -> { - villageplacesection.a((biconsumer) -> { - if (a(chunksection)) { - this.a(chunksection, sectionposition, biconsumer); - } + VillagePlaceSection set = this.get(sectionPos.asLong()).orElse(null); + if (set != null) { + set.refresh((consumer) -> { + if (PointOfInterestTypeHelper.shouldScan(chunksection)) { + this.scanAndPopulate(chunksection, sectionPos, consumer); + } }); - }, () -> { - if (a(chunksection)) { - VillagePlaceSection villageplacesection = (VillagePlaceSection) this.e(sectionposition.s()); + } else { + if (PointOfInterestTypeHelper.shouldScan(chunksection)) { + set = this.getOrCreate(sectionPos.asLong()); - this.a(chunksection, sectionposition, villageplacesection::a); + this.scanAndPopulate(chunksection, sectionPos, set::add); } - - }); + } } private static boolean a(ChunkSection chunksection) { @@ -431,6 +473,7 @@ public class VillagePlace extends RegionFileSection { return chunksection.a(set::contains); } + private void scanAndPopulate(ChunkSection chunksection, SectionPosition sectionposition, BiConsumer biconsumer){ a(chunksection, sectionposition, biconsumer); } // Yatopia - OBFHELPER private void a(ChunkSection chunksection, SectionPosition sectionposition, BiConsumer biconsumer) { sectionposition.tList().forEach((blockposition) -> { // Yatopia IBlockData iblockdata = chunksection.getType(SectionPosition.b(blockposition.getX()), SectionPosition.b(blockposition.getY()), SectionPosition.b(blockposition.getZ())); diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlaceSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlaceSection.java index 866e9a434423702d2edaf9b52fa0e6219e13c2d7..640aee2b8afd66082cc867b14980f06d9811fc28 100644 --- a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlaceSection.java +++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlaceSection.java @@ -26,6 +26,7 @@ import net.minecraft.core.SectionPosition; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.util.Supplier; +import me.jellysquid.mods.lithium.common.util.Collector; public class VillagePlaceSection { @@ -73,7 +74,28 @@ public class VillagePlaceSection { } return ret; } + + public boolean get(Predicate type, VillagePlace.Occupancy status, Collector consumer) { + for (Map.Entry> entry : this.getData().entrySet()) { + if (!type.test(entry.getKey())) { + continue; + } + + for (VillagePlaceRecord poi : entry.getValue()) { + if (!status.getPredicate().test(poi)) { + continue; + } + + if (!consumer.collect(poi)) { + return false; + } + } + } + + return true; + } // Yatopia end + public Stream get(Predicate predicate, VillagePlace.Occupancy villageplace_occupancy){ return a(predicate, villageplace_occupancy); } // Yatopia - OBFHELPER public Stream a(Predicate predicate, VillagePlace.Occupancy villageplace_occupancy) { return this.c.entrySet().stream().filter((entry) -> { return predicate.test(entry.getKey()); @@ -82,6 +104,7 @@ public class VillagePlaceSection { }).filter(villageplace_occupancy.a()); } + public void add(BlockPosition blockPosition, VillagePlaceType villagePlaceType) { a(blockPosition, villagePlaceType); } // Yatopia - OBFHELPER public void a(BlockPosition blockposition, VillagePlaceType villageplacetype) { if (this.a(new VillagePlaceRecord(blockposition, villageplacetype, this.d))) { VillagePlaceSection.LOGGER.debug("Added POI of type {} @ {}", new Supplier[]{() -> { @@ -160,6 +183,7 @@ public class VillagePlaceSection { return villageplacerecord != null ? Optional.of(villageplacerecord.g()) : Optional.empty(); } + public void refresh(Consumer> consumer){ a(consumer); } // Yatopia - OBFHELPER public void a(Consumer> consumer) { if (!this.e) { Short2ObjectMap short2objectmap = new Short2ObjectOpenHashMap(this.b); diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlaceType.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlaceType.java index 4af526ecbed506161cb021ea320b0f21112d7bf0..4a7b3750f1b3ce3143053215dbca6da6eee052bd 100644 --- a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlaceType.java +++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlaceType.java @@ -4,7 +4,11 @@ import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; +import it.unimi.dsi.fastutil.objects.ObjectArraySet; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; +import me.jellysquid.mods.lithium.common.world.interests.PointOfInterestTypeHelper; + import java.util.Map; import java.util.Optional; import java.util.Set; @@ -46,7 +50,8 @@ public class VillagePlaceType { }).filter((iblockdata) -> { return iblockdata.get(BlockBed.PART) == BlockPropertyBedPart.HEAD; }).collect(ImmutableSet.toImmutableSet()); - private static final Map A = Maps.newHashMap(); + private static final Map A = new Reference2ReferenceOpenHashMap<>(); // Yatopia - Port Lithium + public static final Map getBlockStateToPointOfInterestType(){ return A; } // Yatopia - OBFHELPER public static final VillagePlaceType c = a("unemployed", ImmutableSet.of(), 1, VillagePlaceType.a, 1); public static final VillagePlaceType d = a("armorer", a(Blocks.BLAST_FURNACE), 1, 1); public static final VillagePlaceType e = a("butcher", a(Blocks.SMOKER), 1, 1); @@ -75,6 +80,12 @@ public class VillagePlaceType { private final Predicate E; private final int F; + // Yatopia Start - Port lithium + static { + PointOfInterestTypeHelper.init(new ObjectArraySet<>(getBlockStateToPointOfInterestType().keySet())); + } + // Yatopia End + private static Set a(Block block) { return ImmutableSet.copyOf(block.getStates().a()); } diff --git a/src/main/java/net/minecraft/world/entity/raid/EntityRaider.java b/src/main/java/net/minecraft/world/entity/raid/EntityRaider.java index 8eec32af12c69e1963dcd304a25ec4811b2f1f5a..e4bb472002934f749ff0e2f43744c73fcd74d3cc 100644 --- a/src/main/java/net/minecraft/world/entity/raid/EntityRaider.java +++ b/src/main/java/net/minecraft/world/entity/raid/EntityRaider.java @@ -47,9 +47,10 @@ import net.minecraft.world.phys.Vec3D; public abstract class EntityRaider extends EntityMonsterPatrolling { + private static final ItemStack CACHED_OMINOUS_BANNER = Raid.getOminousBanner(); // Yatopia - Port lithium protected static final DataWatcherObject c = DataWatcher.a(EntityRaider.class, DataWatcherRegistry.i); private static final Predicate b = (entityitem) -> { - return !entityitem.p() && entityitem.isAlive() && ItemStack.matches(entityitem.getItemStack(), Raid.s()); + return !entityitem.p() && entityitem.isAlive() && ItemStack.matches(entityitem.getItemStack(), CACHED_OMINOUS_BANNER); // Yatopia - Port lithium }; @Nullable protected Raid d; @@ -151,7 +152,7 @@ public abstract class EntityRaider extends EntityMonsterPatrolling { } } - if (!itemstack.isEmpty() && ItemStack.matches(itemstack, Raid.s()) && entityhuman != null) { + if (!itemstack.isEmpty() && ItemStack.matches(itemstack, CACHED_OMINOUS_BANNER) && entityhuman != null) { // Yatopia - Port lithium MobEffect mobeffect = entityhuman.getEffect(MobEffects.BAD_OMEN); byte b0 = 1; int i; @@ -242,7 +243,7 @@ public abstract class EntityRaider extends EntityMonsterPatrolling { ItemStack itemstack = entityitem.getItemStack(); boolean flag = this.fb() && this.fa().b(this.fc()) != null; - if (this.fb() && !flag && ItemStack.matches(itemstack, Raid.s())) { + if (this.fb() && !flag && ItemStack.matches(itemstack, CACHED_OMINOUS_BANNER)) { // Yatopia - Port lithium EnumItemSlot enumitemslot = EnumItemSlot.HEAD; ItemStack itemstack1 = this.getEquipment(enumitemslot); double d0 = (double) this.e(enumitemslot); @@ -535,7 +536,7 @@ public abstract class EntityRaider extends EntityMonsterPatrolling { if ((!getRaider().world.purpurConfig.pillagerBypassMobGriefing && !getRaider().world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) || !getRaider().canPickupLoot()) return false; // Paper - respect game and entity rules for picking up items // Purpur Raid raid = this.b.fa(); - if (this.b.fb() && !this.b.fa().a() && this.b.eN() && !ItemStack.matches(this.b.getEquipment(EnumItemSlot.HEAD), Raid.s())) { + if (this.b.fb() && !this.b.fa().a() && this.b.eN() && !ItemStack.matches(this.b.getEquipment(EnumItemSlot.HEAD), CACHED_OMINOUS_BANNER)) { // Yatopia - Port lithium EntityRaider entityraider = raid.b(this.b.fc()); if (entityraider == null || !entityraider.isAlive()) { diff --git a/src/main/java/net/minecraft/world/entity/raid/Raid.java b/src/main/java/net/minecraft/world/entity/raid/Raid.java index f9d03b6d11ad5ffbfe5be072e8631f046bcd1646..52def6de2225cf5ed70c807b74fb5c2ccd133503 100644 --- a/src/main/java/net/minecraft/world/entity/raid/Raid.java +++ b/src/main/java/net/minecraft/world/entity/raid/Raid.java @@ -227,8 +227,14 @@ public class Raid { this.status = Raid.Status.STOPPED; } + private boolean isBarDirty; // Yatopia - Port lithium public void o() { if (!this.isStopped()) { + if (this.isBarDirty) { // Yatopia Start - Port lithium + this.bossBattle.setProgress(MathHelper.a(this.sumMobHealth() / this.totalHealth, 0.0F, 1.0F)); + + this.isBarDirty = false; + } // Yatopia End if (this.status == Raid.Status.ONGOING) { boolean flag = this.active; @@ -596,7 +602,7 @@ public class Raid { } public void updateProgress() { - this.bossBattle.setProgress(MathHelper.a(this.sumMobHealth() / this.totalHealth, 0.0F, 1.0F)); + this.isBarDirty = true; // Yatopia - Port lithium } public float sumMobHealth() { @@ -647,6 +653,7 @@ public class Raid { this.world.getPersistentRaid().b(); } + public static ItemStack getOminousBanner(){ return s(); } // Yatopia - OBFHELPER 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 +++ 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import me.jellysquid.mods.lithium.common.ai.pathing.PathNodeDefaults; public class Block extends BlockBase implements IMaterial { @@ -89,6 +90,17 @@ public class Block extends BlockBase implements IMaterial { return timing; } // Paper end + + // Yatopia start - Port lithium + public net.minecraft.world.level.pathfinder.PathType getPathNodeType(IBlockData state) { + return PathNodeDefaults.getNodeType(state); + } + + public net.minecraft.world.level.pathfinder.PathType getNeighborPathNodeType(IBlockData state) { + return PathNodeDefaults.getNeighborNodeType(state); + } + // Yatopia end + public Material getMaterial() { return material; } // Purpur - OBFHELPER @Nullable private String name; diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBase.java b/src/main/java/net/minecraft/world/level/block/state/BlockBase.java index b6de70c3630d96d0782a657c0389ce03839d8c43..611066964d11b2da7ab6dd59c6083c5c25c75de7 100644 --- a/src/main/java/net/minecraft/world/level/block/state/BlockBase.java +++ b/src/main/java/net/minecraft/world/level/block/state/BlockBase.java @@ -3,6 +3,8 @@ package net.minecraft.world.level.block.state; import com.google.common.collect.ImmutableMap; import com.mojang.serialization.MapCodec; +import org.apache.commons.lang3.Validate; + import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -63,6 +65,7 @@ import net.minecraft.world.phys.Vec3D; import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraft.world.phys.shapes.VoxelShapeCollision; import net.minecraft.world.phys.shapes.VoxelShapes; +import net.minecraft.world.level.pathfinder.PathType; public abstract class BlockBase { @@ -349,6 +352,8 @@ public abstract class BlockBase { public abstract static class BlockData extends IBlockDataHolder { + public net.minecraft.world.level.pathfinder.PathType pathNodeType = PathType.OPEN; // Yatopia - Port lithium + public net.minecraft.world.level.pathfinder.PathType pathNodeTypeNeighbor = PathType.OPEN; // Yatopia - Port lithium private final int b; public final int getEmittedLight() { return this.b; } // Tuinity - OBFHELPER private final boolean e; public final boolean isTransparentOnSomeFaces() { return this.e; } // Tuinity - OBFHELPER private final boolean f; @@ -420,6 +425,16 @@ public abstract class BlockBase { } // Tuinity end + // Yatopia start - Port lithium + public net.minecraft.world.level.pathfinder.PathType getPathNodeType() { + return this.pathNodeType; + } + + public net.minecraft.world.level.pathfinder.PathType getNeighborPathNodeType() { + return this.pathNodeTypeNeighbor; + } + // Yatopia end + public void a() { this.fluid = this.getBlock().d(this.p()); // Paper - moved from getFluid() this.isTicking = this.getBlock().isTicking(this.p()); // Paper - moved from isTicking() @@ -429,6 +444,9 @@ public abstract class BlockBase { this.shapeExceedsCube = this.a == null || this.a.c; // Tuinity - moved from actual method to here this.staticPathType = null; // Tuinity - cache static path type this.neighbourOverridePathType = null; // Tuinity - cache static path types + IBlockData state = this.getBlockData(); // Yatopia - Port lithium + this.pathNodeType = Validate.notNull(this.getBlock().getPathNodeType(state)); // Yatopia - Port lithium + this.pathNodeTypeNeighbor = Validate.notNull(this.getBlock().getNeighborPathNodeType(state)); // Yatopia - Port lithium 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 +++ b/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java @@ -177,6 +177,7 @@ public class ChunkSection { return 2 + this.blockIds.c(); } + public boolean hasAny(Predicate predicate) { return a(predicate); } // Yatopia - OBFHELPER public boolean a(Predicate predicate) { return this.blockIds.contains(predicate); } diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java index f70c14385c95763b5f270a6e2ce372cf047ba7bb..5968712468ad1bea4894d31d0d08b213735743e5 100644 --- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java +++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java @@ -13,10 +13,13 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; import java.io.File; import java.io.IOException; +import java.util.BitSet; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.function.BooleanSupplier; import java.util.function.Function; +import java.util.stream.Stream; import javax.annotation.Nullable; import net.minecraft.SharedConstants; import net.minecraft.SystemUtils; @@ -29,27 +32,118 @@ import net.minecraft.world.level.ChunkCoordIntPair; import net.minecraft.world.level.World; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import me.jellysquid.mods.lithium.common.util.Collector; +import me.jellysquid.mods.lithium.common.util.collections.ListeningLong2ObjectOpenHashMap; public class RegionFileSection extends RegionFileCache implements AutoCloseable { // Paper - nuke IOWorker private static final Logger LOGGER = LogManager.getLogger(); // Paper - nuke IOWorker - private final Long2ObjectMap> c = new Long2ObjectOpenHashMap(); protected final Long2ObjectMap> getDataBySection() { return this.c; } // Tuinity - OBFHELPER + private Long2ObjectMap> c = new Long2ObjectOpenHashMap(); protected final Long2ObjectMap> getDataBySection() { return this.c; } // Tuinity - OBFHELPER public final LongLinkedOpenHashSet d = new LongLinkedOpenHashSet(); protected final LongLinkedOpenHashSet getDirtySections() { return this.d; } // Paper - private -> public // Tuinity - OBFHELPER private final Function> e; private final Function f; private final DataFixer g; private final DataFixTypes h; + private Long2ObjectOpenHashMap columns; // Yatopia - Port lithium + public RegionFileSection(File file, Function> function, Function function1, DataFixer datafixer, DataFixTypes datafixtypes, boolean flag) { super(file, flag); // Paper - nuke IOWorker this.e = function; this.f = function1; this.g = datafixer; this.h = datafixtypes; + // Yatopia Start - Port lithium + this.columns = new Long2ObjectOpenHashMap<>(); + this.c = new ListeningLong2ObjectOpenHashMap<>(this::onEntryAdded, this::onEntryRemoved); //this.b = new IOWorker(file, flag, file.getName()); // Paper - nuke IOWorker } + private void onEntryRemoved(long key, Optional value) { + // NO-OP... vanilla never removes anything, leaking entries. + // We might want to fix this. + } + + private void onEntryAdded(long key, Optional value) { + int y = SectionPosition.unpackY(key); + + // We only care about items belonging to a valid sub-chunk + if (y < 0 || y >= 16) { + return; + } + + int x = SectionPosition.unpackX(key); + int z = SectionPosition.unpackZ(key); + + long pos = ChunkCoordIntPair.pair(x, z); + + BitSet flags = this.columns.get(pos); + + if (flags == null) { + this.columns.put(pos, flags = new BitSet(16)); + } + + flags.set(y, value.isPresent()); + } + + public Stream getWithinChunkColumn(int chunkX, int chunkZ) { + BitSet flags = this.getCachedColumnInfo(chunkX, chunkZ); + + // No items are present in this column + if (flags.isEmpty()) { + return Stream.empty(); + } + + return flags.stream() + .mapToObj((chunkY) -> this.getDataBySection().get(SectionPosition.asLong(chunkX, chunkY, chunkZ)).orElse(null)) + .filter(Objects::nonNull); + } + + public boolean collectWithinChunkColumn(int chunkX, int chunkZ, Collector consumer) { + BitSet flags = this.getCachedColumnInfo(chunkX, chunkZ); + + // No items are present in this column + if (flags.isEmpty()) { + return true; + } + + for (int chunkY = flags.nextSetBit(0); chunkY >= 0; chunkY = flags.nextSetBit(chunkY + 1)) { + R obj = this.getDataBySection().get(SectionPosition.asLong(chunkX, chunkY, chunkZ)).orElse(null); + + if (obj != null && !consumer.collect(obj)) { + return false; + } + } + + return true; + } + + private BitSet getCachedColumnInfo(int chunkX, int chunkZ) { + long pos = ChunkCoordIntPair.pair(chunkX, chunkZ); + + BitSet flags = this.getColumnInfo(pos, false); + + if (flags != null) { + return flags; + } + + this.loadDataAt(new ChunkCoordIntPair(pos)); + + return this.getColumnInfo(pos, true); + } + + private BitSet getColumnInfo(long pos, boolean required) { + BitSet set = this.columns.get(pos); + + if (set == null && required) { + throw new NullPointerException("No data is present for column: " + new ChunkCoordIntPair(pos)); + } + + return set; + } + // Yatopia end + protected void a(BooleanSupplier booleansupplier) { while (!this.d.isEmpty() && booleansupplier.getAsBoolean()) { ChunkCoordIntPair chunkcoordintpair = SectionPosition.a(this.d.firstLong()).r(); // Paper - conflict here to avoid obfhelpers @@ -92,6 +186,7 @@ public class RegionFileSection extends RegionFileCache implements AutoCloseab return true; } // Tuinity end - actually unload POI data + protected Optional get(long i){ return d(i); } // Yatopia - OBFHELPER @Nullable public final Optional getIfLoaded(long value) { return this.c(value); } // Tuinity - OBFHELPER // Tuinity - OBFHELPER @Nullable protected Optional c(long i) { // Tuinity - OBFHELPER @@ -125,6 +220,7 @@ public class RegionFileSection extends RegionFileCache implements AutoCloseab return World.b(SectionPosition.c(sectionposition.b())); } + protected R getOrCreate(long i){ return e(i); } // Yatopia - OBFHELPER protected R e(long i) { Optional optional = this.d(i); @@ -140,6 +236,7 @@ public class RegionFileSection extends RegionFileCache implements AutoCloseab } } + public void loadDataAt(ChunkCoordIntPair chunkcoordintpair) { b(chunkcoordintpair); } // Yatopia - OBFHELPER private void b(ChunkCoordIntPair chunkcoordintpair) { // Paper start - load data in function this.loadInData(chunkcoordintpair, this.c(chunkcoordintpair)); diff --git a/src/main/java/net/minecraft/world/level/pathfinder/Pathfinder.java b/src/main/java/net/minecraft/world/level/pathfinder/Pathfinder.java index 76e19f3a4ae988f6f3b59763d639fa5e084fa0bf..be67d9a931aec19105305f316959e97813c4feda 100644 --- a/src/main/java/net/minecraft/world/level/pathfinder/Pathfinder.java +++ b/src/main/java/net/minecraft/world/level/pathfinder/Pathfinder.java @@ -3,6 +3,8 @@ package net.minecraft.world.level.pathfinder; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import me.jellysquid.mods.lithium.common.ai.pathing.PathNodeCache; // Yatopia + import java.util.Comparator; import java.util.Iterator; import java.util.List; @@ -48,7 +50,7 @@ public class Pathfinder { @Nullable private PathEntity a(PathPoint pathpoint, List> list, float f, int i, float f1) { // Paper - optimize collection //Set set = map.keySet(); // Paper - + PathNodeCache.enableChunkCache(); // Yatopia - Port lithium pathpoint.e = 0.0F; pathpoint.f = this.a(pathpoint, list); // Paper - optimize collection pathpoint.g = pathpoint.f; diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathfinderNormal.java b/src/main/java/net/minecraft/world/level/pathfinder/PathfinderNormal.java index 9d08094165cf18d99116b5c721fff888f3cb42e2..b95804e73050dc7eb9786ca4bb5ea095904bad45 100644 --- a/src/main/java/net/minecraft/world/level/pathfinder/PathfinderNormal.java +++ b/src/main/java/net/minecraft/world/level/pathfinder/PathfinderNormal.java @@ -4,6 +4,9 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2BooleanMap; import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap; +import me.jellysquid.mods.lithium.common.ai.pathing.PathNodeCache; // Yatopia +import me.jellysquid.mods.lithium.common.world.WorldHelper; // Yatopia + import java.util.EnumSet; import java.util.Iterator; import javax.annotation.Nullable; @@ -30,6 +33,10 @@ import net.minecraft.world.level.material.Material; import net.minecraft.world.phys.AxisAlignedBB; import net.minecraft.world.phys.Vec3D; import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraft.world.level.chunk.ChunkSection; // Yatopia +import net.minecraft.world.level.chunk.IChunkAccess; // Yatopia +import net.minecraft.world.level.World; // Yatopia +import net.minecraft.world.level.ICollisionAccess; // Yatopia public class PathfinderNormal extends PathfinderAbstract { @@ -476,68 +483,111 @@ public class PathfinderNormal extends PathfinderAbstract { return pathtype; } + /** + * @reason Use optimized implementation + * @author JellySquid + */ public static PathType a(IBlockAccess iblockaccess, BlockPosition.MutableBlockPosition blockposition_mutableblockposition, PathType pathtype) { - int i = blockposition_mutableblockposition.getX(); - int j = blockposition_mutableblockposition.getY(); - int k = blockposition_mutableblockposition.getZ(); + // Yatopia start - Port lithium + int x = blockposition_mutableblockposition.getX(); + int y = blockposition_mutableblockposition.getY(); + int z = blockposition_mutableblockposition.getZ(); + + ChunkSection section = null; + + // Check that all the block's neighbors are within the same chunk column. If so, we can isolate all our block + // reads to just one chunk and avoid hits against the server chunk manager. + if (iblockaccess instanceof ICollisionAccess && WorldHelper.areNeighborsWithinSameChunk(blockposition_mutableblockposition)) { + // If the y-coordinate is within bounds, we can cache the chunk section. Otherwise, the if statement to check + // if the cached chunk section was initialized will early-exit. + if (!World.b(y)) { + // This cast is always safe and is necessary to obtain direct references to chunk sections. + IChunkAccess chunk = (IChunkAccess) ((ICollisionAccess) iblockaccess).c(x >> 4, z >> 4); + + // If the chunk is absent, the cached section above will remain null, as there is no chunk section anyways. + // An empty chunk or section will never pose any danger sources, which will be caught later. + if (chunk != null) { + section = chunk.getSections()[y >> 4]; + } + } - for (int l = -1; l <= 1; ++l) { - for (int i1 = -1; i1 <= 1; ++i1) { - for (int j1 = -1; j1 <= 1; ++j1) { - if (l != 0 || j1 != 0) { - blockposition_mutableblockposition.d(i + l, j + i1, k + j1); - // Paper start - IBlockData iblockdata = iblockaccess.getTypeIfLoaded(blockposition_mutableblockposition); - if (iblockdata == null) { - pathtype = PathType.BLOCKED; - } else { - // Paper end - // Tuinity start - reduce pathfinder branching - if (iblockdata.neighbourOverridePathType == PathType.OPEN) { - continue; - } else if (iblockdata.neighbourOverridePathType != null) { - return iblockdata.neighbourOverridePathType; - } - // Tuinity end - reduce pathfinder branching - if (iblockdata.a(Blocks.CACTUS)) { - return iblockdata.neighbourOverridePathType = PathType.DANGER_CACTUS; // Tuinity - reduce pathfinder branching - } + // If we can guarantee that blocks won't be modified while the cache is active, try to see if the chunk + // section is empty or contains any dangerous blocks within the palette. If not, we can assume any checks + // against this chunk section will always fail, allowing us to fast-exit. + if (ChunkSection.isEmpty(section) || PathNodeCache.isSectionSafeAsNeighbor(section)) { + return pathtype; + } + } - if (iblockdata.a(Blocks.SWEET_BERRY_BUSH) || iblockdata.a(Blocks.STONECUTTER)) { // Purpur - return iblockdata.neighbourOverridePathType = PathType.DANGER_OTHER; // Tuinity - reduce pathfinder branching - } + int xStart = x - 1; + int yStart = y - 1; + int zStart = z - 1; + + int xEnd = x + 1; + int yEnd = y + 1; + int zEnd = z + 1; + + // Vanilla iteration order is XYZ + for (int adjX = xStart; adjX <= xEnd; adjX++) { + for (int adjY = yStart; adjY <= yEnd; adjY++) { + for (int adjZ = zStart; adjZ <= zEnd; adjZ++) { + // Skip the vertical column of the origin block + if (adjX == x && adjZ == z) { + continue; + } - if (a(iblockdata)) { - return iblockdata.neighbourOverridePathType = PathType.DANGER_FIRE; // Tuinity - reduce pathfinder branching - } + IBlockData state; - if (iblockdata.getFluid().a((Tag) TagsFluid.WATER)) { // Paper - remove another getType call - return iblockdata.neighbourOverridePathType = PathType.WATER_BORDER; // Tuinity - reduce pathfinder branching - } - iblockdata.neighbourOverridePathType = PathType.OPEN; // Tuinity - reduce pathfinder branching - } // Paper + // If we're not accessing blocks outside a given section, we can greatly accelerate block state + // retrieval by calling upon the cached chunk directly. + if (section != null) { + state = section.getType(adjX & 15, adjY & 15, adjZ & 15); + } else { + state = iblockaccess.getType(blockposition_mutableblockposition.setValues(adjX, adjY, adjZ)); + } + + // Ensure that the block isn't air first to avoid expensive hash table accesses + if (state.isAir()) { + continue; + } + + PathType neighborType = PathNodeCache.getNeighborPathNodeType(state); + + if (neighborType != PathType.OPEN) { + return neighborType; } } } } - return pathtype; + return pathtype; // Yatopia end } + /** + * @reason Use optimized implementation which avoids scanning blocks for dangers where possible + * @author JellySquid + */ protected static PathType b(IBlockAccess iblockaccess, BlockPosition blockposition) { - IBlockData iblockdata = iblockaccess.getTypeIfLoaded(blockposition); // Paper - if (iblockdata == null) return PathType.BLOCKED; // Paper - // Tuinity start - reduce pathfinder branches - if (iblockdata.staticPathType != null) { - return iblockdata.staticPathType; - } - if (iblockdata.getShapeCache() == null) { - // while it might be called static, it might vary on shape! However, only a few blocks have variable shape. - // So we rarely enter here. - return getStaticTypeSlow(iblockaccess, blockposition, iblockdata); - } else { - return iblockdata.staticPathType = getStaticTypeSlow(iblockaccess, blockposition, iblockdata); + // Yatopia start - Port lithium + IBlockData blockState = iblockaccess.getType(blockposition); + if (blockState == null) return PathType.BLOCKED; + PathType type = PathNodeCache.getPathNodeType(blockState); + + // If the node type is open, it means that we were unable to determine a more specific type, so we need + // to check the fallback path. + if (type == PathType.OPEN) { + // This is only ever called in vanilla after all other possibilities are exhausted, but before fluid checks + // It should be safe to perform it last in actuality and take advantage of the cache for fluid types as well + // since fluids will always pass this check. + if (!blockState.a(iblockaccess, blockposition, PathMode.LAND)) { + return PathType.BLOCKED; + } + + // All checks succeed, this path node really is open! + return PathType.OPEN; } + return type; + // Yatopia end } 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),