From 133e09ced4b65a27f9a95e3d9856513962423b0d Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Mon, 12 Apr 2021 13:03:59 -0400 Subject: [PATCH] MEGA port PR (#459) * add all of the patches * cleanup patches --- PATCHES.md | 8 + patches/server/0064-tic-tacs-unblocking.patch | 407 ++++ ...-BlockPos.iterateOutwards-by-caching.patch | 196 ++ patches/server/0066-lithium-AI.patch | 2153 +++++++++++++++++ patches/server/0067-lithium-block.patch | 231 ++ patches/server/0068-lithium-entity.patch | 59 + patches/server/0069-lithium-gen.patch | 39 + patches/server/0070-lithium-shape.patch | 357 +++ ...king-block-entities-that-are-doing-n.patch | 1218 ++++++++++ 9 files changed, 4668 insertions(+) create mode 100644 patches/server/0064-tic-tacs-unblocking.patch create mode 100644 patches/server/0065-lithium-optimize-BlockPos.iterateOutwards-by-caching.patch create mode 100644 patches/server/0066-lithium-AI.patch create mode 100644 patches/server/0067-lithium-block.patch create mode 100644 patches/server/0068-lithium-entity.patch create mode 100644 patches/server/0069-lithium-gen.patch create mode 100644 patches/server/0070-lithium-shape.patch create mode 100644 patches/server/0071-lithium-skip-ticking-block-entities-that-are-doing-n.patch diff --git a/PATCHES.md b/PATCHES.md index 442d1c48..10cb2d25 100644 --- a/PATCHES.md +++ b/PATCHES.md @@ -422,6 +422,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 | | | server | lithium DataTrackerMixin | JellySquid | tr7zw | | server | lithium HashedList | JellySquid | | | server | lithium MixinBox | JellySquid | | @@ -430,6 +431,13 @@ # Patches | server | lithium NoiseChunkGeneratorMixin | JellySquid | | | server | lithium PerlinNoiseSamplerMixin | JellySquid | Bud Gidiere | | server | lithium VoronoiBiomeAccessTypeMixin | JellySquid | | +| server | lithium block | JellySquid | | +| server | lithium entity | JellySquid | | | server | lithium enum_values | JellySquid | | +| server | lithium gen | Hugo Planque | | | server | lithium reduce allocations | JellySquid | Mykyta Komarnytskyy | +| server | lithium shape | JellySquid | | | server | lithium: cache chunk gen sea level | SuperCoder7979 | | +| server | lithium: optimize `BlockPos.iterateOutwards` by caching | 2No2Name | | +| server | lithium: skip ticking block entities that are doing nothing | 2No2Name | | +| server | tic-tacs: unblocking | Gegy | | diff --git a/patches/server/0064-tic-tacs-unblocking.patch b/patches/server/0064-tic-tacs-unblocking.patch new file mode 100644 index 00000000..7946f27a --- /dev/null +++ b/patches/server/0064-tic-tacs-unblocking.patch @@ -0,0 +1,407 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Gegy +Date: Tue, 9 Feb 2021 13:23:12 -0500 +Subject: [PATCH] tic-tacs: unblocking + +Code originally licenced under LGPLv3 for the tic-tacs project: https://github.com/Gegy/tic-tacs + +diff --git a/src/main/java/net/gegy1000/tictacs/NonBlockingWorldAccess.java b/src/main/java/net/gegy1000/tictacs/NonBlockingWorldAccess.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0d99f3b4930045632d88fa4acb3b8159ee9cf9f3 +--- /dev/null ++++ b/src/main/java/net/gegy1000/tictacs/NonBlockingWorldAccess.java +@@ -0,0 +1,29 @@ ++package net.gegy1000.tictacs; ++ ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.material.Fluid; ++import net.minecraft.world.level.material.FluidTypes; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.world.level.IWorldReader; ++import net.minecraft.world.level.material.FluidTypes; ++ ++public interface NonBlockingWorldAccess extends IWorldReader { ++ ++ public static final IBlockData DEFAULT_BLOCK_STATE = Blocks.AIR.getBlockData(); ++ public static final Fluid DEFAULT_FLUID_STATE = FluidTypes.EMPTY.getFluidData(); ++ ++ default IBlockData getBlockStateIfLoaded(BlockPosition pos) { ++ if (this.isLoaded(pos)) { ++ return this.getType(pos); ++ } ++ return DEFAULT_BLOCK_STATE; ++ } ++ ++ default Fluid getFluidStateIfLoaded(BlockPosition pos) { ++ if (this.isLoaded(pos)) { ++ return this.getFluid(pos); ++ } ++ return DEFAULT_FLUID_STATE; ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index e6bc64037f79a1380bcb99ff3c4455e0d9cb8d5d..5d17deee8c41e4f8e48fe93906af73504a381958 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -657,6 +657,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + } + + public void playerTick() { ++ if (!this.world.isLoaded(this.getChunkCoordinates())) return; // Yatopia - tic-tac unblocking ++ + try { + if (valid && !this.isSpectator() || this.world.isLoaded(this.getChunkCoordinates())) { // Paper - don't tick dead players that are not in the world currently (pending respawn) + super.tick(); +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 8ea7ad63c0f091102f793b3c1f90b4121c4d74e0..3f7072e993da896b36b85ec08849b46c854681a5 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -176,8 +176,9 @@ import org.bukkit.event.weather.LightningStrikeEvent; + import org.bukkit.event.world.TimeSkipEvent; + // CraftBukkit end + import it.unimi.dsi.fastutil.ints.IntArrayList; // Tuinity ++import net.gegy1000.tictacs.NonBlockingWorldAccess; // Yatopia + +-public class WorldServer extends World implements GeneratorAccessSeed { ++public class WorldServer extends World implements GeneratorAccessSeed, NonBlockingWorldAccess { // Yatopia + + public static final BlockPosition a = new BlockPosition(100, 50, 0); + private static final Logger LOGGER = LogManager.getLogger(); +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 4492f2579da79bd58def922894f40422fbeaf54c..cd1f94e5c1c923ee9d8dd451406ac2bee360e9c3 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -142,9 +142,12 @@ import org.bukkit.event.entity.EntityPoseChangeEvent; + import org.bukkit.event.player.PlayerTeleportEvent; + import org.bukkit.plugin.PluginManager; + // CraftBukkit end ++import net.gegy1000.tictacs.NonBlockingWorldAccess; // Yatopia + + public abstract class Entity implements INamableTileEntity, ICommandListener, net.minecraft.server.KeyedObject { // Paper + ++ public boolean updateNeeded; // Yatopia ++ private boolean chunkPosUpdateRequested; // Yatopia + // CraftBukkit start + private static final int CURRENT_LEVEL = 2; + boolean preserveMotion = true; // Paper - keep initial motion on first setPositionRotation +@@ -772,6 +775,12 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + // Tuinity end - detailed watchdog information + public void move(EnumMoveType enummovetype, Vec3D vec3d) { ++ // Yatopia start - tic-tacs unblocking ++ BlockPosition pos = this.getChunkCoordinates(); ++ if (!this.world.isLoaded(pos)) { ++ return; ++ } ++ // Yatopia end + // Tuinity start - detailed watchdog information + com.tuinity.tuinity.util.TickThread.ensureTickThread("Cannot move an entity off-main"); + synchronized (this.posLock) { +@@ -822,7 +831,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + this.v = vec3d.y != vec3d1.y; + this.onGround = this.v && vec3d.y < 0.0D; + BlockPosition blockposition = this.ap(); +- IBlockData iblockdata = this.world.getType(blockposition); ++ IBlockData iblockdata = this.world.getBlockStateIfLoaded(blockposition); // Yatopia + + this.a(vec3d1.y, this.onGround, iblockdata, blockposition); + Vec3D vec3d2 = this.getMot(); +@@ -936,9 +945,9 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + int k = MathHelper.floor(this.loc.z); + BlockPosition blockposition = new BlockPosition(i, j, k); + +- if (this.world.getType(blockposition).isAir()) { ++ if (this.world.getBlockStateIfLoaded(blockposition).isAir()) { // Yatopia + BlockPosition blockposition1 = blockposition.down(); +- IBlockData iblockdata = this.world.getType(blockposition1); ++ IBlockData iblockdata = this.world.getBlockStateIfLoaded(blockposition1); // Yatopia + Block block = iblockdata.getBlock(); + + if (block.a((Tag) TagsBlock.FENCES) || block.a((Tag) TagsBlock.WALLS) || block instanceof BlockFenceGate) { +@@ -950,17 +959,21 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + protected float getBlockJumpFactor() { +- float f = this.world.getType(this.getChunkCoordinates()).getBlock().getJumpFactor(); +- float f1 = this.world.getType(this.as()).getBlock().getJumpFactor(); ++ // Yatopia start - tic-tacs unblocking ++ float f = this.world.getBlockStateIfLoaded(this.getChunkCoordinates()).getBlock().getJumpFactor(); ++ float f1 = this.world.getBlockStateIfLoaded(this.as()).getBlock().getJumpFactor(); ++ // Yatopia end + + return (double) f == 1.0D ? f1 : f; + } + + protected float getBlockSpeedFactor() { +- Block block = this.world.getType(this.getChunkCoordinates()).getBlock(); ++ // Yatopia start - tic-tacs unblocking ++ Block block = this.world.getBlockStateIfLoaded(this.getChunkCoordinates()).getBlock(); + float f = block.getSpeedFactor(); + +- return block != Blocks.WATER && block != Blocks.BUBBLE_COLUMN ? ((double) f == 1.0D ? this.world.getType(this.as()).getBlock().getSpeedFactor() : f) : f; ++ return block != Blocks.WATER && block != Blocks.BUBBLE_COLUMN ? ((double) f == 1.0D ? this.world.getBlockStateIfLoaded(this.as()).getBlock().getSpeedFactor() : f) : f; ++ // Yatopia end + } + + protected BlockPosition as() { +@@ -1305,7 +1318,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + for (int i = blockposition.getX(); i <= blockposition1.getX(); ++i) { + // Tuinity end - reorder iteration to more cache aware + blockposition_mutableblockposition.d(i, j, k); +- IBlockData iblockdata = this.world.getType(blockposition_mutableblockposition); ++ IBlockData iblockdata = this.world.getBlockStateIfLoaded(blockposition_mutableblockposition); + + // Tuinity start - move fire checking in here - reuse getType from this method + if (checkFire) { +@@ -1341,7 +1354,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + + protected void b(BlockPosition blockposition, IBlockData iblockdata) { + if (!iblockdata.getMaterial().isLiquid()) { +- IBlockData iblockdata1 = this.world.getType(blockposition.up()); ++ IBlockData iblockdata1 = this.world.getBlockStateIfLoaded(blockposition.up()); // Yatopia + SoundEffectType soundeffecttype = iblockdata1.a(Blocks.SNOW) ? iblockdata1.getStepSound() : iblockdata.getStepSound(); + + this.playSound(soundeffecttype.getStepSound(), soundeffecttype.getVolume() * 0.15F, soundeffecttype.getPitch()); +@@ -1432,7 +1445,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + + public final boolean isInBubbleColumn() { return k(); } // Paper - OBFHELPER + private boolean k() { +- return this.world.getType(this.getChunkCoordinates()).a(Blocks.BUBBLE_COLUMN); ++ return this.world.getBlockStateIfLoaded(this.getChunkCoordinates()).a(Blocks.BUBBLE_COLUMN); // Yatopia + } + + public boolean isInWaterOrRain() { +@@ -1503,7 +1516,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + BlockPosition blockposition = new BlockPosition(this.locX(), d0, this.locZ()); +- Fluid fluid = this.world.getFluid(blockposition); ++ Fluid fluid = this.world.getFluidStateIfLoaded(blockposition); // Yatopia + Iterator iterator = TagsFluid.b().iterator(); + + Tag tag; +@@ -1561,7 +1574,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + protected IBlockData aN() { +- return this.world.getType(this.ap()); ++ return this.world.getBlockStateIfLoaded(this.ap()); // Yatopia + } + + public boolean aO() { +@@ -1573,7 +1586,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + int j = MathHelper.floor(this.locY() - 0.20000000298023224D); + int k = MathHelper.floor(this.locZ()); + BlockPosition blockposition = new BlockPosition(i, j, k); +- IBlockData iblockdata = this.world.getType(blockposition); ++ IBlockData iblockdata = this.world.getBlockStateIfLoaded(blockposition); // Yatopia + + if (iblockdata.h() != EnumRenderType.INVISIBLE) { + Vec3D vec3d = this.getMot(); +@@ -2827,7 +2840,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + EnumDirection enumdirection1 = aenumdirection[j]; + + blockposition_mutableblockposition.a((BaseBlockPosition) blockposition, enumdirection1); +- if (!this.world.getType(blockposition_mutableblockposition).r(this.world, blockposition_mutableblockposition)) { ++ if (!this.world.getBlockStateIfLoaded(blockposition_mutableblockposition).r(this.world, blockposition_mutableblockposition)) { // Yatopia + double d4 = vec3d.a(enumdirection1.n()); + double d5 = enumdirection1.e() == EnumDirection.EnumAxisDirection.POSITIVE ? 1.0D - d4 : d4; + +@@ -3043,14 +3056,14 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + + return (ShapeDetectorShape) this.findOrCreatePortal(worldserver, blockposition, flag2, event.getSearchRadius(), event.getCanCreatePortal(), event.getCreationRadius()).map((blockutil_rectangle) -> { + // CraftBukkit end +- IBlockData iblockdata = this.world.getType(this.ac); ++ IBlockData iblockdata = this.world.getBlockStateIfLoaded(this.ac); // Yatopia + EnumDirection.EnumAxis enumdirection_enumaxis; + Vec3D vec3d; + + if (iblockdata.b(BlockProperties.E)) { + enumdirection_enumaxis = (EnumDirection.EnumAxis) iblockdata.get(BlockProperties.E); + BlockUtil.Rectangle blockutil_rectangle1 = BlockUtil.a(this.ac, enumdirection_enumaxis, 21, EnumDirection.EnumAxis.Y, 21, (blockposition1) -> { +- return this.world.getType(blockposition1) == iblockdata; ++ return this.world.getBlockStateIfLoaded(blockposition1) == iblockdata; // Yatopia + }); + + vec3d = this.a(enumdirection_enumaxis, blockutil_rectangle1); +@@ -3417,6 +3430,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + public boolean cl() { ++ if (!this.updateNeeded) this.chunkPosUpdateRequested = true; // Yatopia + boolean flag = this.au; + + this.au = false; +@@ -3620,7 +3634,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + for (int i2 = k; i2 < l; ++i2) { + for (int j2 = i1; j2 < j1; ++j2) { + blockposition_mutableblockposition.d(l1, i2, j2); +- Fluid fluid = this.world.getFluid(blockposition_mutableblockposition); ++ Fluid fluid = this.world.getFluidStateIfLoaded(blockposition_mutableblockposition); // Yatopia + + if (fluid.a(tag)) { + double d2 = (double) ((float) i2 + fluid.getHeight(this.world, blockposition_mutableblockposition)); +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index acccb79200d15bfd7f53adf925d6c8f50050d346..df577f4fd61845bce5246e8a04fe63f4c2695259 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -140,6 +140,7 @@ import org.bukkit.event.entity.EntityTeleportEvent; + import org.bukkit.event.player.PlayerItemConsumeEvent; + // CraftBukkit end + ++import net.gegy1000.tictacs.NonBlockingWorldAccess; // Yatopia + + public abstract class EntityLiving extends Entity { + +@@ -393,7 +394,7 @@ public abstract class EntityLiving extends Entity { + boolean flag1 = flag && ((EntityHuman) this).abilities.isInvulnerable; + + if (this.isAlive()) { +- if (this.a((Tag) TagsFluid.WATER) && !this.world.getType(new BlockPosition(this.locX(), this.getHeadY(), this.locZ())).a(Blocks.BUBBLE_COLUMN)) { ++ if (this.a((Tag) TagsFluid.WATER) && !this.world.getBlockStateIfLoaded(new BlockPosition(this.locX(), this.getHeadY(), this.locZ())).a(Blocks.BUBBLE_COLUMN)) { // Yatopia + if (!this.canBreatheUnderwater() && !MobEffectUtil.c(this) && !flag1) { // Paper - use OBFHELPER so it can be overridden + this.setAirTicks(this.l(this.getAirTicks())); + if (this.getAirTicks() == -this.world.purpurConfig.drowningDamageInterval) { // Purpur +@@ -486,7 +487,7 @@ public abstract class EntityLiving extends Entity { + } + + protected boolean cP() { +- return this.world.getType(this.as()).a((Tag) TagsBlock.SOUL_SPEED_BLOCKS); ++ return this.world.getBlockStateIfLoaded(this.as()).a((Tag) TagsBlock.SOUL_SPEED_BLOCKS); // Yatopia + } + + @Override +@@ -534,6 +535,8 @@ public abstract class EntityLiving extends Entity { + } + + protected void c(BlockPosition blockposition) { ++ if (!this.world.isLoaded(blockposition)) return; // Yatopia ++ + int i = EnchantmentManager.a(Enchantments.FROST_WALKER, this); + + if (i > 0) { +@@ -1580,7 +1583,7 @@ public abstract class EntityLiving extends Entity { + BlockPosition blockposition = this.getChunkCoordinates(); + IBlockData iblockdata = Blocks.WITHER_ROSE.getBlockData(); + +- if (this.world.getType(blockposition).isAir() && iblockdata.canPlace(this.world, blockposition)) { ++ if (this.world.getBlockStateIfLoaded(blockposition).isAir() && iblockdata.canPlace(this.world, blockposition)) { // Yatopia + this.world.setTypeAndData(blockposition, iblockdata, 3); + flag = true; + } +@@ -1772,12 +1775,12 @@ public abstract class EntityLiving extends Entity { + } + + public IBlockData ds() { +- return this.world.getType(this.getChunkCoordinates()); ++ return this.world.getBlockStateIfLoaded(this.getChunkCoordinates()); // Yatopia + } + + private boolean c(BlockPosition blockposition, IBlockData iblockdata) { + if ((Boolean) iblockdata.get(BlockTrapdoor.OPEN)) { +- IBlockData iblockdata1 = this.world.getType(blockposition.down()); ++ IBlockData iblockdata1 = this.world.getBlockStateIfLoaded(blockposition.down()); // Yatopia + + if (iblockdata1.a(Blocks.LADDER) && iblockdata1.get(BlockLadder.FACING) == iblockdata.get(BlockTrapdoor.FACING)) { + return true; +@@ -1824,7 +1827,7 @@ public abstract class EntityLiving extends Entity { + int i = MathHelper.floor(this.locX()); + int j = MathHelper.floor(this.locY() - 0.20000000298023224D); + int k = MathHelper.floor(this.locZ()); +- IBlockData iblockdata = this.world.getType(new BlockPosition(i, j, k)); ++ IBlockData iblockdata = this.world.getBlockStateIfLoaded(new BlockPosition(i, j, k)); // Yatopia + + if (!iblockdata.isAir()) { + SoundEffectType soundeffecttype = iblockdata.getStepSound(); +@@ -2315,7 +2318,7 @@ public abstract class EntityLiving extends Entity { + private void a(Entity entity) { + Vec3D vec3d; + +- if (!entity.dead && !this.world.getType(entity.getChunkCoordinates()).getBlock().a((Tag) TagsBlock.PORTALS)) { ++ if (!entity.dead && !this.world.getBlockStateIfLoaded(entity.getChunkCoordinates()).getBlock().a((Tag) TagsBlock.PORTALS)) { // Yatopia + vec3d = entity.b(this); + } else { + vec3d = new Vec3D(entity.locX(), entity.locY() + (double) entity.getHeight(), entity.locZ()); +@@ -2369,7 +2372,7 @@ public abstract class EntityLiving extends Entity { + this.fallDistance = 0.0F; + } + +- Fluid fluid = this.world.getFluid(this.getChunkCoordinates()); ++ Fluid fluid = this.world.getFluidStateIfLoaded(this.getChunkCoordinates()); // Yatopia + double d1; + float f; + +@@ -2485,7 +2488,7 @@ public abstract class EntityLiving extends Entity { + } + } else { + BlockPosition blockposition = this.as(); +- float f5 = this.world.getType(blockposition).getBlock().getFrictionFactor(); ++ float f5 = this.world.getBlockStateIfLoaded(blockposition).getBlock().getFrictionFactor(); // Yatopia + + f = this.onGround ? f5 * 0.91F : 0.91F; + Vec3D vec3d6 = this.a(vec3d, f5); +@@ -3546,7 +3549,7 @@ public abstract class EntityLiving extends Entity { + + while (!flag2 && blockposition.getY() > 0) { + BlockPosition blockposition1 = blockposition.down(); +- IBlockData iblockdata = world.getType(blockposition1); ++ IBlockData iblockdata = this.world.getBlockStateIfLoaded(blockposition1); // Yatopia + + if (iblockdata.getMaterial().isSolid()) { + flag2 = true; +@@ -3641,7 +3644,7 @@ public abstract class EntityLiving extends Entity { + this.stopRiding(); + } + +- IBlockData iblockdata = this.world.getType(blockposition); ++ IBlockData iblockdata = this.world.getBlockStateIfLoaded(blockposition); // Yatopia + + if (iblockdata.getBlock() instanceof BlockBed) { + this.world.setTypeAndData(blockposition, (IBlockData) iblockdata.set(BlockBed.OCCUPIED, true), 3); +@@ -3660,7 +3663,7 @@ public abstract class EntityLiving extends Entity { + + private boolean x() { + return (Boolean) this.getBedPosition().map((blockposition) -> { +- return this.world.getType(blockposition).getBlock() instanceof BlockBed; ++ return this.world.getBlockStateIfLoaded(blockposition).getBlock() instanceof BlockBed; // Yatopia + }).orElse(false); + } + +@@ -3670,7 +3673,7 @@ public abstract class EntityLiving extends Entity { + + this.world.getClass(); + optional.filter(world::isLoaded).ifPresent((blockposition) -> { +- IBlockData iblockdata = this.world.getType(blockposition); ++ IBlockData iblockdata = this.world.getBlockStateIfLoaded(blockposition); // Yatopia + + if (iblockdata.getBlock() instanceof BlockBed) { + this.world.setTypeAndData(blockposition, (IBlockData) iblockdata.set(BlockBed.OCCUPIED, false), 3); +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index aed97af643b8ec9b6fd5a5f06ff4f364daf39742..f5badbe0dee5c40cf83a5d2993d27ed70ddd2c85 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -96,7 +96,9 @@ import org.bukkit.craftbukkit.block.data.CraftBlockData; + import org.bukkit.event.block.BlockPhysicsEvent; + // CraftBukkit end + +-public abstract class World implements GeneratorAccess, AutoCloseable { ++import net.gegy1000.tictacs.NonBlockingWorldAccess; // Yatopia ++ ++public abstract class World implements GeneratorAccess, AutoCloseable, NonBlockingWorldAccess { // Yatopia + + protected static final Logger LOGGER = LogManager.getLogger(); + public static final Codec> f = MinecraftKey.a.xmap(ResourceKey.b(IRegistry.L), ResourceKey::a); +diff --git a/src/main/java/net/minecraft/world/level/material/FluidType.java b/src/main/java/net/minecraft/world/level/material/FluidType.java +index 6a60f53407db840150b84f4d2a709cc2e92362a4..bf3300607f9142486dc790a600d4c2ffa998d376 100644 +--- a/src/main/java/net/minecraft/world/level/material/FluidType.java ++++ b/src/main/java/net/minecraft/world/level/material/FluidType.java +@@ -38,6 +38,7 @@ public abstract class FluidType { + this.a = fluid; + } + ++ public final Fluid getFluidData() { return this.h(); } // Yatopia - OBFHELPER + public final Fluid h() { + return this.a; + } diff --git a/patches/server/0065-lithium-optimize-BlockPos.iterateOutwards-by-caching.patch b/patches/server/0065-lithium-optimize-BlockPos.iterateOutwards-by-caching.patch new file mode 100644 index 00000000..556b3556 --- /dev/null +++ b/patches/server/0065-lithium-optimize-BlockPos.iterateOutwards-by-caching.patch @@ -0,0 +1,196 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <50278648+2No2Name@users.noreply.github.com> +Date: Mon, 15 Feb 2021 14:58:55 -0500 +Subject: [PATCH] lithium: optimize `BlockPos.iterateOutwards` by caching + offsets + +Code taken from: https://github.com/CaffeineMC/lithium-fabric/pull/123 by 2No2Name licenced under the LGPLv3 License + +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/IterateOutwardsCache.java b/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/IterateOutwardsCache.java +new file mode 100644 +index 0000000000000000000000000000000000000000..50e9df6056f713b2f1eaf0cb4a23875e0c6c2e2c +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/IterateOutwardsCache.java +@@ -0,0 +1,72 @@ ++package me.jellysquid.mods.lithium.common.cached_blockpos_iteration; ++ ++import it.unimi.dsi.fastutil.longs.LongArrayList; ++import it.unimi.dsi.fastutil.longs.LongList; ++import net.minecraft.core.BlockPosition; ++ ++import java.util.Iterator; ++import java.util.Random; ++import java.util.concurrent.ConcurrentHashMap; ++ ++/** ++ * @author 2No2Name, original implemenation by SuperCoder7979 and Gegy1000 ++ */ ++public class IterateOutwardsCache { ++ //POS_ZERO must not be replaced with BlockPos.ORIGIN, otherwise iterateOutwards at BlockPos.ORIGIN will not use the cache ++ public static final BlockPosition POS_ZERO = new BlockPosition(0,0,0); ++ ++ ++ private final ConcurrentHashMap table; ++ private final int capacity; ++ private final Random random; ++ ++ public IterateOutwardsCache(int capacity) { ++ this.capacity = capacity; ++ this.table = new ConcurrentHashMap<>(31); ++ this.random = new Random(); ++ } ++ ++ private void fillPositionsWithIterateOutwards(LongList entry, int xRange, int yRange, int zRange) { ++ // Add all positions to the cached list ++ for (BlockPosition pos : BlockPosition.iterateOutwards(POS_ZERO, xRange, yRange, zRange)) { ++ entry.add(pos.asLong()); ++ } ++ } ++ ++ public LongList getOrCompute(int xRange, int yRange, int zRange) { ++ long key = BlockPosition.asLong(xRange, yRange, zRange); ++ ++ LongArrayList entry = this.table.get(key); ++ if (entry != null) { ++ return entry; ++ } ++ ++ // Cache miss: compute and store ++ entry = new LongArrayList(128); ++ ++ this.fillPositionsWithIterateOutwards(entry, xRange, yRange, zRange); ++ ++ //decrease the array size, as of now it won't be modified anymore anyways ++ entry.trim(); ++ ++ //this might overwrite an entry as the same entry could have been computed and added during this thread's computation ++ //we do not use computeIfAbsent, as it can delay other threads for too long ++ Object previousEntry = this.table.put(key, entry); ++ ++ ++ if (previousEntry == null && this.table.size() > this.capacity) { ++ //prevent a memory leak by randomly removing about 1/8th of the elements when the exceed the desired capacity is exceeded ++ final Iterator iterator = this.table.keySet().iterator(); ++ //prevent an unlikely infinite loop caused by another thread filling the table concurrently using counting ++ for (int i = -this.capacity; iterator.hasNext() && i < 5; i++) { ++ Long key2 = iterator.next(); ++ //random is not threadsafe, but it doesn't matter here, because we don't need quality random numbers ++ if (this.random.nextInt(8) == 0 && key2 != key) { ++ iterator.remove(); ++ } ++ } ++ } ++ ++ return entry; ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/LongList2BlockPosMutableIterable.java b/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/LongList2BlockPosMutableIterable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e2e4f7968a399b4641df07b2931fff6dbc85ddb3 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/LongList2BlockPosMutableIterable.java +@@ -0,0 +1,47 @@ ++package me.jellysquid.mods.lithium.common.cached_blockpos_iteration; ++ ++import it.unimi.dsi.fastutil.longs.LongIterator; ++import it.unimi.dsi.fastutil.longs.LongList; ++import net.minecraft.core.BlockPosition; ++ ++import java.util.Iterator; ++ ++/** ++ * @author 2No2Name ++ */ ++public class LongList2BlockPosMutableIterable implements Iterable { ++ ++ private final LongList positions; ++ private final int xOffset, yOffset, zOffset; ++ ++ public LongList2BlockPosMutableIterable(BlockPosition offset, LongList posList) { ++ this.xOffset = offset.getX(); ++ this.yOffset = offset.getY(); ++ this.zOffset = offset.getZ(); ++ this.positions = posList; ++ } ++ ++ @Override ++ public Iterator iterator() { ++ return new Iterator() { ++ ++ private final LongIterator it = LongList2BlockPosMutableIterable.this.positions.iterator(); ++ private final BlockPosition.MutableBlockPosition pos = new BlockPosition.MutableBlockPosition(); ++ ++ @Override ++ public boolean hasNext() { ++ return it.hasNext(); ++ } ++ ++ @Override ++ public BlockPosition next() { ++ long nextPos = this.it.nextLong(); ++ return this.pos.setValues( ++ LongList2BlockPosMutableIterable.this.xOffset + BlockPosition.unpackLongX(nextPos), ++ LongList2BlockPosMutableIterable.this.yOffset + BlockPosition.unpackLongY(nextPos), ++ LongList2BlockPosMutableIterable.this.zOffset + BlockPosition.unpackLongZ(nextPos)); ++ } ++ }; ++ } ++ ++} +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/core/BlockPosition.java b/src/main/java/net/minecraft/core/BlockPosition.java +index 9c508cc41575175d442cd51d75706830351a24f2..df6df2ae3e30ada07c90a26b9615d939508d3246 100644 +--- a/src/main/java/net/minecraft/core/BlockPosition.java ++++ b/src/main/java/net/minecraft/core/BlockPosition.java +@@ -19,10 +19,16 @@ import net.minecraft.world.phys.Vec3D; + import org.apache.commons.lang3.Validate; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import me.jellysquid.mods.lithium.common.cached_blockpos_iteration.IterateOutwardsCache; // Yatopia ++import me.jellysquid.mods.lithium.common.cached_blockpos_iteration.LongList2BlockPosMutableIterable; // Yatopia ++import it.unimi.dsi.fastutil.longs.LongList; // Yatopia + + @Immutable + public class BlockPosition extends BaseBlockPosition { + ++ private static final IterateOutwardsCache ITERATE_OUTWARDS_CACHE = new IterateOutwardsCache(50); // Yatopia ++ private static final LongList HOGLIN_PIGLIN_CACHE = ITERATE_OUTWARDS_CACHE.getOrCompute(8, 4, 8); // Yatopia ++ + public static final Codec a = Codec.INT_STREAM.comapFlatMap((intstream) -> { + return SystemUtils.a(intstream, 3).map((aint) -> { + return new BlockPosition(aint[0], aint[1], aint[2]); +@@ -78,14 +84,17 @@ public class BlockPosition extends BaseBlockPosition { + return a((int) (i >> 38) + j, (int) ((i << 52) >> 52) + k, (int) ((i << 26) >> 38) + l); // Paper - simplify/inline + } + ++ public static int unpackLongX(long i) { return BlockPosition.b(i); } // Yatopia - OBFHELPER + public static int b(long i) { + return (int) (i >> 38); // Paper - simplify/inline + } + ++ public static int unpackLongY(long i) { return BlockPosition.c(i); } // Yatopia - OBFHELPER + public static int c(long i) { + return (int) ((i << 52) >> 52); // Paper - simplify/inline + } + ++ public static int unpackLongZ(long i) { return BlockPosition.d(i); } // Yatopia - OBFHELPER + public static int d(long i) { + return (int) ((i << 26) >> 38); // Paper - simplify/inline + } +@@ -266,7 +275,15 @@ public class BlockPosition extends BaseBlockPosition { + }; + } + ++ public static Iterable iterateOutwards(BlockPosition blockposition, int p_i, int p_j, int p_k) { return BlockPosition.a(blockposition, p_i, p_j, p_k); } // Yatopia - OBFHELPER + public static Iterable a(BlockPosition blockposition, int p_i, int p_j, int p_k) { // Paper - decompile issues - variable name conflicts to inner class field refs ++ // Yatopia start - lithium: optimize `BlockPos.iterateOutwards` by caching offsets ++ if (blockposition != me.jellysquid.mods.lithium.common.cached_blockpos_iteration.IterateOutwardsCache.POS_ZERO) { ++ final LongList positions = p_i == 8 && p_j == 4 && p_k == 8 ? HOGLIN_PIGLIN_CACHE : ITERATE_OUTWARDS_CACHE.getOrCompute(p_i, p_j, p_j); ++ return new LongList2BlockPosMutableIterable(blockposition, positions); ++ } ++ // Yatopia end ++ + int l_decompiled = p_i + p_j + p_k; // Paper - decompile issues + int i1 = blockposition.getX(); + int j1 = blockposition.getY(); diff --git a/patches/server/0066-lithium-AI.patch b/patches/server/0066-lithium-AI.patch new file mode 100644 index 00000000..65131889 --- /dev/null +++ b/patches/server/0066-lithium-AI.patch @@ -0,0 +1,2153 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JellySquid +Date: Sun, 24 Jan 2021 12:17:19 +0100 +Subject: [PATCH] lithium AI + + +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 3f7072e993da896b36b85ec08849b46c854681a5..07d4cb1d42ef663a96ccc26af81db144b7958feb 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 + +@@ -2029,6 +2030,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 df577f4fd61845bce5246e8a04fe63f4c2695259..9e679d977f6bcd54694ae1abc338efecb298c13c 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 eecb17e887bf0d1680a5fb5198a8b4246c14e548..3319c419a9eaf91224396c8b19eb057abd8094e6 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 e6c37db1a8b51dda1e9d493d8fbfecd4ddf1aeb3..4cb0213b0f786aacbb7ef2f682587ec127554e35 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 +@@ -2,6 +2,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; +@@ -62,6 +64,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 { + +@@ -348,6 +351,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; +@@ -419,6 +424,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() +@@ -428,6 +443,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 c07fb5ca761c0f2067bd103026ded618a8620947..cbe21ec18561c991aa27dd48d536c78e358d5b0e 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 { + +@@ -852,6 +853,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) { +@@ -861,6 +867,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), diff --git a/patches/server/0067-lithium-block.patch b/patches/server/0067-lithium-block.patch new file mode 100644 index 00000000..6f0b57b3 --- /dev/null +++ b/patches/server/0067-lithium-block.patch @@ -0,0 +1,231 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JellySquid +Date: Sun, 24 Jan 2021 16:57:03 +0100 +Subject: [PATCH] lithium block + + +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/tuples/FinalObject.java b/src/main/java/me/jellysquid/mods/lithium/common/util/tuples/FinalObject.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f78927e71258e859154cca8ba78d17130b0d82c1 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/util/tuples/FinalObject.java +@@ -0,0 +1,20 @@ ++package me.jellysquid.mods.lithium.common.util.tuples; ++ ++/** ++ * The purpose of this class is safe publication of the wrapped value. (JLS 17.5) ++ */ ++public class FinalObject { ++ private final T value; ++ ++ public FinalObject(T value) { ++ this.value = value; ++ } ++ ++ public FinalObject of(T value) { ++ return new FinalObject<>(value); ++ } ++ ++ public T getValue() { ++ return value; ++ } ++} +diff --git a/src/main/java/net/minecraft/core/EnumDirection.java b/src/main/java/net/minecraft/core/EnumDirection.java +index b8fe75b8c37ef1968519e69e078444b1a3c5c359..a825b17e454138e181926df3f2faa6d9d7af4055 100644 +--- a/src/main/java/net/minecraft/core/EnumDirection.java ++++ b/src/main/java/net/minecraft/core/EnumDirection.java +@@ -100,6 +100,7 @@ public enum EnumDirection implements INamable { + return new EnumDirection[]{enumdirection, enumdirection1, enumdirection2, enumdirection2.opposite(), enumdirection1.opposite(), enumdirection.opposite()}; + } + ++ public int get3DDataValue(){ return c(); } // Yatopia - OBFHELPER + public int c() { + return this.g; + } +diff --git a/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java b/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java +index 58c7a52612fe0f5c1e4ddacc0bf93cd81f1286b8..af2a8fdcaf5962334dcc20717f3a1ad14aebcbcb 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java +@@ -29,9 +29,9 @@ import net.minecraft.world.phys.shapes.VoxelShapes; + public class TileEntityPiston extends TileEntity implements ITickable { + + private IBlockData a; +- private EnumDirection b; +- private boolean c; +- private boolean g; ++ private EnumDirection b; public EnumDirection getFacing() { return b; } // Yatopia - OBFHELPER ++ private boolean c; public boolean isExtending() { return c; } // Yatopia - OBFHELPER ++ private boolean g; public boolean isSource() { return g; } // Yatopia - OBFHELPER + private static final ThreadLocal h = ThreadLocal.withInitial(() -> { + return null; + }); +@@ -40,6 +40,74 @@ public class TileEntityPiston extends TileEntity implements ITickable { + private long k; + private int l; + ++ // Yatopia start - Port Lithium ++ private static final VoxelShape[] PISTON_BASE_WITH_MOVING_HEAD_SHAPES = precomputePistonBaseWithMovingHeadShapes(); ++ /** ++ * We cache the offset and simplified VoxelShapes that are otherwise constructed on every call of getCollisionShape. ++ * For each offset direction and distance (6 directions, 2 distances each, and no direction with 0 distance) we ++ * store the offset and simplified VoxelShapes in the original VoxelShape when they are accessed the first time. ++ * We use safe publication, because both the Render and Server thread are using the cache. ++ * ++ * @param blockShape the original shape, must not be modified after passing it as an argument to this method ++ * @param offset the offset distance ++ * @param direction the offset direction ++ * @return blockShape offset and simplified ++ */ ++ private static VoxelShape getOffsetAndSimplified(VoxelShape blockShape, float offset, EnumDirection direction) { ++ VoxelShape offsetSimplifiedShape = blockShape.getOffsetSimplifiedShape(offset, direction); ++ if (offsetSimplifiedShape == null) { ++ //create the offset shape and store it for later use ++ offsetSimplifiedShape = blockShape.offset(direction.getAdjacentX() * offset, direction.getAdjacentY() * offset, direction.getAdjacentZ() * offset).simplify(); ++ blockShape.setShape(offset, direction, offsetSimplifiedShape); ++ } ++ return offsetSimplifiedShape; ++ } ++ ++ /** ++ * Precompute all 18 possible configurations for the merged piston base and head shape. ++ * ++ * @return The array of the merged VoxelShapes, indexed by {@link TileEntityPiston#getIndexForMergedShape(float, EnumDirection)} ++ */ ++ private static VoxelShape[] precomputePistonBaseWithMovingHeadShapes() { ++ float[] offsets = {0f, 0.5f, 1f}; ++ EnumDirection[] directions = EnumDirection.values(); ++ ++ VoxelShape[] mergedShapes = new VoxelShape[offsets.length * directions.length]; ++ ++ for (EnumDirection facing : directions) { ++ VoxelShape baseShape = Blocks.PISTON.getBlockData().set(BlockPiston.EXTENDED, true) ++ .set(BlockPiston.FACING, facing).getCollisionShape(null, null); ++ for (float offset : offsets) { ++ //this cache is only required for the merged piston head + base shape. ++ //this shape is only used when !this.extending ++ //here: isShort = this.extending != 1.0F - this.progress < 0.25F can be simplified to: ++ //isShort = f < 0.25F , because f = getAmountExtended(this.progress) can be simplified to f == 1.0F - this.progress ++ //therefore isShort is dependent on the offset: ++ boolean isShort = offset < 0.25f; ++ ++ VoxelShape headShape = (Blocks.PISTON_HEAD.getBlockData().set(BlockPistonExtension.FACING, facing)) ++ .set(BlockPistonExtension.SHORT, isShort).getCollisionShape(null, null); ++ ++ VoxelShape offsetHead = headShape.offset(facing.getAdjacentX() * offset, ++ facing.getAdjacentY() * offset, ++ facing.getAdjacentZ() * offset); ++ mergedShapes[getIndexForMergedShape(offset, facing)] = VoxelShapes.union(baseShape, offsetHead); ++ } ++ ++ } ++ ++ return mergedShapes; ++ } ++ ++ private static int getIndexForMergedShape(float offset, EnumDirection direction) { ++ if (offset != 0f && offset != 0.5f && offset != 1f) { ++ return -1; ++ } ++ //shape of offset 0 is still dependent on the direction, due to piston head and base being directional blocks ++ return (int) (2 * offset) + (3 * direction.get3DDataValue()); ++ } ++ // Yatopia End ++ + public TileEntityPiston() { + super(TileEntityTypes.PISTON); + } +@@ -350,13 +418,21 @@ public class TileEntityPiston extends TileEntity implements ITickable { + } else { + iblockdata = this.a; + } ++ float f = this.e(this.i); // Yatopia Start - Port Lithium ++ if (this.isExtending() || !this.isSource()) { ++ //here voxelShape2.isEmpty() is guaranteed, vanilla code would call union() which calls simplify() ++ VoxelShape blockShape = iblockdata.getCollisionShape(iblockaccess, blockposition); ++ ++ //we cache the simplified shapes, as the simplify() method costs a lot of CPU time and allocates several objects ++ VoxelShape offsetAndSimplified = getOffsetAndSimplified(blockShape, Math.abs(f), f < 0f ? this.getFacing().opposite() : this.getFacing()); ++ return offsetAndSimplified; ++ } else { ++ //retracting piston heads have to act like their base as well, as the base block is replaced with the moving block ++ //f >= 0f is guaranteed (assuming no other mod interferes) ++ int index = getIndexForMergedShape(f, this.getFacing()); ++ return PISTON_BASE_WITH_MOVING_HEAD_SHAPES[index]; ++ } // Yatopia End + +- float f = this.e(this.i); +- double d0 = (double) ((float) this.b.getAdjacentX() * f); +- double d1 = (double) ((float) this.b.getAdjacentY() * f); +- double d2 = (double) ((float) this.b.getAdjacentZ() * f); +- +- return VoxelShapes.a(voxelshape, iblockdata.getCollisionShape(iblockaccess, blockposition).a(d0, d1, d2)); + } + } + +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java +index 9567630593da7c77a0173b93c9a57ceb7887a59b..fbc5fab9946b6f9233f3014f0e69f2576b2138d7 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java +@@ -13,9 +13,50 @@ import net.minecraft.util.MathHelper; + import net.minecraft.world.phys.AxisAlignedBB; + import net.minecraft.world.phys.MovingObjectPositionBlock; + import net.minecraft.world.phys.Vec3D; ++import me.jellysquid.mods.lithium.common.util.tuples.FinalObject; // Yatopia + + public abstract class VoxelShape { + ++ // Yatopia - Port Lithium ++ private FinalObject[] offsetAndSimplified; ++ ++ public void setShape(float offset, EnumDirection direction, VoxelShape offsetShape) { ++ if (offsetShape == null) { ++ throw new IllegalArgumentException("offsetShape must not be null!"); ++ } ++ int index = getIndexForOffsetSimplifiedShapes(offset, direction); ++ FinalObject[] offsetAndSimplified = this.offsetAndSimplified; ++ if (offsetAndSimplified == null) { ++ //noinspection unchecked ++ this.offsetAndSimplified = (offsetAndSimplified = new FinalObject[1 + (2 * 6)]); ++ } ++ //using FinalObject as it stores the value in a final field, which guarantees safe publication ++ offsetAndSimplified[index] = new FinalObject<>(offsetShape); ++ } ++ ++ public VoxelShape getOffsetSimplifiedShape(float offset, EnumDirection direction) { ++ FinalObject[] offsetAndSimplified = this.offsetAndSimplified; ++ if (offsetAndSimplified == null) { ++ return null; ++ } ++ int index = getIndexForOffsetSimplifiedShapes(offset, direction); ++ //usage of FinalObject guarantees that we are seeing a fully initialized VoxelShape here, even when it was created on a different thread ++ FinalObject wrappedShape = offsetAndSimplified[index]; ++ //noinspection FinalObjectAssignedToNull,FinalObjectGetWithoutIsPresent ++ return wrappedShape == null ? null : wrappedShape.getValue(); ++ } ++ ++ private static int getIndexForOffsetSimplifiedShapes(float offset, EnumDirection direction) { ++ if (offset != 0f && offset != 0.5f && offset != 1f) { ++ throw new IllegalArgumentException("offset must be one of {0f, 0.5f, 1f}"); ++ } ++ if (offset == 0f) { ++ return 0; //can treat offsetting by 0 in all directions the same ++ } ++ return (int) (2 * offset) + 2 * direction.get3DDataValue(); ++ } ++ // Yatopia End ++ + protected final VoxelShapeDiscrete a; public final VoxelShapeDiscrete getShape() { return this.a; } // Tuinity - OBFHELPER + @Nullable + private VoxelShape[] b; +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java +index 98e787e6383a39de1708428137fd7f9e057ff153..049aa5f51e7517744b25f8c2c4b5e7a7de24a73e 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java +@@ -187,6 +187,7 @@ public final class VoxelShapes { + return (long) i * (long) (j / IntMath.gcd(i, j)); + } + ++ public static VoxelShape union(VoxelShape voxelshape, VoxelShape voxelshape1) { return a(voxelshape, voxelshape1); } // Yatopia - OBFHELPER + public static VoxelShape a(VoxelShape voxelshape, VoxelShape voxelshape1) { + return a(voxelshape, voxelshape1, OperatorBoolean.OR); + } diff --git a/patches/server/0068-lithium-entity.patch b/patches/server/0068-lithium-entity.patch new file mode 100644 index 00000000..dbacf976 --- /dev/null +++ b/patches/server/0068-lithium-entity.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JellySquid +Date: Sun, 24 Jan 2021 22:55:55 +0100 +Subject: [PATCH] lithium entity + + +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index 9e679d977f6bcd54694ae1abc338efecb298c13c..75c8226866d9899a83a120fc044fc97fd0556bf9 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -286,6 +286,10 @@ public abstract class EntityLiving extends Entity { + public NearbyEntityListenerMulti getListener() { + return this.tracker; + } ++ ++ private IBlockData lastStateAtFeet = null; ++ ++ private long lastPos = Long.MIN_VALUE; + // Yatopia end + protected void initAttributes() {} // Purpur + +@@ -363,6 +367,10 @@ public abstract class EntityLiving extends Entity { + + @Override + public void entityBaseTick() { ++ // Yatopia start - port lithium ++ this.lastStateAtFeet = null; ++ this.lastPos = Long.MIN_VALUE; ++ // Yatopia end + this.ar = this.as; + if (this.justCreated) { + this.getBedPosition().ifPresent(this::a); +@@ -1784,6 +1792,26 @@ public abstract class EntityLiving extends Entity { + + public IBlockData ds() { + return this.world.getBlockStateIfLoaded(this.getChunkCoordinates()); // Yatopia ++ /* ++ // Yatopia start - port lithium ++ int x = MathHelper.floor(this.locX()); ++ int y = MathHelper.floor(this.locY()); ++ int z = MathHelper.floor(this.locZ()); ++ ++ long pos = getChunkCoordinates().asLong(); ++ ++ if (this.lastPos == pos) { ++ return this.lastStateAtFeet; ++ } ++ ++ IBlockData state = this.world.getType(getChunkCoordinates()); ++ ++ this.lastPos = pos; ++ this.lastStateAtFeet = state; ++ ++ return state; ++ // Yatopia end ++ */ + } + + private boolean c(BlockPosition blockposition, IBlockData iblockdata) { diff --git a/patches/server/0069-lithium-gen.patch b/patches/server/0069-lithium-gen.patch new file mode 100644 index 00000000..398ab583 --- /dev/null +++ b/patches/server/0069-lithium-gen.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hugo Planque +Date: Sun, 24 Jan 2021 23:42:56 +0100 +Subject: [PATCH] lithium gen + + +diff --git a/src/main/java/net/minecraft/world/level/newbiome/area/AreaLazy.java b/src/main/java/net/minecraft/world/level/newbiome/area/AreaLazy.java +index 69a021b84cd3f6e2a397a03cfbb562a94f72aace..9a897ad2b53e281cf165106d03737d2ec517eb29 100644 +--- a/src/main/java/net/minecraft/world/level/newbiome/area/AreaLazy.java ++++ b/src/main/java/net/minecraft/world/level/newbiome/area/AreaLazy.java +@@ -16,6 +16,7 @@ public final class AreaLazy implements Area { + this.a = areatransformer8; + } + ++ public int sample(int i, int j){ return a(i, j); } // Yatopia - OBFHELPER + @Override + public int a(int i, int j) { + long k = ChunkCoordIntPair.pair(i, j); +diff --git a/src/main/java/net/minecraft/world/level/newbiome/layer/GenLayer.java b/src/main/java/net/minecraft/world/level/newbiome/layer/GenLayer.java +index 8b61bce3d7587832ddb2c0e29e76f9c68ddf5d8b..acf74fccde2fc1fc3bd065f7d1bd2f5ba7f475e4 100644 +--- a/src/main/java/net/minecraft/world/level/newbiome/layer/GenLayer.java ++++ b/src/main/java/net/minecraft/world/level/newbiome/layer/GenLayer.java +@@ -15,13 +15,14 @@ public class GenLayer { + + private static final Logger LOGGER = LogManager.getLogger(); + private final AreaLazy b; +- ++ private final ThreadLocal tlSampler; // Yatopia - Port lithium + public GenLayer(AreaFactory areafactory) { + this.b = (AreaLazy) areafactory.make(); ++ this.tlSampler = ThreadLocal.withInitial(areafactory::make); // Yatopia - Port lithium + } + + public BiomeBase a(IRegistry iregistry, int i, int j) { +- int k = this.b.a(i, j); ++ int k = this.tlSampler.get().sample(i, j); // Yatopia - Port lithium + ResourceKey resourcekey = BiomeRegistry.a(k); + + if (resourcekey == null) { diff --git a/patches/server/0070-lithium-shape.patch b/patches/server/0070-lithium-shape.patch new file mode 100644 index 00000000..9552d129 --- /dev/null +++ b/patches/server/0070-lithium-shape.patch @@ -0,0 +1,357 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JellySquid +Date: Wed, 27 Jan 2021 21:05:05 +0100 +Subject: [PATCH] lithium shape + + +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/block/LithiumEntityShapeContext.java b/src/main/java/me/jellysquid/mods/lithium/common/block/LithiumEntityShapeContext.java +new file mode 100644 +index 0000000000000000000000000000000000000000..85d55d144a38a16d0f148a29cd11985d7febe880 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/block/LithiumEntityShapeContext.java +@@ -0,0 +1,55 @@ ++package me.jellysquid.mods.lithium.common.block; ++ ++import net.minecraft.core.BlockPosition; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.world.level.material.Fluid; ++import net.minecraft.world.level.material.FluidTypeFlowing; ++import net.minecraft.world.item.Item; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import net.minecraft.world.phys.shapes.VoxelShapeCollision; ++ ++/** ++ * A replacement for EntityShapeContext that does not calculate the heldItem on construction. As most instances never ++ * use this field, a lazy evaluation is faster on average. The initialization of the heldItem field takes about 1% of ++ * the server thread CPU time in a fresh world with lots of animals (singleplayer 1.16.5, renderdistance 24). ++ * ++ * @author 2No2Name ++ */ ++public class LithiumEntityShapeContext implements VoxelShapeCollision { ++ private final Entity entity; ++ private final boolean descending; ++ private final double minY; ++ private Item heldItem; ++ ++ public LithiumEntityShapeContext(Entity entity) { ++ this.entity = entity; ++ this.descending = entity.isDescending(); ++ this.minY = entity.locY(); ++ } ++ ++ @Override ++ public boolean a(Item item) { ++ if (this.heldItem == null) { ++ this.heldItem = entity instanceof EntityLiving ? ((EntityLiving)entity).getItemInMainHand().getItem() : Items.AIR; ++ } ++ return this.heldItem == item; ++ } ++ ++ @Override ++ public boolean a(Fluid aboveState, FluidTypeFlowing fluid) { ++ return this.entity instanceof EntityLiving && ((EntityLiving) this.entity).a(fluid) && !aboveState.getType().a(fluid); ++ } ++ ++ @Override ++ public boolean b() { ++ return this.descending; ++ } ++ ++ @Override ++ public boolean a(VoxelShape shape, BlockPosition pos, boolean defaultValue) { ++ return this.minY > (double)pos.getY() + shape.getMax(EnumDirection.EnumAxis.Y) - 9.999999747378752E-6D; ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/shapes/pairs/LithiumDoublePairList.java b/src/main/java/me/jellysquid/mods/lithium/common/shapes/pairs/LithiumDoublePairList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bfa06e714050260779fc8727b4b2cabb6f811398 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/shapes/pairs/LithiumDoublePairList.java +@@ -0,0 +1,117 @@ ++package me.jellysquid.mods.lithium.common.shapes.pairs; ++ ++import it.unimi.dsi.fastutil.doubles.DoubleArrayList; ++import it.unimi.dsi.fastutil.doubles.DoubleList; ++import net.minecraft.world.phys.shapes.VoxelShapeMerger; ++ ++/** ++ * Optimized variant of {@link net.minecraft.util.shape.SimplePairList}. This implementation works directly against ++ * flat arrays and tries to organize code in a manner that hits the JIT's happy path. In my testing, this is about ++ * ~50% faster than the vanilla implementation. ++ */ ++public final class LithiumDoublePairList implements VoxelShapeMerger { ++ private final double[] merged; ++ private final int[] indicesFirst; ++ private final int[] indicesSecond; ++ ++ private final DoubleArrayList pairs; ++ ++ public LithiumDoublePairList(DoubleList aPoints, DoubleList bPoints, boolean flag1, boolean flag2) { ++ int size = aPoints.size() + bPoints.size(); ++ ++ this.merged = new double[size]; ++ this.indicesFirst = new int[size]; ++ this.indicesSecond = new int[size]; ++ ++ this.pairs = DoubleArrayList.wrap(this.merged); ++ ++ this.merge(getArray(aPoints), getArray(bPoints), aPoints.size(), bPoints.size(), flag1, flag2); ++ } ++ ++ private void merge(double[] aPoints, double[] bPoints, int aSize, int bSize, boolean flag1, boolean flag2) { ++ int aIdx = 0; ++ int bIdx = 0; ++ ++ double prev = 0.0D; ++ ++ int a1 = 0, a2 = 0; ++ ++ while (true) { ++ boolean aWithinBounds = aIdx < aSize; ++ boolean bWithinBounds = bIdx < bSize; ++ ++ if (!aWithinBounds && !bWithinBounds) { ++ break; ++ } ++ ++ boolean flip = aWithinBounds && (!bWithinBounds || aPoints[aIdx] < bPoints[bIdx] + 1.0E-7D); ++ ++ double value; ++ ++ if (flip) { ++ value = aPoints[aIdx++]; ++ } else { ++ value = bPoints[bIdx++]; ++ } ++ ++ if ((aIdx == 0 || !aWithinBounds) && !flip && !flag2) { ++ continue; ++ } ++ ++ if ((bIdx == 0 || !bWithinBounds) && flip && !flag1) { ++ continue; ++ } ++ ++ if (a2 == 0 || prev < value - 1.0E-7D) { ++ this.indicesFirst[a1] = aIdx - 1; ++ this.indicesSecond[a1] = bIdx - 1; ++ this.merged[a2] = value; ++ ++ a1++; ++ a2++; ++ prev = value; ++ } else if (a2 > 0) { ++ this.indicesFirst[a1 - 1] = aIdx - 1; ++ this.indicesSecond[a1 - 1] = bIdx - 1; ++ } ++ } ++ ++ if (a2 == 0) { ++ this.merged[a2++] = Math.min(aPoints[aSize - 1], bPoints[bSize - 1]); ++ } ++ ++ this.pairs.size(a2); ++ } ++ ++ @Override ++ public boolean a(VoxelShapeMerger.a predicate) { ++ int l = this.pairs.size() - 1; ++ ++ for (int i = 0; i < l; i++) { ++ if (!predicate.merge(this.indicesFirst[i], this.indicesSecond[i], i)) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ ++ @Override ++ public DoubleList a() { ++ return this.pairs; ++ } ++ ++ private static double[] getArray(DoubleList list) { ++ if (list instanceof DoubleArrayList) { ++ return ((DoubleArrayList) list).elements(); ++ } ++ ++ double[] points = new double[list.size()]; ++ ++ for (int i = 0; i < points.length; i++) { ++ points[i] = list.getDouble(i); ++ } ++ ++ return points; ++ } ++} +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/collections/Object2BooleanCacheTable.java b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/Object2BooleanCacheTable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e210e0fa39b74805429832c3d232fadb33975414 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/Object2BooleanCacheTable.java +@@ -0,0 +1,61 @@ ++package me.jellysquid.mods.lithium.common.util.collections; ++ ++import it.unimi.dsi.fastutil.HashCommon; ++import net.minecraft.util.MathHelper; ++ ++import java.util.function.Predicate; ++ ++/** ++ * A lossy hashtable implementation that stores a mapping between an object and a boolean. ++ *

++ * Any hash collisions will result in an overwrite: this is safe because the correct value can always be recomputed, ++ * given that the given operator is deterministic. ++ *

++ * This implementation is safe to use from multiple threads ++ */ ++public final class Object2BooleanCacheTable { ++ private final int capacity; ++ private final int mask; ++ ++ private final Node[] nodes; ++ ++ private final Predicate operator; ++ ++ @SuppressWarnings("unchecked") ++ public Object2BooleanCacheTable(int capacity, Predicate operator) { ++ this.capacity = MathHelper.smallestEncompassingPowerOfTwo(capacity); ++ this.mask = this.capacity - 1; ++ ++ this.nodes = (Node[]) new Node[this.capacity]; ++ ++ this.operator = operator; ++ } ++ ++ private static int hash(T key) { ++ return HashCommon.mix(key.hashCode()); ++ } ++ ++ public boolean get(T key) { ++ int idx = hash(key) & this.mask; ++ ++ Node node = this.nodes[idx]; ++ if (node != null && key.equals(node.key)) { ++ return node.value; ++ } ++ ++ boolean test = this.operator.test(key); ++ this.nodes[idx] = new Node<>(key, test); ++ ++ return test; ++ } ++ ++ static class Node { ++ final T key; ++ final boolean value; ++ ++ Node(T key, boolean value) { ++ this.key = key; ++ this.value = value; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/util/MathHelper.java b/src/main/java/net/minecraft/util/MathHelper.java +index b95115aca72ba0cf6451096ddbd8b50a8f3bb5c6..0afb8c643cb3e5938e12183c6132797d6ed645bb 100644 +--- a/src/main/java/net/minecraft/util/MathHelper.java ++++ b/src/main/java/net/minecraft/util/MathHelper.java +@@ -198,6 +198,7 @@ public class MathHelper { + return c(f, f + f3, f2); + } + ++ public static int smallestEncompassingPowerOfTwo(int i){ return c(i); } // Yatopia - OBFHELPER + public static int c(int i) { + int j = i - 1; + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 7befe4263a2d046922438e1a9853f2d8290ee230..ab5fb00885d22126639e7459ef71c56be4b5eb9e 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2647,6 +2647,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + return this.isSneaking(); + } + ++ public boolean isDescending() { return by(); } // Yatopia - OBFHELPER + public boolean by() { + 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 3319c419a9eaf91224396c8b19eb057abd8094e6..9dee9ff25ba12f4de6d9080176c371627215b029 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -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 { + +@@ -229,8 +230,14 @@ public class Block extends BlockBase implements IMaterial { + return a(voxelshape1); + } + ++ // Yatopia start - Port lithium ++ private static final Object2BooleanCacheTable FULL_CUBE_CACHE = new Object2BooleanCacheTable<>( ++ 512, ++ shape -> !VoxelShapes.applyOperation(VoxelShapes.fullCube(), shape, OperatorBoolean.NOT_SAME) ++ ); ++ + public static boolean a(VoxelShape voxelshape) { +- return (Boolean) Block.a.getUnchecked(voxelshape); ++ return FULL_CUBE_CACHE.get(voxelshape); // Yatopia end + } + + public boolean b(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition) { +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java +index fbc5fab9946b6f9233f3014f0e69f2576b2138d7..012892063405616c8ae35b6e06020008dfd147df 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java +@@ -71,6 +71,7 @@ public abstract class VoxelShape { + return i >= this.a.c(enumdirection_enumaxis) ? Double.POSITIVE_INFINITY : this.a(enumdirection_enumaxis, i); + } + ++ public double getMax(EnumDirection.EnumAxis enumdirection_enumaxis) { return c(enumdirection_enumaxis); } // Yatopia - OBFHELPER + public double c(EnumDirection.EnumAxis enumdirection_enumaxis) { + int i = this.a.b(enumdirection_enumaxis); + +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapeMerger.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapeMerger.java +index d2a46ce0c5c980d34dc2f4b716a174a81a68f1d0..0c594866cb291abb2e90b149d52445a628331879 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapeMerger.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapeMerger.java +@@ -2,7 +2,7 @@ package net.minecraft.world.phys.shapes; + + import it.unimi.dsi.fastutil.doubles.DoubleList; + +-interface VoxelShapeMerger { ++public interface VoxelShapeMerger { // Yatopia - make Public + + DoubleList a(); + +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java +index 049aa5f51e7517744b25f8c2c4b5e7a7de24a73e..173197e9dc6acb31aa41ce645224fd9a2733f27c 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java +@@ -5,6 +5,8 @@ import com.google.common.math.DoubleMath; + import com.google.common.math.IntMath; + import it.unimi.dsi.fastutil.doubles.DoubleArrayList; + import it.unimi.dsi.fastutil.doubles.DoubleList; ++import me.jellysquid.mods.lithium.common.shapes.pairs.LithiumDoublePairList; // Yatopia ++ + import java.util.Arrays; + import java.util.Iterator; + import java.util.Objects; +@@ -476,7 +478,7 @@ public final class VoxelShapes { + // doublelist is usually a DoubleArrayList with Infinite head/tails that falls to the final else clause + // This is actually the most common path, so jump to it straight away + if (doublelist.getDouble(0) == Double.NEGATIVE_INFINITY && doublelist.getDouble(doublelist.size() - 1) == Double.POSITIVE_INFINITY) { +- return new VoxelShapeMergerList(doublelist, doublelist1, flag, flag1); ++ return new LithiumDoublePairList(doublelist, doublelist1, flag, flag1); // Yatopia - Port lithium + } + // Split out rest to hopefully inline the above + return lessCommonMerge(i, doublelist, doublelist1, flag, flag1); diff --git a/patches/server/0071-lithium-skip-ticking-block-entities-that-are-doing-n.patch b/patches/server/0071-lithium-skip-ticking-block-entities-that-are-doing-n.patch new file mode 100644 index 00000000..38e3d9f1 --- /dev/null +++ b/patches/server/0071-lithium-skip-ticking-block-entities-that-are-doing-n.patch @@ -0,0 +1,1218 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <50278648+2No2Name@users.noreply.github.com> +Date: Sun, 21 Feb 2021 21:34:18 -0500 +Subject: [PATCH] lithium: skip ticking block entities that are doing nothing + + +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/collections/ListeningList.java b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/ListeningList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..edcc9ec27cc7d8dc5bf04e1f70362b505742570c +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/ListeningList.java +@@ -0,0 +1,255 @@ ++package me.jellysquid.mods.lithium.common.util.collections; ++ ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.*; ++import java.util.function.Consumer; ++import java.util.function.Predicate; ++import java.util.function.UnaryOperator; ++import java.util.stream.Stream; ++ ++public class ListeningList implements List { ++ private final List delegate; ++ private final Runnable changeCallback; ++ ++ public ListeningList(List delegate, Runnable changeCallback) { ++ this.delegate = delegate; ++ this.changeCallback = changeCallback; ++ } ++ ++ private void onChange() { ++ this.changeCallback.run(); ++ } ++ ++ ++ @Override ++ public int size() { ++ return this.delegate.size(); ++ } ++ ++ @Override ++ public boolean isEmpty() { ++ return this.delegate.isEmpty(); ++ } ++ ++ @Override ++ public boolean contains(Object o) { ++ return this.delegate.contains(o); ++ } ++ ++ @NotNull ++ @Override ++ public Iterator iterator() { ++ return this.listIterator(); ++ } ++ ++ @NotNull ++ @Override ++ public Object[] toArray() { ++ return this.delegate.toArray(); ++ } ++ ++ @Override ++ public void forEach(Consumer consumer) { ++ this.delegate.forEach(consumer); ++ } ++ ++ @NotNull ++ @Override ++ public T1[] toArray(@NotNull T1[] t1s) { ++ //noinspection SuspiciousToArrayCall ++ return this.delegate.toArray(t1s); ++ } ++ ++ @Override ++ public boolean add(T t) { ++ boolean add = this.delegate.add(t); ++ this.onChange(); ++ //noinspection ConstantConditions ++ return add; ++ } ++ ++ @Override ++ public boolean remove(Object o) { ++ boolean remove = this.delegate.remove(o); ++ this.onChange(); ++ return remove; ++ } ++ ++ @Override ++ public boolean containsAll(@NotNull Collection collection) { ++ return this.delegate.containsAll(collection); ++ } ++ ++ @Override ++ public boolean addAll(@NotNull Collection collection) { ++ boolean addAll = this.delegate.addAll(collection); ++ this.onChange(); ++ return addAll; ++ } ++ ++ @Override ++ public boolean addAll(int i, @NotNull Collection collection) { ++ boolean addAll = this.delegate.addAll(i, collection); ++ this.onChange(); ++ return addAll; ++ } ++ ++ @Override ++ public boolean removeAll(@NotNull Collection collection) { ++ boolean b = this.delegate.removeAll(collection); ++ this.onChange(); ++ return b; ++ } ++ ++ @Override ++ public boolean removeIf(Predicate predicate) { ++ boolean b = this.delegate.removeIf(predicate); ++ this.onChange(); ++ return b; ++ } ++ ++ @Override ++ public boolean retainAll(@NotNull Collection collection) { ++ boolean b = this.delegate.retainAll(collection); ++ this.onChange(); ++ return b; ++ } ++ ++ @Override ++ public void replaceAll(UnaryOperator unaryOperator) { ++ this.delegate.replaceAll( unaryOperator); ++ this.onChange(); ++ } ++ ++ @Override ++ public void sort(Comparator comparator) { ++ this.delegate.sort(comparator); ++ this.onChange(); ++ } ++ ++ @Override ++ public void clear() { ++ this.delegate.clear(); ++ this.onChange(); ++ } ++ ++ @Override ++ public T get(int i) { ++ return this.delegate.get(i); ++ } ++ ++ @Override ++ public T set(int i, T t) { ++ T set = this.delegate.set(i, t); ++ this.onChange(); ++ return set; ++ } ++ ++ @Override ++ public void add(int i, T t) { ++ this.delegate.add(i, t); ++ this.onChange(); ++ } ++ ++ @Override ++ public T remove(int i) { ++ T remove = this.delegate.remove(i); ++ this.onChange(); ++ return remove; ++ } ++ ++ @Override ++ public int indexOf(Object o) { ++ return this.delegate.indexOf(o); ++ } ++ ++ @Override ++ public int lastIndexOf(Object o) { ++ return this.delegate.lastIndexOf(o); ++ } ++ ++ @NotNull ++ @Override ++ public ListIterator listIterator() { ++ return this.listIterator(0); ++ } ++ ++ @NotNull ++ @Override ++ public ListIterator listIterator(int i) { ++ return new ListIterator() { ++ final ListIterator itDelegate = ListeningList.this.delegate.listIterator(i); ++ ++ @Override ++ public boolean hasNext() { ++ return this.itDelegate.hasNext(); ++ } ++ ++ @Override ++ public T next() { ++ return this.itDelegate.next(); ++ } ++ ++ @Override ++ public boolean hasPrevious() { ++ return this.itDelegate.hasPrevious(); ++ } ++ ++ @Override ++ public T previous() { ++ return this.itDelegate.previous(); ++ } ++ ++ @Override ++ public int nextIndex() { ++ return this.itDelegate.nextIndex(); ++ } ++ ++ @Override ++ public int previousIndex() { ++ return this.itDelegate.previousIndex(); ++ } ++ ++ @Override ++ public void remove() { ++ this.itDelegate.remove(); ++ ListeningList.this.onChange(); ++ } ++ ++ @Override ++ public void set(T t) { ++ this.itDelegate.set(t); ++ ListeningList.this.onChange(); ++ ++ } ++ ++ @Override ++ public void add(T t) { ++ this.itDelegate.add(t); ++ ListeningList.this.onChange(); ++ } ++ }; ++ } ++ ++ @NotNull ++ @Override ++ public List subList(int i, int i1) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public Spliterator spliterator() { ++ return this.delegate.spliterator(); ++ } ++ ++ @Override ++ public Stream stream() { ++ return this.delegate.stream(); ++ } ++ ++ @Override ++ public Stream parallelStream() { ++ return this.delegate.parallelStream(); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/collections/MaskedTickingBlockEntityList.java b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/MaskedTickingBlockEntityList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7f7e4810a9aca382d75862e56b0cf93157b54b92 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/MaskedTickingBlockEntityList.java +@@ -0,0 +1,320 @@ ++package me.jellysquid.mods.lithium.common.util.collections; ++ ++import com.google.common.collect.Iterators; ++import it.unimi.dsi.fastutil.ints.IntArrayList; ++import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ReferenceArrayList; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.*; ++import java.util.function.Predicate; ++ ++public class MaskedTickingBlockEntityList implements List { ++ private final Predicate mayContain; ++ ++ private final Reference2IntOpenHashMap allElements2Index; ++ private final ReferenceArrayList allElements; ++ ++ private final IntArrayList filteredSuccessor; ++ private final BitSet filteredElementsMask; ++ ++ private int firstRemovedIndex; ++ ++ //Visualization of the internal datastructures ++ //indices: 0 1 2 3 4 5 6 7 8 9 ++ //allElements: A B C D - F G H I J //E was fully removed, C,F,G,I were filtered away ++ //filteredMask: 1 1 0 1 0 0 0 1 0 1 ++ //successor: 0 1 3 - 7 - - - 9 - - (index offset by 1, due to the first successor having to point to the first element) ++ //Removals from the allElements ArrayList are done by setting the value to null ++ //The successor list is used to iterate through the allElements ArrayList with an increasing index, but skipping long chains of null elements. ++ //The BitSet mask is used to find the predecessor and successor quickly (not asymptotically fast, but fast enough) ++ ++ public MaskedTickingBlockEntityList(List list, Predicate mayContain) { ++ this.mayContain = mayContain; ++ this.allElements = new ReferenceArrayList<>(); ++ this.allElements2Index = new Reference2IntOpenHashMap<>(); ++ this.allElements2Index.defaultReturnValue(-1); ++ this.filteredSuccessor = new IntArrayList(); ++ this.filteredElementsMask = new BitSet(); ++ this.firstRemovedIndex = Integer.MAX_VALUE; ++ ++ for (T t : list) { ++ if (this.mayContain.test(t)) { ++ int index = this.allElements.size(); ++ this.allElements.add(t); ++ this.filteredElementsMask.set(index); ++ this.filteredSuccessor.add(index); ++ this.allElements2Index.put(t, index); ++ } ++ } ++ this.filteredSuccessor.add(-1); ++ } ++ ++ public void setEntryVisible(T t, boolean value) { ++ this.setEntryVisible(this.allElements2Index.getOrDefault(t, -1), value); ++ } ++ ++ public void setEntryVisible(int index, boolean value) { ++ //Visualization of the operations possible ++ //All: A B C D - F G H I J (- for null) ++ //filteredMask:1 1 0 1 0 0 0 1 0 1 ++ //indices: 0 1 2 3 4 5 6 7 8 9 ++ //successor: 0 1 3 - 7 - - - 9 - - (- for no successor) ++ //Set F visible: ++ //All: A B C D - F G H I J ++ //filteredMask:1 1 0 1 0 1 0 1 0 1 //set mask at F to 1 ++ //indices: 0 1 2 3 4 5 6 7 8 9 ++ //successor: 0 1 3 - 5 - 7 - 9 - - //update successor of predecessor to F and set F's successor to old successor of predecessor ++ //Set D filtered: ++ //All: A B C D - F G H I J ++ //Mask: 1 1 0 0 0 1 0 1 0 1 //set mask at D to 0 ++ //indices: 0 1 2 3 4 5 6 7 8 9 ++ //successor: 0 1 5 - - - 7 - 9 - - //update successor of predecessor to old successor of D and remove D's successor value ++ ++ //These calls do not modify the size, they can't cause rehashing, they are safe to use during iteration ++ if (index == -1 || value == this.filteredElementsMask.get(index)) { ++ return; ++ } ++ ++ this.filteredElementsMask.set(index, value); ++ int predecessor = this.filteredElementsMask.previousSetBit(index - 1); ++ if (value) { ++ int successor = this.filteredSuccessor.getInt(predecessor + 1); ++ this.filteredSuccessor.set(predecessor + 1, index); ++ this.filteredSuccessor.set(index + 1, successor); ++ } else { ++ int successor = this.filteredSuccessor.getInt(index + 1); ++ this.filteredSuccessor.set(predecessor + 1, successor); ++ this.filteredSuccessor.set(index + 1, -2); //no successor as this element cannot be reached ++ } ++ } ++ ++ private void compact() { ++ int targetSize = this.size(); ++ int newIndex = this.firstRemovedIndex - 1; ++ int lastVisible = this.filteredElementsMask.previousSetBit(newIndex); ++ ++ ++ for (int i = newIndex + 1; i < this.allElements.size(); i++) { ++ T t = this.allElements.get(i); ++ if (t == null) { ++ continue; ++ } ++ boolean visible = this.filteredElementsMask.get(i); ++ //shift all entries to the lower indices (filling the gaps created by remove() setting null) ++ newIndex++; ++ //i is guaranteed to not be smaller than newIndex, therefore we can write to the same collections ++ ++ this.allElements.set(newIndex, t); ++ this.allElements2Index.put(t, newIndex); ++ this.filteredElementsMask.set(newIndex, visible); ++ ++ //update the successor links ++ this.filteredSuccessor.set(newIndex + 1, -2); //no successor as there is no next entry yet ++ if (visible) { ++ this.filteredSuccessor.set(lastVisible + 1, newIndex); ++ lastVisible = newIndex; ++ } ++ } ++ ++ if (newIndex + 1 != targetSize) { ++ throw new IllegalStateException("Compaction ended up with incorrect size: Should be: " + targetSize + " but is: " + (newIndex + 1)); ++ } ++ ++ this.filteredSuccessor.set(lastVisible + 1, -1); //-1 means this was the last element ++ this.firstRemovedIndex = Integer.MAX_VALUE; ++ ++ this.filteredSuccessor.removeElements(targetSize + 1, this.filteredSuccessor.size()); ++ this.allElements.removeElements(targetSize, this.allElements.size()); ++ this.filteredElementsMask.clear(targetSize, this.filteredElementsMask.size()); ++ ++ this.filteredSuccessor.trim(targetSize * 4); ++ this.allElements.trim(targetSize * 4); ++ this.allElements2Index.trim(targetSize * 4); ++ } ++ ++ public Iterator filteredIterator() { ++ return new Iterator() { ++ int next = MaskedTickingBlockEntityList.this.filteredSuccessor.getInt(0); ++ T prev; ++ ++ @Override ++ public boolean hasNext() { ++ return this.next != -1; ++ } ++ ++ @Override ++ public T next() { ++ int next = this.next; ++ T prev = MaskedTickingBlockEntityList.this.allElements.get(next); ++ this.prev = prev; ++ this.next = MaskedTickingBlockEntityList.this.filteredSuccessor.getInt(next + 1); ++ return prev; ++ } ++ ++ @Override ++ public void remove() { ++ MaskedTickingBlockEntityList.this.remove(this.prev); ++ } ++ }; ++ } ++ ++ @Override ++ public int size() { ++ return this.allElements2Index.size(); ++ } ++ ++ @Override ++ public boolean isEmpty() { ++ return this.size() == 0; ++ } ++ ++ @Override ++ public boolean contains(Object o) { ++ //noinspection SuspiciousMethodCalls ++ return this.allElements2Index.containsKey(o); ++ } ++ ++ @Override ++ public @NotNull Iterator iterator() { ++ return Iterators.unmodifiableIterator(this.allElements2Index.keySet().iterator()); ++ } ++ ++ @Override ++ public Object[] toArray() { ++ return this.allElements2Index.keySet().toArray(); ++ } ++ ++ @NotNull ++ @Override ++ public T1[] toArray(@NotNull T1[] t1s) { ++ //noinspection SuspiciousToArrayCall ++ return this.allElements2Index.keySet().toArray(t1s); ++ } ++ ++ @Override ++ public boolean add(T t) { ++ int arraySize = this.allElements.size(); ++ int invalidEntries = arraySize - this.size(); ++ //Compaction is done during the add operation as it is guaranteed to not happen during iteration ++ if ((arraySize > 2048 && invalidEntries > (arraySize >> 1)) || arraySize >= Integer.MAX_VALUE - 1 && invalidEntries != 0) { ++ this.compact(); ++ } ++ ++ if (!this.mayContain.test(t)) { ++ return false; ++ } ++ ++ int index = this.allElements.size(); ++ int i = this.allElements2Index.putIfAbsent(t, index); ++ if (i != -1) { ++ return false; ++ } ++ this.allElements.add(t); ++ this.filteredSuccessor.add(0);//increase size so setEntryVisible doesn't crash with indexOutOfBounds ++ this.setEntryVisible(index, true); ++ ++ return true; ++ } ++ ++ @Override ++ public boolean remove(Object o) { ++ int index = this.allElements2Index.removeInt(o); ++ if (index == -1) { ++ return false; ++ } ++ this.setEntryVisible(index, false); ++ this.allElements.set(index, null); ++ this.firstRemovedIndex = Math.min(this.firstRemovedIndex, index); ++ return true; ++ } ++ ++ @Override ++ public boolean containsAll(@NotNull Collection c) { ++ return this.allElements2Index.keySet().containsAll(c); ++ } ++ ++ @Override ++ public boolean addAll(@NotNull Collection c) { ++ boolean b = false; ++ for (T t : c) { ++ this.add(t); ++ b = true; ++ } ++ return b; ++ } ++ ++ @Override ++ public boolean addAll(int index, @NotNull Collection c) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public boolean removeAll(@NotNull Collection c) { ++ boolean b = false; ++ for (Object t : c) { ++ b |= this.remove(t); ++ } ++ return b; ++ } ++ ++ @Override ++ public boolean retainAll(@NotNull Collection c) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void clear() { ++ this.allElements2Index.clear(); ++ this.allElements.clear(); ++ this.filteredSuccessor.clear(); ++ this.filteredElementsMask.clear(); ++ this.firstRemovedIndex = Integer.MAX_VALUE; ++ this.filteredSuccessor.add(-1); ++ } ++ ++ @Override ++ public T get(int index) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public T set(int index, T element) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void add(int index, T element) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public T remove(int index) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public int indexOf(Object o) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public int lastIndexOf(Object o) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public @NotNull ListIterator listIterator() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public @NotNull ListIterator listIterator(int index) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public @NotNull List subList(int fromIndex, int toIndex) { ++ throw new UnsupportedOperationException(); ++ } ++} +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/world/blockentity/BlockEntitySleepTracker.java b/src/main/java/me/jellysquid/mods/lithium/common/world/blockentity/BlockEntitySleepTracker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c54e1e1f9c6b09022c0f20d5ea4d552bd3ceed55 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/world/blockentity/BlockEntitySleepTracker.java +@@ -0,0 +1,7 @@ ++package me.jellysquid.mods.lithium.common.world.blockentity; ++ ++import net.minecraft.world.level.block.entity.TileEntity; ++ ++public interface BlockEntitySleepTracker { ++ void setAwake(TileEntity tileEntity, boolean needsTicking); ++} +\ No newline at end of file +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/world/blockentity/SleepingBlockEntity.java b/src/main/java/me/jellysquid/mods/lithium/common/world/blockentity/SleepingBlockEntity.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4e765ab19ffb300b6c810333b2dc797637ae5c1b +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/world/blockentity/SleepingBlockEntity.java +@@ -0,0 +1,7 @@ ++package me.jellysquid.mods.lithium.common.world.blockentity; ++ ++public interface SleepingBlockEntity { ++ default boolean canTickOnSide(boolean isClient) { ++ return true; ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index db2b1863b7f46be53839fb3e86870745fb7567fd..8cecf61169d28b7ddb8e1af180ac8bc0e5fef324 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -98,8 +98,11 @@ import org.bukkit.event.block.BlockPhysicsEvent; + + import net.gegy1000.tictacs.NonBlockingWorldAccess; // Yatopia + import me.jellysquid.mods.lithium.common.entity.tracker.EntityTrackerEngine; // Yatopia ++import me.jellysquid.mods.lithium.common.util.collections.MaskedTickingBlockEntityList; // Yatopia ++import me.jellysquid.mods.lithium.common.world.blockentity.BlockEntitySleepTracker; // Yatopia ++import me.jellysquid.mods.lithium.common.world.blockentity.SleepingBlockEntity; // Yatopia + +-public abstract class World implements GeneratorAccess, AutoCloseable, NonBlockingWorldAccess { // Yatopia ++public abstract class World implements GeneratorAccess, AutoCloseable, NonBlockingWorldAccess, BlockEntitySleepTracker { // Yatopia + + protected static final Logger LOGGER = LogManager.getLogger(); + public static final Codec> f = MinecraftKey.a.xmap(ResourceKey.b(IRegistry.L), ResourceKey::a); +@@ -108,9 +111,17 @@ public abstract class World implements GeneratorAccess, AutoCloseable, NonBlocki + public static final ResourceKey THE_END = ResourceKey.a(IRegistry.L, new MinecraftKey("the_end")); + private static final EnumDirection[] a = EnumDirection.values(); + //public final List tileEntityList = Lists.newArrayList(); // Paper - remove unused list +- public final List tileEntityListTick = me.jellysquid.mods.lithium.common.util.collections.HashedList.wrapper(Lists.newArrayList()); // Yatopia ++ public List tileEntityListTick = me.jellysquid.mods.lithium.common.util.collections.HashedList.wrapper(Lists.newArrayList()); // Yatopia + protected final List tileEntityListPending = me.jellysquid.mods.lithium.common.util.collections.HashedList.wrapper(Lists.newArrayList()); // Yatopia + protected final java.util.Set tileEntityListUnload = java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<>()); // Airplane - use set with faster contains ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ private MaskedTickingBlockEntityList tileEntityListTick$lithium; ++ ++ @Override ++ public void setAwake(TileEntity tileEntity, boolean needsTicking) { ++ this.tileEntityListTick$lithium.setEntryVisible(tileEntity, needsTicking); ++ } ++ // Yatopia end + public final Thread serverThread; + private final boolean debugWorld; + private int d; +@@ -371,6 +382,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable, NonBlocki + this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); + this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); + this.tracker = new EntityTrackerEngine(); // Yatopia - Port lithium ++ this.tileEntityListTick$lithium = new MaskedTickingBlockEntityList<>(this.tileEntityListTick, blockEntity -> ((SleepingBlockEntity) blockEntity).canTickOnSide(false)); // Yatopia ++ this.tileEntityListTick = tileEntityListTick$lithium; // Yatopia + } + + // Yatopia start - Port lithium +@@ -1041,6 +1054,15 @@ public abstract class World implements GeneratorAccess, AutoCloseable, NonBlocki + + } + ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ public Iterator getAwakeBlockEntities(List list) { ++ if (list == this.tileEntityListTick && list instanceof MaskedTickingBlockEntityList) { ++ return ((MaskedTickingBlockEntityList) list).filteredIterator(); ++ } ++ return list.iterator(); ++ } ++ // Yatopia end ++ + public void tickBlockEntities() { + GameProfilerFiller gameprofilerfiller = this.getMethodProfiler(); + +@@ -1063,11 +1085,19 @@ public abstract class World implements GeneratorAccess, AutoCloseable, NonBlocki + + this.tickingTileEntities = true; + // Spigot start ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing + // Iterator iterator = this.tileEntityListTick.iterator(); ++ Iterator iterator = getAwakeBlockEntities(this.tileEntityListTick); ++ + int tilesThisCycle = 0; ++ while (iterator.hasNext()) { ++ TileEntity tileentity = (TileEntity) iterator.next(); ++ /* + for (tileTickPosition = 0; tileTickPosition < tileEntityListTick.size(); tileTickPosition++) { // Paper - Disable tick limiters + tileTickPosition = (tileTickPosition < tileEntityListTick.size()) ? tileTickPosition : 0; + TileEntity tileentity = (TileEntity) this.tileEntityListTick.get(tileTickPosition); ++ */ ++ // Yatopia end + // Spigot start + if (tileentity == null) { + getServer().getLogger().severe("Spigot has detected a null entity and has removed it, preventing a crash"); +@@ -1105,8 +1135,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable, NonBlocki + throwable.printStackTrace(); + getServer().getPluginManager().callEvent(new ServerExceptionEvent(new ServerInternalException(msg, throwable))); + // Paper end +- tilesThisCycle--; +- this.tileEntityListTick.remove(tileTickPosition--); ++ iterator.remove(); // Yatopia + continue; + // Paper end + // Spigot start +@@ -1119,8 +1148,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable, NonBlocki + + if (tileentity.isRemoved()) { + // Spigot start +- tilesThisCycle--; +- this.tileEntityListTick.remove(tileTickPosition--); ++ iterator.remove(); // Yatopia + // Spigot end + //this.tileEntityList.remove(tileentity); // Paper - remove unused list + // Paper - prevent double chunk lookups +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java +index 8a049d3de8937a6c8afe178ccd134e2511fb3baf..9b447a9416613f0ccfeb0f81d5b099fcd0417df7 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java +@@ -27,8 +27,9 @@ import org.bukkit.inventory.InventoryHolder; + import org.spigotmc.CustomTimingsHandler; // Spigot + import co.aikar.timings.MinecraftTimings; // Paper + import co.aikar.timings.Timing; // Paper ++import me.jellysquid.mods.lithium.common.world.blockentity.SleepingBlockEntity; + +-public abstract class TileEntity implements net.minecraft.server.KeyedObject { // Paper ++public abstract class TileEntity implements net.minecraft.server.KeyedObject, SleepingBlockEntity { // Paper // Yatopia + + public Timing tickTimer = MinecraftTimings.getTileEntityTimings(this); // Paper + // CraftBukkit start - data containers +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBeehive.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBeehive.java +index d97acd72c30e1051f3e14c3c23b1a6b6e6fa42f0..b11c260908791d7fca711954ae0b715a9bd0634f 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBeehive.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBeehive.java +@@ -23,6 +23,9 @@ import net.minecraft.world.level.block.BlockBeehive; + import net.minecraft.world.level.block.BlockCampfire; + import net.minecraft.world.level.block.BlockFire; + import net.minecraft.world.level.block.state.IBlockData; ++import me.jellysquid.mods.lithium.common.util.collections.ListeningList; ++import me.jellysquid.mods.lithium.common.world.blockentity.BlockEntitySleepTracker; ++import java.util.ArrayList; + + public class TileEntityBeehive extends TileEntity implements ITickable { + +@@ -30,11 +33,25 @@ public class TileEntityBeehive extends TileEntity implements ITickable { + @Nullable + public BlockPosition flowerPos = null; + public int maxBees = 3; // CraftBukkit - allow setting max amount of bees a hive can hold ++ public boolean doInit = true; // Yatopia ++ public boolean isTicking = true; // Yatopia ++ + + public TileEntityBeehive() { + super(TileEntityTypes.BEEHIVE); + } + ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ public void checkSleepState() { ++ if (this.world != null ) { ++ if ((this.bees.size() == 0) == this.isTicking) { ++ this.isTicking = !this.isTicking; ++ ((BlockEntitySleepTracker) this.world).setAwake(this, this.isTicking); ++ } ++ } ++ } ++ // Yatopia end ++ + @Override + public void update() { + if (this.d()) { +@@ -304,6 +321,12 @@ public class TileEntityBeehive extends TileEntity implements ITickable { + + @Override + public void tick() { ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ if (this.doInit) { ++ this.doInit = false; ++ this.checkSleepState(); ++ } ++ // Yatopia end + if (this.bees.size() == 0) { return; } // Yatopia - TE optimizations + if (!this.world.isClientSide) { + this.y(); +@@ -324,6 +347,7 @@ public class TileEntityBeehive extends TileEntity implements ITickable { + @Override + public void load(IBlockData iblockdata, NBTTagCompound nbttagcompound) { + super.load(iblockdata, nbttagcompound); ++ this.checkSleepState(); // Yatopia + this.bees.clear(); + NBTTagList nbttaglist = nbttagcompound.getList("Bees", 10); + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBell.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBell.java +index 84f9f52c5b632621b509448ac1c760f64de6b062..c4c983b15c28db856be7335e53637a21af2c1b2f 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBell.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBell.java +@@ -20,6 +20,7 @@ import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.level.World; + import net.minecraft.world.phys.AxisAlignedBB; + import org.apache.commons.lang3.mutable.MutableInt; ++import me.jellysquid.mods.lithium.common.world.blockentity.BlockEntitySleepTracker; + + public class TileEntityBell extends TileEntity implements ITickable { + +@@ -30,6 +31,8 @@ public class TileEntityBell extends TileEntity implements ITickable { + private List h; private List getEntitiesAtRing() { return this.h; } // Paper - OBFHELPER + private boolean i; private boolean getShouldReveal() { return this.i; } // Paper - OBFHELPER + private int j; ++ public boolean ringing; // Yatopia ++ public boolean resonating; // Yatopia + + public TileEntityBell() { + super(TileEntityTypes.BELL); +@@ -37,6 +40,11 @@ public class TileEntityBell extends TileEntity implements ITickable { + + @Override + public boolean setProperty(int i, int j) { ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ if (!this.ringing && i == 1 && this.world != null) { ++ ((BlockEntitySleepTracker) this.world).setAwake(this, true); ++ } ++ // Yatopia end + if (i == 1) { + this.f(); + this.j = 0; +@@ -81,6 +89,11 @@ public class TileEntityBell extends TileEntity implements ITickable { + } + } + ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ if (!this.ringing && !this.resonating && this.world != null) { ++ ((BlockEntitySleepTracker)this.world).setAwake(this, false); ++ } ++ // Yatopia end + } + + private void d() { +@@ -88,6 +101,11 @@ public class TileEntityBell extends TileEntity implements ITickable { + } + + public void a(EnumDirection enumdirection) { ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ if (!this.ringing && this.world != null) { ++ ((BlockEntitySleepTracker)this.world).setAwake(this, true); ++ } ++ // Yatopia end + BlockPosition blockposition = this.getPosition(); + + this.c = enumdirection; +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBrewingStand.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBrewingStand.java +index 95ca7d9e1eadbe5cc5674ed7352c1ed4d707363d..b5d5f38e520bb5d7912cc86d8ee25858bbd0786b 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBrewingStand.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBrewingStand.java +@@ -34,6 +34,7 @@ import org.bukkit.event.inventory.BrewEvent; + import org.bukkit.event.inventory.BrewingStandFuelEvent; + import org.bukkit.inventory.InventoryHolder; + // CraftBukkit end ++import me.jellysquid.mods.lithium.common.world.blockentity.BlockEntitySleepTracker; + + public class TileEntityBrewingStand extends TileEntityContainer implements IWorldInventory, ITickable { + +@@ -50,6 +51,7 @@ public class TileEntityBrewingStand extends TileEntityContainer implements IWorl + private int lastTick = MinecraftServer.currentTick; + public List transaction = new java.util.ArrayList(); + private int maxStack = 64; ++ public boolean isTicking = true; // Yatopia + + public void onOpen(CraftHumanEntity who) { + transaction.add(who); +@@ -72,6 +74,17 @@ public class TileEntityBrewingStand extends TileEntityContainer implements IWorl + return maxStack; + } + ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ @Override ++ public void update() { ++ super.update(); ++ if (!this.isTicking && this.world != null) { ++ this.isTicking = true; ++ ((BlockEntitySleepTracker)this.world).setAwake(this, true); ++ } ++ } ++ // Yatopia end ++ + public void setMaxStackSize(int size) { + maxStack = size; + } +@@ -141,6 +154,12 @@ public class TileEntityBrewingStand extends TileEntityContainer implements IWorl + + @Override + public void tick() { ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ if (this.brewTime == 0 && this.world != null) { ++ this.isTicking = false; ++ ((BlockEntitySleepTracker)this.world).setAwake(this, false); ++ } ++ // Yatopia end + ItemStack itemstack = (ItemStack) this.items.get(4); + + if (this.fuelLevel <= 0 && itemstack.getItem() == Items.BLAZE_POWDER) { +@@ -280,6 +299,12 @@ public class TileEntityBrewingStand extends TileEntityContainer implements IWorl + @Override + public void load(IBlockData iblockdata, NBTTagCompound nbttagcompound) { + super.load(iblockdata, nbttagcompound); ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ if (!this.isTicking && this.world != null) { ++ this.isTicking = true; ++ ((BlockEntitySleepTracker) this.world).setAwake(this, true); ++ } ++ // Yatopia end + this.items = NonNullList.a(this.getSize(), ItemStack.b); + ContainerUtil.b(nbttagcompound, this.items); + this.brewTime = nbttagcompound.getShort("BrewTime"); +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityCampfire.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityCampfire.java +index 08759f461ec947c0d5655557f49d8717afee6f00..cdff01d4da417ef1be6238d3faaf464ee7884bd5 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityCampfire.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityCampfire.java +@@ -27,12 +27,18 @@ import org.bukkit.craftbukkit.block.CraftBlock; + import org.bukkit.craftbukkit.inventory.CraftItemStack; + import org.bukkit.event.block.BlockCookEvent; + // CraftBukkit end ++import net.minecraft.core.NonNullList; ++import me.jellysquid.mods.lithium.common.world.blockentity.BlockEntitySleepTracker; + + public class TileEntityCampfire extends TileEntity implements Clearable, ITickable { + + private final NonNullList items; + public final int[] cookingTimes; + public final int[] cookingTotalTimes; ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ public boolean isTicking = true; ++ public boolean doInit = true; ++ // Yatopia end + + public TileEntityCampfire() { + super(TileEntityTypes.CAMPFIRE); +@@ -41,8 +47,50 @@ public class TileEntityCampfire extends TileEntity implements Clearable, ITickab + this.cookingTotalTimes = new int[4]; + } + ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ @Override ++ public void update() { ++ super.update(); ++ this.checkSleepState(); ++ } ++ ++ @Override ++ public void invalidateBlockCache() { ++ super.invalidateBlockCache(); ++ this.checkSleepState(); ++ } ++ ++ public void checkSleepState() { ++ if (this.world == null) { ++ return; ++ } ++ boolean shouldTick = false; ++ NonNullList beingCooked = this.items; ++ for (int i = 0; i < beingCooked.size(); i++) { ++ ItemStack stack = beingCooked.get(i); ++ if (!stack.isEmpty()) { ++ if (this.cookingTimes[i] > 0 || this.getBlock().get(BlockCampfire.LIT)) { ++ shouldTick = true; ++ break; ++ } ++ } ++ } ++ ++ if (shouldTick != this.isTicking) { ++ this.isTicking = shouldTick; ++ ((BlockEntitySleepTracker)this.world).setAwake(this, shouldTick); ++ } ++ } ++ // Yatopia end ++ + @Override + public void tick() { ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ if (this.doInit) { ++ this.doInit = false; ++ this.checkSleepState(); ++ } ++ // Yatopia end + boolean flag = (Boolean) this.getBlock().get(BlockCampfire.LIT); + boolean flag1 = this.world.isClientSide; + +@@ -149,6 +197,7 @@ public class TileEntityCampfire extends TileEntity implements Clearable, ITickab + @Override + public void load(IBlockData iblockdata, NBTTagCompound nbttagcompound) { + super.load(iblockdata, nbttagcompound); ++ this.checkSleepState(); // Yatopia - lithium: skip ticking block entities that are doing nothing + this.items.clear(); + ContainerUtil.b(nbttagcompound, this.items); + int[] aint; +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityChest.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityChest.java +index 111f62d0e5b40e945793b8f504f2c035c0884a6a..130ffed3827d3716430414976a0e4f591598586c 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityChest.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityChest.java +@@ -33,14 +33,27 @@ import net.minecraft.world.level.block.Blocks; + import org.bukkit.craftbukkit.entity.CraftHumanEntity; + import org.bukkit.entity.HumanEntity; + // CraftBukkit end ++ import me.jellysquid.mods.lithium.common.world.blockentity.BlockEntitySleepTracker; // Yatopia + + public class TileEntityChest extends TileEntityLootable { // Paper - Remove ITickable + + private NonNullList items; + protected float a; ++ private float getAnimationAngle() { return a; } // Yatopia - OBFHELPER + protected float b; ++ private float getLastAnimationAngle() { return b; } // Yatopia - OBFHELPER + public int viewingCount; + private int j; ++ private int ticksOpen = j; // Yatopia - OBFHELPER ++ private int lastTime; // Yatopia ++ ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ public void checkWakeUp() { ++ if ((this.viewingCount != 0 || this.getAnimationAngle() != 0.0F || this.getLastAnimationAngle() != 0) && this.world != null) { ++ ((BlockEntitySleepTracker) this.world).setAwake(this, true); ++ } ++ } ++ // Yatopia end + + // CraftBukkit start - add fields and methods + public List transaction = new java.util.ArrayList(); +@@ -113,11 +126,27 @@ public class TileEntityChest extends TileEntityLootable { // Paper - Remove ITic + } + + public void tick() { ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ // noinspection ConstantConditions ++ int time = (int) this.world.getTime(); ++ // ticksOpen == 0 implies most likely that this is the first tick. We don't want to update the value then. ++ // overflow case is handled by not going to sleep when this.ticksOpen == 0 ++ if (this.ticksOpen != 0) { ++ this.ticksOpen += time - this.lastTime - 1; ++ } ++ this.lastTime = time; ++ // Yatopia end ++ + int i = this.position.getX(); + int j = this.position.getY(); + int k = this.position.getZ(); + + ++this.j; ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ if (this.viewingCount == 0 && this.getAnimationAngle() == 0.0F && this.getLastAnimationAngle() == 0 && this.ticksOpen != 0 && this.world != null) { ++ ((BlockEntitySleepTracker) this.world).setAwake(this, false); ++ } ++ // Yatopia end + } + + public void doOpenLogic() { +@@ -223,6 +252,7 @@ public class TileEntityChest extends TileEntityLootable { // Paper - Remove ITic + + @Override + public boolean setProperty(int i, int j) { ++ this.checkWakeUp(); // Yatopia - lithium: skip ticking block entities that are doing nothing + if (i == 1) { + this.viewingCount = j; + return true; +@@ -254,7 +284,7 @@ public class TileEntityChest extends TileEntityLootable { // Paper - Remove ITic + // CraftBukkit end + this.onOpen(); + } +- ++ this.checkWakeUp(); // Yatopia - lithium: skip ticking block entities that are doing nothing + } + + @Override +@@ -275,6 +305,7 @@ public class TileEntityChest extends TileEntityLootable { // Paper - Remove ITic + // CraftBukkit end + this.onOpen(); + } ++ this.checkWakeUp(); // Yatopia - lithium: skip ticking block entities that are doing nothing + + } + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityEnchantTable.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityEnchantTable.java +index 4b1cb089355b455c6210f2df1af797cc363997cf..0049a675557cbc05e361c12e9fb84a38a3a21160 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityEnchantTable.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityEnchantTable.java +@@ -9,6 +9,7 @@ import net.minecraft.util.MathHelper; + import net.minecraft.world.INamableTileEntity; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.level.block.state.IBlockData; ++import me.jellysquid.mods.lithium.common.world.blockentity.BlockEntitySleepTracker; + + public class TileEntityEnchantTable extends TileEntity implements INamableTileEntity, ITickable { + +@@ -51,6 +52,7 @@ public class TileEntityEnchantTable extends TileEntity implements INamableTileEn + @Override + public void tick() { + if (!org.yatopiamc.yatopia.server.YatopiaConfig.shouldTickEnchantingTables) { return; } // Yatopia - TE optimizations ++ ((BlockEntitySleepTracker) this.world).setAwake(this, false); // Yatopia + this.j = this.i; + this.l = this.k; + EntityHuman entityhuman = this.world.a((double) this.position.getX() + 0.5D, (double) this.position.getY() + 0.5D, (double) this.position.getZ() + 0.5D, 3.0D, false); +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java +index 8a1425f565204fedd1007862b2af9db4c9217461..6083bec6879a625168bd55944d5c21e8151b8189 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java +@@ -53,9 +53,11 @@ import org.bukkit.event.inventory.FurnaceBurnEvent; + import org.bukkit.event.inventory.FurnaceExtractEvent; + import org.bukkit.event.inventory.FurnaceSmeltEvent; + // CraftBukkit end ++import me.jellysquid.mods.lithium.common.world.blockentity.BlockEntitySleepTracker; + + public abstract class TileEntityFurnace extends TileEntityContainer implements IWorldInventory, RecipeHolder, AutoRecipeOutput, ITickable { + ++ private boolean isTicking = true; // Yatopia + private static final int[] g = new int[]{0}; + private static final int[] h = new int[]{2, 1}; + private static final int[] i = new int[]{1}; +@@ -197,6 +199,17 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I + private int maxStack = MAX_STACK; + public List transaction = new java.util.ArrayList(); + ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ @Override ++ public void update() { ++ super.update(); ++ if (!this.isTicking && this.world != null) { ++ this.isTicking = true; ++ ((BlockEntitySleepTracker) this.world).setAwake(this, true); ++ } ++ } ++ // Yatopia end ++ + public List getContents() { + return this.items; + } +@@ -259,6 +272,12 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I + @Override + public void load(IBlockData iblockdata, NBTTagCompound nbttagcompound) { + super.load(iblockdata, nbttagcompound); ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ if (!this.isTicking && this.world != null) { ++ this.isTicking = true; ++ ((BlockEntitySleepTracker)this.world).setAwake(this, true); ++ } ++ // Yatopia end + this.items = NonNullList.a(this.getSize(), ItemStack.b); + ContainerUtil.b(nbttagcompound, this.items); + this.burnTime = nbttagcompound.getShort("BurnTime"); +@@ -389,7 +408,12 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I + if (flag1) { + this.update(); + } +- ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ if (!this.isTicking && this.world != null) { ++ this.isTicking = true; ++ ((BlockEntitySleepTracker)this.world).setAwake(this, true); ++ } ++ // Yatopia end + } + + protected boolean canBurn(@Nullable IRecipe irecipe) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityShulkerBox.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityShulkerBox.java +index c1a90eb01689b8b91200ba49d58360d5ea2004c4..8095740f1848443e87fd52c14d19dabb00a3ecd4 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityShulkerBox.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityShulkerBox.java +@@ -33,6 +33,7 @@ import net.minecraft.world.phys.shapes.VoxelShapes; + import org.bukkit.craftbukkit.entity.CraftHumanEntity; + import org.bukkit.entity.HumanEntity; + // CraftBukkit end ++import me.jellysquid.mods.lithium.common.world.blockentity.BlockEntitySleepTracker; + + public class TileEntityShulkerBox extends TileEntityLootable implements IWorldInventory, ITickable { + +@@ -41,7 +42,9 @@ public class TileEntityShulkerBox extends TileEntityLootable implements IWorldIn + public int viewingCount; + private TileEntityShulkerBox.AnimationPhase i; + private float j; ++ public float animationProgress = j; // Yatopia - OBFHELPER + private float k; ++ public float prevAnimationProgress = k; // Yatopia - OBFHELPER + @Nullable + private EnumColor l; + private boolean m; +@@ -96,6 +99,11 @@ public class TileEntityShulkerBox extends TileEntityLootable implements IWorldIn + this.m(); + } + ++ // Yatopia start - lithium: skip ticking block entities that are doing nothing ++ if (this.getAnimationPhase() == TileEntityShulkerBox.AnimationPhase.CLOSED && this.prevAnimationProgress == 0f && this.animationProgress == 0f && this.world != null) { ++ ((BlockEntitySleepTracker) this.world).setAwake(this, false); ++ } ++ // Yatopia end + } + + protected void h() { +@@ -127,6 +135,7 @@ public class TileEntityShulkerBox extends TileEntityLootable implements IWorldIn + + } + ++ public TileEntityShulkerBox.AnimationPhase getAnimationPhase() {return this.j(); } // Yatopia - OBFHELPER + public TileEntityShulkerBox.AnimationPhase j() { + return this.i; + } +@@ -209,6 +218,7 @@ public class TileEntityShulkerBox extends TileEntityLootable implements IWorldIn + + @Override + public boolean setProperty(int i, int j) { ++ if (this.world != null && i == 1) ((BlockEntitySleepTracker) this.world).setAwake(this, true); // Yatopia + if (i == 1) { + this.viewingCount = j; + if (j == 0) {