diff --git a/Spigot-API-Patches/Add-Mob-Goal-API.patch b/Spigot-API-Patches/Add-Mob-Goal-API.patch index e60f6b6f57..966f3a9f8d 100644 --- a/Spigot-API-Patches/Add-Mob-Goal-API.patch +++ b/Spigot-API-Patches/Add-Mob-Goal-API.patch @@ -315,7 +315,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + GoalKey PHANTOM_ATTACK_STRATEGY = GoalKey.of(Phantom.class, NamespacedKey.minecraft("phantom_attack_strategy")); + GoalKey PHANTOM_CIRCLE_AROUND_ANCHOR = GoalKey.of(Phantom.class, NamespacedKey.minecraft("phantom_circle_around_anchor")); + GoalKey PHANTOM_SWEEP_ATTACK = GoalKey.of(Phantom.class, NamespacedKey.minecraft("phantom_sweep_attack")); ++ /** ++ * @deprecated removed in 1.16 ++ */ ++ @Deprecated + GoalKey ANGER = GoalKey.of(PigZombie.class, NamespacedKey.minecraft("anger")); ++ /** ++ * @deprecated removed in 1.16 ++ */ ++ @Deprecated + GoalKey ANGER_OTHER = GoalKey.of(PigZombie.class, NamespacedKey.minecraft("anger_other")); + GoalKey POLARBEAR_ATTACK_PLAYERS = GoalKey.of(PolarBear.class, NamespacedKey.minecraft("polarbear_attack_players")); + GoalKey POLARBEAR_HURT_BY = GoalKey.of(PolarBear.class, NamespacedKey.minecraft("polarbear_hurt_by")); @@ -424,6 +432,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + GoalKey WATER = GoalKey.of(Creature.class, NamespacedKey.minecraft("water")); + GoalKey WATER_JUMP = GoalKey.of(Dolphin.class, NamespacedKey.minecraft("water_jump")); + GoalKey ZOMBIE_ATTACK = GoalKey.of(Zombie.class, NamespacedKey.minecraft("zombie_attack")); ++ GoalKey STROLL_VILLAGE_GOLEM = GoalKey.of(Creature.class, NamespacedKey.minecraft("stroll_village_golem")); ++ GoalKey UNIVERSAL_ANGER_RESET = GoalKey.of(Mob.class, NamespacedKey.minecraft("universal_anger_reset")); +} diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 diff --git a/Spigot-API-Patches/Potential-bed-API.patch b/Spigot-API-Patches/Potential-bed-API.patch index e6e0b4f696..8da39c803d 100644 --- a/Spigot-API-Patches/Potential-bed-API.patch +++ b/Spigot-API-Patches/Potential-bed-API.patch @@ -15,14 +15,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 */ public int getSleepTicks(); -+ /** -+ * Gets the Location where the player will spawn at their bed, null if -+ * they have not slept in one or their current bed spawn is invalid. -+ * -+ * @return Bed Spawn Location if bed exists, otherwise null. -+ */ -+ @Nullable -+ public Location getBedSpawnLocation(); + + // Paper start - Potential bed api + /** @@ -35,22 +27,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + @Nullable + public Location getPotentialBedLocation(); + // Paper end -+ -+ /** -+ * Sets the Location where the player will spawn at their bed. -+ * -+ * @param location where to set the respawn location -+ */ -+ public void setBedSpawnLocation(@Nullable Location location); -+ -+ /** -+ * Sets the Location where the player will spawn at their bed. -+ * -+ * @param location where to set the respawn location -+ * @param force whether to forcefully set the respawn location even if a -+ * valid bed is not present -+ */ -+ public void setBedSpawnLocation(@Nullable Location location, boolean force); + /** * Attempts to make the entity sleep at the given location. diff --git a/Spigot-Server-Patches/Add-Raw-Byte-ItemStack-Serialization.patch b/Spigot-Server-Patches/Add-Raw-Byte-ItemStack-Serialization.patch index dd478869a3..e32dfeeadd 100644 --- a/Spigot-Server-Patches/Add-Raw-Byte-ItemStack-Serialization.patch +++ b/Spigot-Server-Patches/Add-Raw-Byte-ItemStack-Serialization.patch @@ -33,7 +33,7 @@ diff --git a/src/main/java/net/minecraft/server/NBTCompressedStreamTools.java b/ index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/NBTCompressedStreamTools.java +++ b/src/main/java/net/minecraft/server/NBTCompressedStreamTools.java -@@ -0,0 +0,0 @@ import java.util.zip.GZIPOutputStream; +@@ -0,0 +0,0 @@ import io.netty.buffer.ByteBufInputStream; // Paper public class NBTCompressedStreamTools { diff --git a/Spigot-Server-Patches/Add-TNTPrimeEvent.patch b/Spigot-Server-Patches/Add-TNTPrimeEvent.patch index e070a1b9cb..0340916701 100644 --- a/Spigot-Server-Patches/Add-TNTPrimeEvent.patch +++ b/Spigot-Server-Patches/Add-TNTPrimeEvent.patch @@ -131,7 +131,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 craftBlock.getNMS().dropNaturally(world, blockposition, ItemStack.b); } + // Paper start - TNTPrimeEvent -+ org.bukkit.block.Block tntBlock = world.getWorld().getBlockAt(blockposition.x, blockposition.y, blockposition.z); ++ org.bukkit.block.Block tntBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); + if(!new TNTPrimeEvent(tntBlock, TNTPrimeEvent.PrimeReason.EXPLOSION, explosionSource.getSource().getBukkitEntity()).callEvent()) + continue; + // Paper end diff --git a/Spigot-Server-Patches/Add-Velocity-IP-Forwarding-Support.patch b/Spigot-Server-Patches/Add-Velocity-IP-Forwarding-Support.patch index 34707dec93..b24092e868 100644 --- a/Spigot-Server-Patches/Add-Velocity-IP-Forwarding-Support.patch +++ b/Spigot-Server-Patches/Add-Velocity-IP-Forwarding-Support.patch @@ -133,6 +133,14 @@ diff --git a/src/main/java/net/minecraft/server/LoginListener.java b/src/main/ja index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/LoginListener.java +++ b/src/main/java/net/minecraft/server/LoginListener.java +@@ -0,0 +0,0 @@ import org.bukkit.craftbukkit.util.Waitable; + import org.bukkit.event.player.AsyncPlayerPreLoginEvent; + import org.bukkit.event.player.PlayerPreLoginEvent; + // CraftBukkit end ++import io.netty.buffer.Unpooled; // Paper + + public class LoginListener implements PacketLoginInListener { + @@ -0,0 +0,0 @@ public class LoginListener implements PacketLoginInListener { private SecretKey loginKey; private EntityPlayer l; @@ -148,7 +156,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper start - Velocity support + if (com.destroystokyo.paper.PaperConfig.velocitySupport) { + this.velocityLoginMessageId = java.util.concurrent.ThreadLocalRandom.current().nextInt(); -+ PacketLoginOutCustomPayload packet = new PacketLoginOutCustomPayload(this.velocityLoginMessageId, com.destroystokyo.paper.proxy.VelocityProxy.PLAYER_INFO_CHANNEL, new PacketDataSerializer(io.netty.buffer.Unpooled.EMPTY_BUFFER)); ++ PacketLoginOutCustomPayload packet = new PacketLoginOutCustomPayload(this.velocityLoginMessageId, com.destroystokyo.paper.proxy.VelocityProxy.PLAYER_INFO_CHANNEL, new PacketDataSerializer(Unpooled.EMPTY_BUFFER)); + this.networkManager.sendPacket(packet); + return; + } diff --git a/Spigot-Server-Patches/Allow-disabling-armour-stand-ticking.patch b/Spigot-Server-Patches/Allow-disabling-armour-stand-ticking.patch new file mode 100644 index 0000000000..8579fdecc0 --- /dev/null +++ b/Spigot-Server-Patches/Allow-disabling-armour-stand-ticking.patch @@ -0,0 +1,176 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Riley Park +Date: Wed, 15 Aug 2018 01:26:09 -0700 +Subject: [PATCH] Allow disabling armour stand ticking + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -0,0 +0,0 @@ public class PaperWorldConfig { + private void armorStandEntityLookups() { + armorStandEntityLookups = getBoolean("armor-stands-do-collision-entity-lookups", true); + } ++ ++ public boolean armorStandTick = true; ++ private void armorStandTick() { ++ this.armorStandTick = this.getBoolean("armor-stands-tick", this.armorStandTick); ++ log("ArmorStand ticking is " + (this.armorStandTick ? "enabled" : "disabled") + " by default"); ++ } + } +diff --git a/src/main/java/net/minecraft/server/EntityArmorStand.java b/src/main/java/net/minecraft/server/EntityArmorStand.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/EntityArmorStand.java ++++ b/src/main/java/net/minecraft/server/EntityArmorStand.java +@@ -0,0 +0,0 @@ public class EntityArmorStand extends EntityLiving { + public Vector3f leftLegPose; + public Vector3f rightLegPose; + public boolean canMove = true; // Paper ++ // Paper start - Allow ArmorStands not to tick ++ public boolean canTick = true; ++ public boolean canTickSetByAPI = false; ++ private boolean noTickPoseDirty = false; ++ private boolean noTickEquipmentDirty = false; ++ // Paper end + + public EntityArmorStand(EntityTypes entitytypes, World world) { + super(entitytypes, world); +@@ -0,0 +0,0 @@ public class EntityArmorStand extends EntityLiving { + this.rightArmPose = EntityArmorStand.bt; + this.leftLegPose = EntityArmorStand.bu; + this.rightLegPose = EntityArmorStand.bv; ++ if (world != null) this.canTick = world.paperConfig.armorStandTick; // Paper - armour stand ticking + this.G = 0.0F; + } + +@@ -0,0 +0,0 @@ public class EntityArmorStand extends EntityLiving { + this.armorItems.set(enumitemslot.b(), itemstack); + } + ++ this.noTickEquipmentDirty = true; // Paper - Allow equipment to be updated even when tick disabled + } + + @Override +@@ -0,0 +0,0 @@ public class EntityArmorStand extends EntityLiving { + } + + nbttagcompound.set("Pose", this.B()); ++ if (this.canTickSetByAPI) nbttagcompound.setBoolean("Paper.CanTickOverride", this.canTick); // Paper - persist no tick setting + } + + @Override +@@ -0,0 +0,0 @@ public class EntityArmorStand extends EntityLiving { + this.setBasePlate(nbttagcompound.getBoolean("NoBasePlate")); + this.setMarker(nbttagcompound.getBoolean("Marker")); + this.noclip = !this.A(); ++ // Paper start - persist no tick ++ if (nbttagcompound.hasKey("Paper.CanTickOverride")) { ++ this.canTick = nbttagcompound.getBoolean("Paper.CanTickOverride"); ++ this.canTickSetByAPI = true; ++ } ++ // Paper end + NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Pose"); + + this.g(nbttagcompound1); +@@ -0,0 +0,0 @@ public class EntityArmorStand extends EntityLiving { + + @Override + public void tick() { ++ // Paper start ++ if (!this.canTick) { ++ if (this.noTickPoseDirty) { ++ this.noTickPoseDirty = false; ++ this.updatePose(); ++ } ++ ++ if (this.noTickEquipmentDirty) { ++ this.noTickEquipmentDirty = false; ++ this.updateEntityEquipment(); ++ } ++ ++ return; ++ } ++ // Paper end ++ + super.tick(); ++ // Paper start - Split into separate method ++ updatePose(); ++ } ++ ++ public void updatePose() { ++ // Paper end + Vector3f vector3f = (Vector3f) this.datawatcher.get(EntityArmorStand.c); + + if (!this.headPose.equals(vector3f)) { +@@ -0,0 +0,0 @@ public class EntityArmorStand extends EntityLiving { + public void setHeadPose(Vector3f vector3f) { + this.headPose = vector3f; + this.datawatcher.set(EntityArmorStand.c, vector3f); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setBodyPose(Vector3f vector3f) { + this.bodyPose = vector3f; + this.datawatcher.set(EntityArmorStand.d, vector3f); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setLeftArmPose(Vector3f vector3f) { + this.leftArmPose = vector3f; + this.datawatcher.set(EntityArmorStand.e, vector3f); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setRightArmPose(Vector3f vector3f) { + this.rightArmPose = vector3f; + this.datawatcher.set(EntityArmorStand.f, vector3f); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setLeftLegPose(Vector3f vector3f) { + this.leftLegPose = vector3f; + this.datawatcher.set(EntityArmorStand.g, vector3f); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setRightLegPose(Vector3f vector3f) { + this.rightLegPose = vector3f; + this.datawatcher.set(EntityArmorStand.bo, vector3f); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public Vector3f r() { +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -0,0 +0,0 @@ public abstract class EntityLiving extends Entity { + } + } + ++ public void updateEntityEquipment() { q(); }; // Paper + private void q() { + Map map = this.r(); + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +@@ -0,0 +0,0 @@ public class CraftArmorStand extends CraftLivingEntity implements ArmorStand { + public boolean isSlotDisabled(org.bukkit.inventory.EquipmentSlot slot) { + return getHandle().isSlotDisabled(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)); + } ++ ++ @Override ++ public boolean canTick() { ++ return this.getHandle().canTick; ++ } ++ ++ @Override ++ public void setCanTick(final boolean tick) { ++ this.getHandle().canTick = tick; ++ this.getHandle().canTickSetByAPI = true; ++ } + // Paper end + } diff --git a/Spigot-Server-Patches/Asynchronous-chunk-IO-and-loading.patch b/Spigot-Server-Patches/Asynchronous-chunk-IO-and-loading.patch index 59a8abc786..b4bb915da0 100644 --- a/Spigot-Server-Patches/Asynchronous-chunk-IO-and-loading.patch +++ b/Spigot-Server-Patches/Asynchronous-chunk-IO-and-loading.patch @@ -140,7 +140,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public final Timing chunkUnloadDataSave; + public WorldTimingsHandler(World server) { - String name = server.getWorld().getName() +" - "; + String name = ((WorldDataServer) server.getWorldData()).getName() + " - "; @@ -0,0 +0,0 @@ public class WorldTimingsHandler { @@ -1574,7 +1574,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // apply fixes + + try { -+ chunkData.chunkData = chunkManager.getChunkData(this.world.getWorldProvider().getDimensionManager(), ++ chunkData.chunkData = chunkManager.getChunkData(this.world.getTypeKey(), + chunkManager.getWorldPersistentDataSupplier(), chunkData.chunkData, chunkPos, this.world); // clone data for safety, file IO thread does not clone + } catch (final Throwable ex) { + PaperFileIOThread.LOGGER.error("Could not apply datafixers for chunk task: " + this.toString(), ex); @@ -1973,7 +1973,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + for (int i = 0; i < threads; ++i) { + this.workers[i] = new QueueExecutorThread<>(this.queue, (long)0.10e6); //0.1ms -+ this.workers[i].setName("Async chunk loader thread #" + i + " for world: " + world.getWorldData().getName()); ++ this.workers[i].setName("Async chunk loader thread #" + i + " for world: " + world.getWorld().getName()); + this.workers[i].setPriority(Thread.NORM_PRIORITY - 1); + this.workers[i].setUncaughtExceptionHandler((final Thread thread, final Throwable throwable) -> { + PaperFileIOThread.LOGGER.fatal("Thread '" + thread.getName() + "' threw an uncaught exception!", throwable); diff --git a/Spigot-Server-Patches/Configurable-Player-Collision.patch b/Spigot-Server-Patches/Configurable-Player-Collision.patch index 1c79fceb5e..d385b2cb8f 100644 --- a/Spigot-Server-Patches/Configurable-Player-Collision.patch +++ b/Spigot-Server-Patches/Configurable-Player-Collision.patch @@ -73,7 +73,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 entityplayer.syncInventory(); + // Paper start - Add to collideRule team if needed -+ final Scoreboard scoreboard = this.getServer().getWorldServer(DimensionManager.OVERWORLD).getScoreboard(); ++ final Scoreboard scoreboard = this.getServer().getWorldServer(World.OVERWORLD).getScoreboard(); + final ScoreboardTeam collideRuleTeam = scoreboard.getTeam(collideRuleTeamName); + if (this.collideRuleTeamName != null && collideRuleTeam != null && entityplayer.getScoreboardTeam() == null) { + scoreboard.addPlayerToTeam(entityplayer.getName(), collideRuleTeam); @@ -88,7 +88,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper start - Remove from collideRule team if needed + if (this.collideRuleTeamName != null) { -+ final Scoreboard scoreBoard = this.server.getWorldServer(DimensionManager.OVERWORLD).getScoreboard(); ++ final Scoreboard scoreBoard = this.server.getWorldServer(World.OVERWORLD).getScoreboard(); + final ScoreboardTeam team = scoreBoard.getTeam(this.collideRuleTeamName); + if (entityplayer.getScoreboardTeam() == team && team != null) { + scoreBoard.removePlayerFromTeam(entityplayer.getName(), team); @@ -105,7 +105,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper start - Remove collideRule team if it exists + if (this.collideRuleTeamName != null) { -+ final Scoreboard scoreboard = this.getServer().getWorldServer(DimensionManager.OVERWORLD).getScoreboard(); ++ final Scoreboard scoreboard = this.getServer().getWorldServer(World.OVERWORLD).getScoreboard(); + final ScoreboardTeam team = scoreboard.getTeam(this.collideRuleTeamName); + if (team != null) scoreboard.removeTeam(team); + } diff --git a/Spigot-Server-Patches/Configurable-speed-for-water-flowing-over-lava.patch b/Spigot-Server-Patches/Configurable-speed-for-water-flowing-over-lava.patch index a77bf072f3..26449fed76 100644 --- a/Spigot-Server-Patches/Configurable-speed-for-water-flowing-over-lava.patch +++ b/Spigot-Server-Patches/Configurable-speed-for-water-flowing-over-lava.patch @@ -9,8 +9,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java @@ -0,0 +0,0 @@ public class PaperWorldConfig { - private void armorStandEntityLookups() { - armorStandEntityLookups = getBoolean("armor-stands-do-collision-entity-lookups", true); + this.armorStandTick = this.getBoolean("armor-stands-tick", this.armorStandTick); + log("ArmorStand ticking is " + (this.armorStandTick ? "enabled" : "disabled") + " by default"); } + + public int waterOverLavaFlowSpeed; diff --git a/Spigot-Server-Patches/Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch b/Spigot-Server-Patches/Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch index 74b6d19d14..94f40b824d 100644 --- a/Spigot-Server-Patches/Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch +++ b/Spigot-Server-Patches/Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch @@ -95,8 +95,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper start + } -+ public void onPlayerJoinFinish(EntityPlayer entityplayer, WorldServer worldserver, String s1) { ++ public void onPlayerJoinFinish(EntityPlayer entityplayer, WorldServer worldserver1, String s1) { + // Paper end entityplayer.syncInventory(); // Paper start - Add to collideRule team if needed - final Scoreboard scoreboard = this.getServer().getWorldServer(DimensionManager.OVERWORLD).getScoreboard(); + final Scoreboard scoreboard = this.getServer().getWorldServer(World.OVERWORLD).getScoreboard(); diff --git a/Spigot-Server-Patches/Generator-Settings.patch b/Spigot-Server-Patches/Generator-Settings.patch index f4cda5030d..9a058217ab 100644 --- a/Spigot-Server-Patches/Generator-Settings.patch +++ b/Spigot-Server-Patches/Generator-Settings.patch @@ -9,8 +9,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java @@ -0,0 +0,0 @@ public class PaperWorldConfig { - private void perPlayerMobSpawns() { - perPlayerMobSpawns = getBoolean("per-player-mob-spawns", false); + } + } } + + public boolean generateFlatBedrock; diff --git a/Spigot-Server-Patches/Handle-Large-Packets-disconnecting-client.patch b/Spigot-Server-Patches/Handle-Large-Packets-disconnecting-client.patch index 88ea38a3bf..9e1e95d4e9 100644 --- a/Spigot-Server-Patches/Handle-Large-Packets-disconnecting-client.patch +++ b/Spigot-Server-Patches/Handle-Large-Packets-disconnecting-client.patch @@ -10,12 +10,20 @@ diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/j index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/NetworkManager.java +++ b/src/main/java/net/minecraft/server/NetworkManager.java +@@ -0,0 +0,0 @@ import io.netty.channel.epoll.EpollEventLoopGroup; + import io.netty.channel.local.LocalChannel; + import io.netty.channel.local.LocalServerChannel; + import io.netty.channel.nio.NioEventLoopGroup; ++import io.netty.handler.codec.EncoderException; // Paper + import io.netty.handler.timeout.TimeoutException; + import io.netty.util.AttributeKey; + import io.netty.util.concurrent.Future; @@ -0,0 +0,0 @@ public class NetworkManager extends SimpleChannelInboundHandler> { } public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) { + // Paper start -+ if (throwable instanceof io.netty.handler.codec.EncoderException && throwable.getCause() instanceof PacketEncoder.PacketTooLargeException) { ++ if (throwable instanceof EncoderException && throwable.getCause() instanceof PacketEncoder.PacketTooLargeException) { + if (((PacketEncoder.PacketTooLargeException) throwable.getCause()).getPacket().packetTooLarge(this)) { + return; + } else { diff --git a/Spigot-Server-Patches/Implement-Brigadier-Mojang-API.patch b/Spigot-Server-Patches/Implement-Brigadier-Mojang-API.patch index 62fecb8ad1..841305f3a9 100644 --- a/Spigot-Server-Patches/Implement-Brigadier-Mojang-API.patch +++ b/Spigot-Server-Patches/Implement-Brigadier-Mojang-API.patch @@ -82,7 +82,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getPlayer(), suggestions, buffer); + suggestEvent.setCancelled(suggestions.isEmpty()); + if (!suggestEvent.callEvent()) return; -+ this.networkManager.sendPacket(new PacketPlayOutTabComplete(packetplayintabcomplete.b(), (Suggestions) suggestEvent.getSuggestions())); // CraftBukkit - decompile error // Paper ++ this.networkManager.sendPacket(new PacketPlayOutTabComplete(packetplayintabcomplete.b(), (com.mojang.brigadier.suggestion.Suggestions) suggestEvent.getSuggestions())); // CraftBukkit - decompile error // Paper + // Paper end }); }); @@ -92,7 +92,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 builder = builder.createOffset(builder.getInput().lastIndexOf(' ') + 1); completions.forEach(builder::suggest); - player.playerConnection.sendPacket(new PacketPlayOutTabComplete(packetplayintabcomplete.b(), builder.buildFuture().join())); -+ Suggestions suggestions = builder.buildFuture().join(); ++ com.mojang.brigadier.suggestion.Suggestions suggestions = builder.buildFuture().join(); + com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getPlayer(), suggestions, buffer); + suggestEvent.setCancelled(suggestions.isEmpty()); + if (!suggestEvent.callEvent()) return; diff --git a/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch b/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch index ee4cbb798c..ff59d05fa0 100644 --- a/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch +++ b/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch @@ -587,8 +587,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private int lastFoodSent = -99999999; private boolean lastSentSaturationZero = true; @@ -0,0 +0,0 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + this.canPickUpLoot = true; this.maxHealthCache = this.getMaxHealth(); - this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper } + // Paper start + public BlockPosition getPointInFront(double inFront) { @@ -1069,8 +1069,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + // Paper end - public void updatePlayerMobTypeMap(Entity entity) { - if (!this.world.paperConfig.perPlayerMobSpawns) { + private static double getDistanceSquaredFromChunk(ChunkCoordIntPair chunkPos, Entity entity) { return a(chunkPos, entity); } // Paper - OBFHELPER + private static double a(ChunkCoordIntPair chunkcoordintpair, Entity entity) { @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { List>> list = Lists.newArrayList(); int j = chunkcoordintpair.x; diff --git a/Spigot-Server-Patches/Implement-Mob-Goal-API.patch b/Spigot-Server-Patches/Implement-Mob-Goal-API.patch index 95d0b0363c..92904a7f0e 100644 --- a/Spigot-Server-Patches/Implement-Mob-Goal-API.patch +++ b/Spigot-Server-Patches/Implement-Mob-Goal-API.patch @@ -251,6 +251,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + bukkitMap.put(EntityZombie.class, Zombie.class); + bukkitMap.put(EntityZombieHusk.class, Husk.class); + bukkitMap.put(EntityZombieVillager.class, ZombieVillager.class); ++ bukkitMap.put(EntityHoglin.class, Hoglin.class); ++ bukkitMap.put(EntityPiglin.class, Piglin.class); ++ bukkitMap.put(EntityStrider.class, Strider.class); ++ bukkitMap.put(EntityZoglin.class, Zoglin.class); + } + + public static String getUsableName(Class clazz) { @@ -857,10 +861,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ +package com.destroystokyo.paper.entity.ai; + -+import com.destroystokyo.paper.entity.ai.GoalKey; -+import com.destroystokyo.paper.entity.ai.MobGoalHelper; -+import com.destroystokyo.paper.entity.ai.VanillaGoal; -+ +import net.minecraft.server.EntityInsentient; +import net.minecraft.server.PathfinderGoal; + @@ -882,11 +882,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + @Test + public void testKeys() { ++ List> deprecated = new ArrayList<>(); + List> keys = new ArrayList<>(); + for (Field field : VanillaGoal.class.getFields()) { + if (field.getType().equals(GoalKey.class)) { + try { -+ keys.add((GoalKey) field.get(null)); ++ GoalKey goalKey = (GoalKey) field.get(null); ++ if (field.getAnnotation(Deprecated.class) != null) { ++ deprecated.add(goalKey); ++ } else { ++ keys.add(goalKey); ++ } + } catch (IllegalAccessException e) { + System.out.println("Skipping " + field.getName() + ": " + e.getMessage()); + } @@ -925,6 +931,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + shouldFail = true; + } + ++ if (deprecated.size() != 0) { ++ System.out.println("Deprecated (might want to remove them at some point): "); ++ deprecated.forEach(System.out::println); ++ } ++ + if (shouldFail) Assert.fail("See above"); + } + @@ -939,12 +950,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + classes = scanResult.getSubclasses("net.minecraft.server.EntityInsentient").loadClasses(); + } + -+ boolean shouldFail =false; ++ boolean shouldFail = false; + for (Class nmsClass : classes) { + Class bukkitClass = MobGoalHelper.toBukkitClass((Class) nmsClass); -+ if(bukkitClass == null) { ++ if (bukkitClass == null) { + shouldFail = true; -+ System.out.println("Missing bukkitMap.put(" + nmsClass.getSimpleName() + ".class, "+nmsClass.getSimpleName().replace("Entity","")+".class);"); ++ System.out.println("Missing bukkitMap.put(" + nmsClass.getSimpleName() + ".class, " + nmsClass.getSimpleName().replace("Entity", "") + ".class);"); + } + } + diff --git a/Spigot-Server-Patches/MC-145656-Fix-Follow-Range-Initial-Target.patch b/Spigot-Server-Patches/MC-145656-Fix-Follow-Range-Initial-Target.patch index 4d9bdd59c1..a3f188ac35 100644 --- a/Spigot-Server-Patches/MC-145656-Fix-Follow-Range-Initial-Target.patch +++ b/Spigot-Server-Patches/MC-145656-Fix-Follow-Range-Initial-Target.patch @@ -57,7 +57,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + private double getFollowRange(EntityLiving entityliving) { -+ AttributeInstance attributeinstance = entityliving.getAttributeInstance(GenericAttributes.FOLLOW_RANGE); ++ AttributeModifiable attributeinstance = entityliving.getAttributeInstance(GenericAttributes.FOLLOW_RANGE); + return attributeinstance == null ? 16.0D : attributeinstance.getValue(); + } + // Paper end diff --git a/Spigot-Server-Patches/MC-Dev-fixes.patch b/Spigot-Server-Patches/MC-Dev-fixes.patch index 71f013b968..2c2209ca14 100644 --- a/Spigot-Server-Patches/MC-Dev-fixes.patch +++ b/Spigot-Server-Patches/MC-Dev-fixes.patch @@ -193,6 +193,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 return this.getTemperature() - (f + (float) blockposition.getY() - 64.0F) * 0.05F / 30.0F; } else { +diff --git a/src/main/java/net/minecraft/server/Biomes.java b/src/main/java/net/minecraft/server/Biomes.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/Biomes.java ++++ b/src/main/java/net/minecraft/server/Biomes.java +@@ -0,0 +0,0 @@ public abstract class Biomes { + private static BiomeBase a(int i, String s, BiomeBase biomebase) { + IRegistry.a(IRegistry.BIOME, i, s, biomebase); + if (biomebase.b()) { +- BiomeBase.d.a(biomebase, IRegistry.BIOME.a(IRegistry.BIOME.get(new MinecraftKey(biomebase.l)))); ++ BiomeBase.reg.a(biomebase, IRegistry.BIOME.a(IRegistry.BIOME.get(new MinecraftKey(biomebase.l)))); + } + + return biomebase; diff --git a/src/main/java/net/minecraft/server/BlockStateEnum.java b/src/main/java/net/minecraft/server/BlockStateEnum.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/BlockStateEnum.java @@ -403,6 +416,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 this.b(); this.g.set(false); })); +diff --git a/src/main/java/net/minecraft/server/LootEntryAbstract.java b/src/main/java/net/minecraft/server/LootEntryAbstract.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/LootEntryAbstract.java ++++ b/src/main/java/net/minecraft/server/LootEntryAbstract.java +@@ -0,0 +0,0 @@ public abstract class LootEntryAbstract implements LootEntryChildren { + + // CraftBukkit start + @Override +- public final void a(JsonObject jsonobject, T t0, JsonSerializationContext jsonserializationcontext) { ++ public void a(JsonObject jsonobject, T t0, JsonSerializationContext jsonserializationcontext) { // Paper - remove final + if (!org.apache.commons.lang3.ArrayUtils.isEmpty(t0.d)) { + jsonobject.add("conditions", jsonserializationcontext.serialize(t0.d)); + } diff --git a/src/main/java/net/minecraft/server/LootItemFunctionExplorationMap.java b/src/main/java/net/minecraft/server/LootItemFunctionExplorationMap.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/LootItemFunctionExplorationMap.java @@ -473,6 +499,27 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 default String asString() { return this.toString(); +diff --git a/src/main/java/net/minecraft/server/NBTCompressedStreamTools.java b/src/main/java/net/minecraft/server/NBTCompressedStreamTools.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/NBTCompressedStreamTools.java ++++ b/src/main/java/net/minecraft/server/NBTCompressedStreamTools.java +@@ -0,0 +0,0 @@ import java.io.InputStream; + import java.io.OutputStream; + import java.util.zip.GZIPInputStream; + import java.util.zip.GZIPOutputStream; ++import io.netty.buffer.ByteBufInputStream; // Paper + + public class NBTCompressedStreamTools { + +@@ -0,0 +0,0 @@ public class NBTCompressedStreamTools { + + public static NBTTagCompound a(DataInput datainput, NBTReadLimiter nbtreadlimiter) throws IOException { + // Spigot start +- if ( datainput instanceof io.netty.buffer.ByteBufInputStream ) ++ if ( datainput instanceof ByteBufInputStream) // Paper + { + datainput = new DataInputStream(new org.spigotmc.LimitStream((InputStream) datainput, nbtreadlimiter)); + } diff --git a/src/main/java/net/minecraft/server/NBTTagList.java b/src/main/java/net/minecraft/server/NBTTagList.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/NBTTagList.java @@ -549,6 +596,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 this.e = 0; this.f = 0; +diff --git a/src/main/java/net/minecraft/server/StructureGenerator.java b/src/main/java/net/minecraft/server/StructureGenerator.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/StructureGenerator.java ++++ b/src/main/java/net/minecraft/server/StructureGenerator.java +@@ -0,0 +0,0 @@ public abstract class StructureGenerator + private static > F a(String s, F f0, WorldGenStage.Decoration worldgenstage_decoration) { + StructureGenerator.a.put(s.toLowerCase(Locale.ROOT), f0); + StructureGenerator.u.put(f0, worldgenstage_decoration); +- return (StructureGenerator) IRegistry.a(IRegistry.STRUCTURE_FEATURE, s.toLowerCase(Locale.ROOT), (Object) f0); ++ return (F) IRegistry.>a(IRegistry.STRUCTURE_FEATURE, s.toLowerCase(Locale.ROOT), f0); // Paper - decomp fix + } + + public StructureGenerator(Codec codec) { diff --git a/src/main/java/net/minecraft/server/SystemUtils.java b/src/main/java/net/minecraft/server/SystemUtils.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/SystemUtils.java diff --git a/Spigot-Server-Patches/No-Tick-view-distance-implementation.patch b/Spigot-Server-Patches/No-Tick-view-distance-implementation.patch index f3b2891ba8..b497bd6820 100644 --- a/Spigot-Server-Patches/No-Tick-view-distance-implementation.patch +++ b/Spigot-Server-Patches/No-Tick-view-distance-implementation.patch @@ -353,7 +353,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper end - no-tick view distance } - public void updatePlayerMobTypeMap(Entity entity) { + private static double getDistanceSquaredFromChunk(ChunkCoordIntPair chunkPos, Entity entity) { return a(chunkPos, entity); } // Paper - OBFHELPER @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { completablefuture1.thenAcceptAsync((either) -> { either.mapLeft((chunk) -> { @@ -373,10 +373,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 return completablefuture1; } @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } } - protected void setViewDistance(int i) { +- protected void setViewDistance(int i) { - int j = MathHelper.clamp(i + 1, 3, 33); ++ public void setViewDistance(int i) { // Paper - public + int j = MathHelper.clamp(i + 1, 3, 33); // Paper - diff on change, these make the lower view distance limit 2 and the upper 32 if (j != this.viewDistance) { diff --git a/Spigot-Server-Patches/Optimize-Network-Manager-and-add-advanced-packet-sup.patch b/Spigot-Server-Patches/Optimize-Network-Manager-and-add-advanced-packet-sup.patch index 28eda373bd..135054489f 100644 --- a/Spigot-Server-Patches/Optimize-Network-Manager-and-add-advanced-packet-sup.patch +++ b/Spigot-Server-Patches/Optimize-Network-Manager-and-add-advanced-packet-sup.patch @@ -361,6 +361,14 @@ diff --git a/src/main/java/net/minecraft/server/ServerConnection.java b/src/main index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/ServerConnection.java +++ b/src/main/java/net/minecraft/server/ServerConnection.java +@@ -0,0 +0,0 @@ import io.netty.channel.epoll.EpollServerSocketChannel; + import io.netty.channel.nio.NioEventLoopGroup; + import io.netty.channel.socket.ServerSocketChannel; + import io.netty.channel.socket.nio.NioServerSocketChannel; ++import io.netty.handler.flush.FlushConsolidationHandler; // Paper + import io.netty.handler.timeout.ReadTimeoutHandler; + import java.io.IOException; + import java.net.InetAddress; @@ -0,0 +0,0 @@ public class ServerConnection { private final List connectedChannels = Collections.synchronizedList(Lists.newArrayList()); // Paper start - prevent blocking on adding a new network manager while the server is ticking @@ -378,7 +386,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 ; } -+ if (!disableFlushConsolidation) channel.pipeline().addFirst(new io.netty.handler.flush.FlushConsolidationHandler()); // Paper ++ if (!disableFlushConsolidation) channel.pipeline().addFirst(new FlushConsolidationHandler()); // Paper channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30)).addLast("legacy_query", new LegacyPingHandler(ServerConnection.this)).addLast("splitter", new PacketSplitter()).addLast("decoder", new PacketDecoder(EnumProtocolDirection.SERVERBOUND)).addLast("prepender", new PacketPrepender()).addLast("encoder", new PacketEncoder(EnumProtocolDirection.CLIENTBOUND)); NetworkManager networkmanager = new NetworkManager(EnumProtocolDirection.SERVERBOUND); diff --git a/Spigot-Server-Patches/Optimize-RegistryMaterials.patch b/Spigot-Server-Patches/Optimize-RegistryMaterials.patch index 631d0e862f..7ff21e016b 100644 --- a/Spigot-Server-Patches/Optimize-RegistryMaterials.patch +++ b/Spigot-Server-Patches/Optimize-RegistryMaterials.patch @@ -42,3 +42,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 builder.add(Pair.of(registrymaterials.c(t0).get(), t0)); } +@@ -0,0 +0,0 @@ public class RegistryMaterials extends IRegistryWritable { + + registrymaterials.bb.entrySet().stream().filter((entry) -> { + return registrymaterials.c((ResourceKey) entry.getKey()); +- }).forEach(com_google_common_collect_immutablemap_builder::put); ++ }).forEach(entry1 -> com_google_common_collect_immutablemap_builder.put((java.util.Map.Entry,? extends T>) entry1)); // Paper - compiler fix (expand method reference + add cast) + return com_google_common_collect_immutablemap_builder.build(); + }); + } diff --git a/Spigot-Server-Patches/Optimize-TileEntity-Ticking.patch b/Spigot-Server-Patches/Optimize-TileEntity-Ticking.patch index 28dcd268e7..6174e02618 100644 --- a/Spigot-Server-Patches/Optimize-TileEntity-Ticking.patch +++ b/Spigot-Server-Patches/Optimize-TileEntity-Ticking.patch @@ -4,6 +4,43 @@ Date: Sun, 8 Mar 2015 22:55:25 -0600 Subject: [PATCH] Optimize TileEntity Ticking +diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/co/aikar/timings/TimingsExport.java ++++ b/src/main/java/co/aikar/timings/TimingsExport.java +@@ -0,0 +0,0 @@ public class TimingsExport extends Thread { + pair("end", System.currentTimeMillis() / 1000), + pair("online-mode", Bukkit.getServer().getOnlineMode()), + pair("sampletime", (System.currentTimeMillis() - TimingsManager.timingStart) / 1000), +- pair("datapacks", toArrayMapper(MinecraftServer.getServer().getResourcePackRepository().d(), pack -> { ++ pair("datapacks", toArrayMapper(MinecraftServer.getServer().getResourcePackRepository().e(), pack -> { + // Don't feel like obf helper'ing these, non fatal if its temp missed. + return ChatColor.stripColor(CraftChatMessage.fromComponent(pack.a(true))); + })) +@@ -0,0 +0,0 @@ public class TimingsExport extends Thread { + ); + + parent.put("worlds", toObjectMapper(MinecraftServer.getServer().getWorlds(), world -> { +- if (world.getWorldData().getName().equals("worldeditregentempworld")) return null; +- return pair(world.getWorldData().getName(), createObject( ++ if (world.getWorld().getName().equals("worldeditregentempworld")) return null; ++ return pair(world.getWorld().getName(), createObject( + pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> { + return pair(rule, world.getWorld().getGameRuleValue(rule)); + })), +diff --git a/src/main/java/net/minecraft/server/BlockChest.java b/src/main/java/net/minecraft/server/BlockChest.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/BlockChest.java ++++ b/src/main/java/net/minecraft/server/BlockChest.java +@@ -0,0 +0,0 @@ public class BlockChest extends BlockChestAbstract implements I + + public static final BlockStateDirection FACING = BlockFacingHorizontal.FACING; + public static final BlockStateEnum c = BlockProperties.aF; +- public static final BlockStateBoolean d = BlockProperties.C; ++ public static final BlockStateBoolean d = BlockProperties.C; public static final BlockStateBoolean waterlogged() { return d; } // Paper OBFHELPER + protected static final VoxelShape e = Block.a(1.0D, 0.0D, 0.0D, 15.0D, 14.0D, 15.0D); + protected static final VoxelShape f = Block.a(1.0D, 0.0D, 1.0D, 15.0D, 14.0D, 16.0D); + protected static final VoxelShape g = Block.a(0.0D, 0.0D, 1.0D, 15.0D, 14.0D, 15.0D); diff --git a/src/main/java/net/minecraft/server/TileEntityChest.java b/src/main/java/net/minecraft/server/TileEntityChest.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/TileEntityChest.java @@ -70,7 +107,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } private void a(SoundEffect soundeffect) { -+ if (!this.getBlock().hasProperty(BlockChest.c)) { return; } // Paper - this can be delayed, double check exists - Fixes GH-2074 ++ if (!this.getBlock().get(BlockChest.waterlogged())) { return; } // Paper - this can be delayed, double check exists - Fixes GH-2074 BlockPropertyChestType blockpropertychesttype = (BlockPropertyChestType) this.getBlock().get(BlockChest.c); if (blockpropertychesttype != BlockPropertyChestType.LEFT) { diff --git a/Spigot-Server-Patches/Optimize-isOutsideRange-to-use-distance-maps.patch b/Spigot-Server-Patches/Optimize-isOutsideRange-to-use-distance-maps.patch index a536028223..bcb3df5f67 100644 --- a/Spigot-Server-Patches/Optimize-isOutsideRange-to-use-distance-maps.patch +++ b/Spigot-Server-Patches/Optimize-isOutsideRange-to-use-distance-maps.patch @@ -290,11 +290,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper end - optimise PlayerChunkMap#isOutsideRange } - public void updatePlayerMobTypeMap(Entity entity) { -@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { - return entityPlayer.mobCounts[enumCreatureType.ordinal()]; - } - + private static double getDistanceSquaredFromChunk(ChunkCoordIntPair chunkPos, Entity entity) { return a(chunkPos, entity); } // Paper - OBFHELPER private static double a(ChunkCoordIntPair chunkcoordintpair, Entity entity) { double d0 = (double) (chunkcoordintpair.x * 16 + 8); diff --git a/Spigot-Server-Patches/Paper-config-files.patch b/Spigot-Server-Patches/Paper-config-files.patch index 6cb7313e90..60b387e6d3 100644 --- a/Spigot-Server-Patches/Paper-config-files.patch +++ b/Spigot-Server-Patches/Paper-config-files.patch @@ -564,7 +564,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 // Spigot end + // Paper start + try { -+ com.destroystokyo.paper.PaperConfig.init((File) options.valueOf("paper-settings")); ++ com.destroystokyo.paper.PaperConfig.init((java.io.File) options.valueOf("paper-settings")); + } catch (Exception e) { + DedicatedServer.LOGGER.error("Unable to load server configuration", e); + return false; diff --git a/Spigot-Server-Patches/RangedEntity-API.patch b/Spigot-Server-Patches/RangedEntity-API.patch index ac299cd0e5..4b16cf4d97 100644 --- a/Spigot-Server-Patches/RangedEntity-API.patch +++ b/Spigot-Server-Patches/RangedEntity-API.patch @@ -43,7 +43,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + void a(EntityLiving entityliving, float f); default void rangedAttack(EntityLiving entityliving, float f) { a(entityliving, f); } // Paper - OBFHELPER + + // - see EntitySkeletonAbstract melee goal -+ void q(boolean flag); default void setChargingAttack(boolean charging) { q(charging); }; // Paper ++ void setAggressive(boolean flag); default void setChargingAttack(boolean charging) { setAggressive(charging); }; // Paper } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 diff --git a/Spigot-Server-Patches/Remove-FishingHook-reference-on-Craft-Entity-removal.patch b/Spigot-Server-Patches/Remove-FishingHook-reference-on-Craft-Entity-removal.patch index b19c38731a..777f149c91 100644 --- a/Spigot-Server-Patches/Remove-FishingHook-reference-on-Craft-Entity-removal.patch +++ b/Spigot-Server-Patches/Remove-FishingHook-reference-on-Craft-Entity-removal.patch @@ -17,10 +17,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + @Override + public void remove() { + super.remove(); -+ if (getHandle().owner != null) { -+ getHandle().owner.hookedFish = null; ++ if (getHandle().getOwner() != null) { ++ getHandle().getOwner().hookedFish = null; + } + } + // Paper end -+ ++ } diff --git a/Spigot-Server-Patches/String-based-Action-Bar-API.patch b/Spigot-Server-Patches/String-based-Action-Bar-API.patch index 66111022b2..20a8e6b311 100644 --- a/Spigot-Server-Patches/String-based-Action-Bar-API.patch +++ b/Spigot-Server-Patches/String-based-Action-Bar-API.patch @@ -4,6 +4,19 @@ Date: Tue, 27 Dec 2016 15:02:42 -0500 Subject: [PATCH] String based Action Bar API +diff --git a/src/main/java/net/minecraft/server/SystemUtils.java b/src/main/java/net/minecraft/server/SystemUtils.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/SystemUtils.java ++++ b/src/main/java/net/minecraft/server/SystemUtils.java +@@ -0,0 +0,0 @@ public class SystemUtils { + private static final ExecutorService e = a("Main"); + private static final ExecutorService f = n(); + public static LongSupplier a = System::nanoTime; +- public static final UUID b = new UUID(0L, 0L); ++ public static final UUID b = new UUID(0L, 0L); public static final UUID getNullUUID() {return b;} // Paper OBFHELPER + private static final Logger LOGGER = LogManager.getLogger(); + + public static Collector, ?, Map> a() { diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -15,7 +28,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + @Override + public void sendActionBar(String message) { + if (getHandle().playerConnection == null || message == null || message.isEmpty()) return; -+ getHandle().playerConnection.sendPacket(new PacketPlayOutChat(new net.minecraft.server.ChatComponentText(message), net.minecraft.server.ChatMessageType.GAME_INFO)); ++ getHandle().playerConnection.sendPacket(new PacketPlayOutChat(new net.minecraft.server.ChatComponentText(message), net.minecraft.server.ChatMessageType.GAME_INFO, SystemUtils.getNullUUID())); + } + + @Override diff --git a/Spigot-Server-Patches/Timings-v2.patch b/Spigot-Server-Patches/Timings-v2.patch index 9eaceba285..4a894b8f4b 100644 --- a/Spigot-Server-Patches/Timings-v2.patch +++ b/Spigot-Server-Patches/Timings-v2.patch @@ -551,6 +551,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +package co.aikar.timings; + +import net.minecraft.server.World; ++import net.minecraft.server.WorldDataServer; +import net.minecraft.server.WorldServer; + +/** @@ -608,7 +609,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public final Timing miscMobSpawning; + + public WorldTimingsHandler(World server) { -+ String name = server.getWorld().getName() +" - "; ++ String name = ((WorldDataServer) server.getWorldData()).getName() + " - "; + + mobSpawn = Timings.ofSafe(name + "mobSpawn"); + doChunkUnload = Timings.ofSafe(name + "doChunkUnload"); @@ -663,7 +664,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + public static Timing getTickList(WorldServer worldserver, String timingsType) { -+ return Timings.ofSafe(worldserver.getWorld().getName() + " - Scheduled " + timingsType); ++ return Timings.ofSafe(((WorldDataServer) worldserver.getWorldData()).getName() + " - Scheduled " + timingsType); + } +} diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java diff --git a/Spigot-Server-Patches/Use-distance-map-to-optimise-entity-tracker.patch b/Spigot-Server-Patches/Use-distance-map-to-optimise-entity-tracker.patch index 2a7b598adc..66bc7675c8 100644 --- a/Spigot-Server-Patches/Use-distance-map-to-optimise-entity-tracker.patch +++ b/Spigot-Server-Patches/Use-distance-map-to-optimise-entity-tracker.patch @@ -117,9 +117,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 // Paper end @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.l = supplier; this.m = new VillagePlace(new File(this.w, "poi"), datafixer, flag, this.world); // Paper this.setViewDistance(i); - this.playerMobDistanceMap = this.world.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper + // Paper start - use distance map to optimise entity tracker + this.playerEntityTrackerTrackMaps = new com.destroystokyo.paper.util.misc.PlayerAreaMap[TRACKING_RANGE_TYPES.length]; + this.entityTrackerTrackRanges = new int[TRACKING_RANGE_TYPES.length]; @@ -161,7 +161,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper end - use distance map to optimise entity tracker } - public void updatePlayerMobTypeMap(Entity entity) { + private static double a(ChunkCoordIntPair chunkcoordintpair, Entity entity) { @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } diff --git a/Spigot-Server-Patches/Villager-Restocks-API.patch b/Spigot-Server-Patches/Villager-Restocks-API.patch index 3bc317aec4..0cbe114363 100644 --- a/Spigot-Server-Patches/Villager-Restocks-API.patch +++ b/Spigot-Server-Patches/Villager-Restocks-API.patch @@ -9,19 +9,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/server/EntityVillager.java +++ b/src/main/java/net/minecraft/server/EntityVillager.java @@ -0,0 +0,0 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation - - return optional.isPresent() ? i - (Long) optional.get() < 24000L : false; - } -+ -+ // Paper start -+ public int getRestocksToday(){ -+ return this.bL; -+ } -+ public void setRestocksToday(int restocksToday){ -+ this.bL = restocksToday; -+ } -+ // Paper end - } + private long bH; + private int bI; + private long bJ; +- private int bK; ++ private int bK; public int getRestocksToday(){ return this.bK; } public void setRestocksToday(int restocksToday){ this.bK = restocksToday; } // Paper OBFHELPER + private long bL; + private boolean bM; + private static final ImmutableList> bN = ImmutableList.of(MemoryModuleType.HOME, MemoryModuleType.JOB_SITE, MemoryModuleType.POTENTIAL_JOB_SITE, MemoryModuleType.MEETING_POINT, MemoryModuleType.MOBS, MemoryModuleType.VISIBLE_MOBS, MemoryModuleType.VISIBLE_VILLAGER_BABIES, MemoryModuleType.NEAREST_PLAYERS, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_TARGETABLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, MemoryModuleType.WALK_TARGET, new MemoryModuleType[]{MemoryModuleType.LOOK_TARGET, MemoryModuleType.INTERACTION_TARGET, MemoryModuleType.BREED_TARGET, MemoryModuleType.PATH, MemoryModuleType.INTERACTABLE_DOORS, MemoryModuleType.OPENED_DOORS, MemoryModuleType.NEAREST_BED, MemoryModuleType.HURT_BY, MemoryModuleType.HURT_BY_ENTITY, MemoryModuleType.NEAREST_HOSTILE, MemoryModuleType.SECONDARY_JOB_SITE, MemoryModuleType.HIDING_PLACE, MemoryModuleType.HEARD_BELL_TIME, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.LAST_SLEPT, MemoryModuleType.LAST_WOKEN, MemoryModuleType.LAST_WORKED_AT_POI, MemoryModuleType.GOLEM_LAST_SEEN_TIME}); diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java diff --git a/Spigot-Server-Patches/Workaround-for-Client-Lag-Spikes-MC-162253.patch b/Spigot-Server-Patches/Workaround-for-Client-Lag-Spikes-MC-162253.patch index d522ba9bda..5843536c77 100644 --- a/Spigot-Server-Patches/Workaround-for-Client-Lag-Spikes-MC-162253.patch +++ b/Spigot-Server-Patches/Workaround-for-Client-Lag-Spikes-MC-162253.patch @@ -40,15 +40,6 @@ diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/j index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java -@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { - player.needsChunkCenterUpdate = false; - player.playerConnection.sendPacket(new PacketPlayOutViewCentre(currPosX, currPosZ)); - } -- PlayerChunkMap.this.sendChunk(player, new ChunkCoordIntPair(rangeX, rangeZ), new Packet[2], false, true); // unloaded, loaded -+ PlayerChunkMap.this.sendChunk(player, new ChunkCoordIntPair(rangeX, rangeZ), new Packet[10], false, true); // unloaded, loaded - }, - (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, - com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } diff --git a/Spigot-Server-Patches/implement-optional-per-player-mob-spawns.patch b/Spigot-Server-Patches/implement-optional-per-player-mob-spawns.patch deleted file mode 100644 index f3e456e258..0000000000 --- a/Spigot-Server-Patches/implement-optional-per-player-mob-spawns.patch +++ /dev/null @@ -1,825 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kickash32 -Date: Mon, 19 Aug 2019 01:27:58 +0500 -Subject: [PATCH] implement optional per player mob spawns - - -diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java -+++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java -@@ -0,0 +0,0 @@ public class WorldTimingsHandler { - - - public final Timing miscMobSpawning; -+ public final Timing playerMobDistanceMapUpdate; - - public final Timing poiUnload; - public final Timing chunkUnload; -@@ -0,0 +0,0 @@ public class WorldTimingsHandler { - - - miscMobSpawning = Timings.ofSafe(name + "Mob spawning - Misc"); -+ playerMobDistanceMapUpdate = Timings.ofSafe(name + "Per Player Mob Spawning - Distance Map Update"); - - poiUnload = Timings.ofSafe(name + "Chunk unload - POI"); - chunkUnload = Timings.ofSafe(name + "Chunk unload - Chunk"); -diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java -+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java -@@ -0,0 +0,0 @@ public class PaperWorldConfig { - } - } - } -+ -+ public boolean perPlayerMobSpawns = false; -+ private void perPlayerMobSpawns() { -+ perPlayerMobSpawns = getBoolean("per-player-mob-spawns", false); -+ } - } -diff --git a/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java b/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java -@@ -0,0 +0,0 @@ -+package com.destroystokyo.paper.util; -+ -+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; -+import net.minecraft.server.ChunkCoordIntPair; -+import net.minecraft.server.EntityPlayer; -+import net.minecraft.server.SectionPosition; -+import org.spigotmc.AsyncCatcher; -+import java.util.HashMap; -+import java.util.List; -+import java.util.Map; -+import java.util.Set; -+ -+/** @author Spottedleaf */ -+public final class PlayerMobDistanceMap { -+ -+ private static final PooledHashSets.PooledObjectLinkedOpenHashSet EMPTY_SET = new PooledHashSets.PooledObjectLinkedOpenHashSet<>(); -+ -+ private final Map players = new HashMap<>(); -+ // we use linked for better iteration. -+ private final Long2ObjectOpenHashMap> playerMap = new Long2ObjectOpenHashMap<>(32, 0.5f); -+ private int viewDistance; -+ -+ private final PooledHashSets pooledHashSets = new PooledHashSets<>(); -+ -+ public PooledHashSets.PooledObjectLinkedOpenHashSet getPlayersInRange(final ChunkCoordIntPair chunkPos) { -+ return this.getPlayersInRange(chunkPos.x, chunkPos.z); -+ } -+ -+ public PooledHashSets.PooledObjectLinkedOpenHashSet getPlayersInRange(final int chunkX, final int chunkZ) { -+ return this.playerMap.getOrDefault(ChunkCoordIntPair.pair(chunkX, chunkZ), EMPTY_SET); -+ } -+ -+ public void update(final List currentPlayers, final int newViewDistance) { -+ AsyncCatcher.catchOp("Distance map update"); -+ final ObjectLinkedOpenHashSet gone = new ObjectLinkedOpenHashSet<>(this.players.keySet()); -+ -+ final int oldViewDistance = this.viewDistance; -+ this.viewDistance = newViewDistance; -+ -+ for (final EntityPlayer player : currentPlayers) { -+ if (player.isSpectator() || !player.affectsSpawning) { -+ continue; // will be left in 'gone' (or not added at all) -+ } -+ -+ gone.remove(player); -+ -+ final SectionPosition newPosition = player.getPlayerMapSection(); -+ final SectionPosition oldPosition = this.players.put(player, newPosition); -+ -+ if (oldPosition == null) { -+ this.addNewPlayer(player, newPosition, newViewDistance); -+ } else { -+ this.updatePlayer(player, oldPosition, newPosition, oldViewDistance, newViewDistance); -+ } -+ //this.validatePlayer(player, newViewDistance); // debug only -+ } -+ -+ for (final EntityPlayer player : gone) { -+ final SectionPosition oldPosition = this.players.remove(player); -+ if (oldPosition != null) { -+ this.removePlayer(player, oldPosition, oldViewDistance); -+ } -+ } -+ } -+ -+ // expensive op, only for debug -+ private void validatePlayer(final EntityPlayer player, final int viewDistance) { -+ int entiesGot = 0; -+ int expectedEntries = (2 * viewDistance + 1); -+ expectedEntries *= expectedEntries; -+ -+ final SectionPosition currPosition = player.getPlayerMapSection(); -+ -+ final int centerX = currPosition.getX(); -+ final int centerZ = currPosition.getZ(); -+ -+ for (final Long2ObjectLinkedOpenHashMap.Entry> entry : this.playerMap.long2ObjectEntrySet()) { -+ final long key = entry.getLongKey(); -+ final PooledHashSets.PooledObjectLinkedOpenHashSet map = entry.getValue(); -+ -+ if (map.referenceCount == 0) { -+ throw new IllegalStateException("Invalid map"); -+ } -+ -+ if (map.set.contains(player)) { -+ ++entiesGot; -+ -+ final int chunkX = ChunkCoordIntPair.getX(key); -+ final int chunkZ = ChunkCoordIntPair.getZ(key); -+ -+ final int dist = Math.max(Math.abs(chunkX - centerX), Math.abs(chunkZ - centerZ)); -+ -+ if (dist > viewDistance) { -+ throw new IllegalStateException("Expected view distance " + viewDistance + ", got " + dist); -+ } -+ } -+ } -+ -+ if (entiesGot != expectedEntries) { -+ throw new IllegalStateException("Expected " + expectedEntries + ", got " + entiesGot); -+ } -+ } -+ -+ private void addPlayerTo(final EntityPlayer player, final int chunkX, final int chunkZ) { -+ this.playerMap.compute(ChunkCoordIntPair.pair(chunkX, chunkZ), (final Long key, final PooledHashSets.PooledObjectLinkedOpenHashSet players) -> { -+ if (players == null) { -+ return player.cachedSingleMobDistanceMap; -+ } else { -+ return PlayerMobDistanceMap.this.pooledHashSets.findMapWith(players, player); -+ } -+ }); -+ } -+ -+ private void removePlayerFrom(final EntityPlayer player, final int chunkX, final int chunkZ) { -+ this.playerMap.compute(ChunkCoordIntPair.pair(chunkX, chunkZ), (final Long keyInMap, final PooledHashSets.PooledObjectLinkedOpenHashSet players) -> { -+ return PlayerMobDistanceMap.this.pooledHashSets.findMapWithout(players, player); // rets null instead of an empty map -+ }); -+ } -+ -+ private void updatePlayer(final EntityPlayer player, final SectionPosition oldPosition, final SectionPosition newPosition, final int oldViewDistance, final int newViewDistance) { -+ final int toX = newPosition.getX(); -+ final int toZ = newPosition.getZ(); -+ final int fromX = oldPosition.getX(); -+ final int fromZ = oldPosition.getZ(); -+ -+ final int dx = toX - fromX; -+ final int dz = toZ - fromZ; -+ -+ final int totalX = Math.abs(fromX - toX); -+ final int totalZ = Math.abs(fromZ - toZ); -+ -+ if (Math.max(totalX, totalZ) > (2 * oldViewDistance)) { -+ // teleported? -+ this.removePlayer(player, oldPosition, oldViewDistance); -+ this.addNewPlayer(player, newPosition, newViewDistance); -+ return; -+ } -+ -+ // x axis is width -+ // z axis is height -+ // right refers to the x axis of where we moved -+ // top refers to the z axis of where we moved -+ -+ if (oldViewDistance == newViewDistance) { -+ // same view distance -+ -+ // used for relative positioning -+ final int up = 1 | (dz >> (Integer.SIZE - 1)); // 1 if dz >= 0, -1 otherwise -+ final int right = 1 | (dx >> (Integer.SIZE - 1)); // 1 if dx >= 0, -1 otherwise -+ -+ // The area excluded by overlapping the two view distance squares creates four rectangles: -+ // Two on the left, and two on the right. The ones on the left we consider the "removed" section -+ // and on the right the "added" section. -+ // https://i.imgur.com/MrnOBgI.png is a reference image. Note that the outside border is not actually -+ // exclusive to the regions they surround. -+ -+ // 4 points of the rectangle -+ int maxX; // exclusive -+ int minX; // inclusive -+ int maxZ; // exclusive -+ int minZ; // inclusive -+ -+ if (dx != 0) { -+ // handle right addition -+ -+ maxX = toX + (oldViewDistance * right) + right; // exclusive -+ minX = fromX + (oldViewDistance * right) + right; // inclusive -+ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive -+ minZ = toZ - (oldViewDistance * up); // inclusive -+ -+ for (int currX = minX; currX != maxX; currX += right) { -+ for (int currZ = minZ; currZ != maxZ; currZ += up) { -+ this.addPlayerTo(player, currX, currZ); -+ } -+ } -+ } -+ -+ if (dz != 0) { -+ // handle up addition -+ -+ maxX = toX + (oldViewDistance * right) + right; // exclusive -+ minX = toX - (oldViewDistance * right); // inclusive -+ maxZ = toZ + (oldViewDistance * up) + up; // exclusive -+ minZ = fromZ + (oldViewDistance * up) + up; // inclusive -+ -+ for (int currX = minX; currX != maxX; currX += right) { -+ for (int currZ = minZ; currZ != maxZ; currZ += up) { -+ this.addPlayerTo(player, currX, currZ); -+ } -+ } -+ } -+ -+ if (dx != 0) { -+ // handle left removal -+ -+ maxX = toX - (oldViewDistance * right); // exclusive -+ minX = fromX - (oldViewDistance * right); // inclusive -+ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive -+ minZ = toZ - (oldViewDistance * up); // inclusive -+ -+ for (int currX = minX; currX != maxX; currX += right) { -+ for (int currZ = minZ; currZ != maxZ; currZ += up) { -+ this.removePlayerFrom(player, currX, currZ); -+ } -+ } -+ } -+ -+ if (dz != 0) { -+ // handle down removal -+ -+ maxX = fromX + (oldViewDistance * right) + right; // exclusive -+ minX = fromX - (oldViewDistance * right); // inclusive -+ maxZ = toZ - (oldViewDistance * up); // exclusive -+ minZ = fromZ - (oldViewDistance * up); // inclusive -+ -+ for (int currX = minX; currX != maxX; currX += right) { -+ for (int currZ = minZ; currZ != maxZ; currZ += up) { -+ this.removePlayerFrom(player, currX, currZ); -+ } -+ } -+ } -+ } else { -+ // different view distance -+ // for now :) -+ this.removePlayer(player, oldPosition, oldViewDistance); -+ this.addNewPlayer(player, newPosition, newViewDistance); -+ } -+ } -+ -+ private void removePlayer(final EntityPlayer player, final SectionPosition position, final int viewDistance) { -+ final int x = position.getX(); -+ final int z = position.getZ(); -+ -+ for (int xoff = -viewDistance; xoff <= viewDistance; ++xoff) { -+ for (int zoff = -viewDistance; zoff <= viewDistance; ++zoff) { -+ this.removePlayerFrom(player, x + xoff, z + zoff); -+ } -+ } -+ } -+ -+ private void addNewPlayer(final EntityPlayer player, final SectionPosition position, final int viewDistance) { -+ final int x = position.getX(); -+ final int z = position.getZ(); -+ -+ for (int xoff = -viewDistance; xoff <= viewDistance; ++xoff) { -+ for (int zoff = -viewDistance; zoff <= viewDistance; ++zoff) { -+ this.addPlayerTo(player, x + xoff, z + zoff); -+ } -+ } -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java b/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java -@@ -0,0 +0,0 @@ -+package com.destroystokyo.paper.util; -+ -+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; -+import java.lang.ref.WeakReference; -+import java.util.Iterator; -+ -+/** @author Spottedleaf */ -+public class PooledHashSets { -+ -+ // we really want to avoid that equals() check as much as possible... -+ protected final Object2ObjectOpenHashMap, PooledObjectLinkedOpenHashSet> mapPool = new Object2ObjectOpenHashMap<>(64, 0.25f); -+ -+ protected void decrementReferenceCount(final PooledObjectLinkedOpenHashSet current) { -+ if (current.referenceCount == 0) { -+ throw new IllegalStateException("Cannot decrement reference count for " + current); -+ } -+ if (current.referenceCount == -1 || --current.referenceCount > 0) { -+ return; -+ } -+ -+ this.mapPool.remove(current); -+ return; -+ } -+ -+ public PooledObjectLinkedOpenHashSet findMapWith(final PooledObjectLinkedOpenHashSet current, final E object) { -+ final PooledObjectLinkedOpenHashSet cached = current.getAddCache(object); -+ -+ if (cached != null) { -+ if (cached.referenceCount != -1) { -+ ++cached.referenceCount; -+ } -+ -+ decrementReferenceCount(current); -+ -+ return cached; -+ } -+ -+ if (!current.add(object)) { -+ return current; -+ } -+ -+ // we use get/put since we use a different key on put -+ PooledObjectLinkedOpenHashSet ret = this.mapPool.get(current); -+ -+ if (ret == null) { -+ ret = new PooledObjectLinkedOpenHashSet<>(current); -+ current.remove(object); -+ this.mapPool.put(ret, ret); -+ ret.referenceCount = 1; -+ } else { -+ if (ret.referenceCount != -1) { -+ ++ret.referenceCount; -+ } -+ current.remove(object); -+ } -+ -+ current.updateAddCache(object, ret); -+ -+ decrementReferenceCount(current); -+ return ret; -+ } -+ -+ // rets null if current.size() == 1 -+ public PooledObjectLinkedOpenHashSet findMapWithout(final PooledObjectLinkedOpenHashSet current, final E object) { -+ if (current.set.size() == 1) { -+ decrementReferenceCount(current); -+ return null; -+ } -+ -+ final PooledObjectLinkedOpenHashSet cached = current.getRemoveCache(object); -+ -+ if (cached != null) { -+ if (cached.referenceCount != -1) { -+ ++cached.referenceCount; -+ } -+ -+ decrementReferenceCount(current); -+ -+ return cached; -+ } -+ -+ if (!current.remove(object)) { -+ return current; -+ } -+ -+ // we use get/put since we use a different key on put -+ PooledObjectLinkedOpenHashSet ret = this.mapPool.get(current); -+ -+ if (ret == null) { -+ ret = new PooledObjectLinkedOpenHashSet<>(current); -+ current.add(object); -+ this.mapPool.put(ret, ret); -+ ret.referenceCount = 1; -+ } else { -+ if (ret.referenceCount != -1) { -+ ++ret.referenceCount; -+ } -+ current.add(object); -+ } -+ -+ current.updateRemoveCache(object, ret); -+ -+ decrementReferenceCount(current); -+ return ret; -+ } -+ -+ public static final class PooledObjectLinkedOpenHashSet implements Iterable { -+ -+ private static final WeakReference NULL_REFERENCE = new WeakReference(null); -+ -+ final ObjectLinkedOpenHashSet set; -+ int referenceCount; // -1 if special -+ int hash; // optimize hashcode -+ -+ // add cache -+ WeakReference lastAddObject = NULL_REFERENCE; -+ WeakReference> lastAddMap = NULL_REFERENCE; -+ -+ // remove cache -+ WeakReference lastRemoveObject = NULL_REFERENCE; -+ WeakReference> lastRemoveMap = NULL_REFERENCE; -+ -+ public PooledObjectLinkedOpenHashSet() { -+ this.set = new ObjectLinkedOpenHashSet<>(2, 0.6f); -+ } -+ -+ public PooledObjectLinkedOpenHashSet(final E single) { -+ this(); -+ this.referenceCount = -1; -+ this.add(single); -+ } -+ -+ public PooledObjectLinkedOpenHashSet(final PooledObjectLinkedOpenHashSet other) { -+ this.set = other.set.clone(); -+ this.hash = other.hash; -+ } -+ -+ // from https://github.com/Spottedleaf/ConcurrentUtil/blob/master/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java -+ // generated by https://github.com/skeeto/hash-prospector -+ static int hash0(int x) { -+ x *= 0x36935555; -+ x ^= x >>> 16; -+ return x; -+ } -+ -+ public PooledObjectLinkedOpenHashSet getAddCache(final E element) { -+ final E currentAdd = this.lastAddObject.get(); -+ -+ if (currentAdd == null || !(currentAdd == element || currentAdd.equals(element))) { -+ return null; -+ } -+ -+ final PooledObjectLinkedOpenHashSet map = this.lastAddMap.get(); -+ if (map == null || map.referenceCount == 0) { -+ // we need to ret null if ref count is zero as calling code will assume the map is in use -+ return null; -+ } -+ -+ return map; -+ } -+ -+ public PooledObjectLinkedOpenHashSet getRemoveCache(final E element) { -+ final E currentRemove = this.lastRemoveObject.get(); -+ -+ if (currentRemove == null || !(currentRemove == element || currentRemove.equals(element))) { -+ return null; -+ } -+ -+ final PooledObjectLinkedOpenHashSet map = this.lastRemoveMap.get(); -+ if (map == null || map.referenceCount == 0) { -+ // we need to ret null if ref count is zero as calling code will assume the map is in use -+ return null; -+ } -+ -+ return map; -+ } -+ -+ public void updateAddCache(final E element, final PooledObjectLinkedOpenHashSet map) { -+ this.lastAddObject = new WeakReference<>(element); -+ this.lastAddMap = new WeakReference<>(map); -+ } -+ -+ public void updateRemoveCache(final E element, final PooledObjectLinkedOpenHashSet map) { -+ this.lastRemoveObject = new WeakReference<>(element); -+ this.lastRemoveMap = new WeakReference<>(map); -+ } -+ -+ boolean add(final E element) { -+ boolean added = this.set.add(element); -+ -+ if (added) { -+ this.hash += hash0(element.hashCode()); -+ } -+ -+ return added; -+ } -+ -+ boolean remove(Object element) { -+ boolean removed = this.set.remove(element); -+ -+ if (removed) { -+ this.hash -= hash0(element.hashCode()); -+ } -+ -+ return removed; -+ } -+ -+ @Override -+ public Iterator iterator() { -+ return this.set.iterator(); -+ } -+ -+ @Override -+ public int hashCode() { -+ return this.hash; -+ } -+ -+ @Override -+ public boolean equals(final Object other) { -+ if (!(other instanceof PooledObjectLinkedOpenHashSet)) { -+ return false; -+ } -+ if (this.referenceCount == 0) { -+ return other == this; -+ } else { -+ if (other == this) { -+ // Unfortunately we are never equal to our own instance while in use! -+ return false; -+ } -+ return this.hash == ((PooledObjectLinkedOpenHashSet)other).hash && this.set.equals(((PooledObjectLinkedOpenHashSet)other).set); -+ } -+ } -+ -+ @Override -+ public String toString() { -+ return "PooledHashSet: size: " + this.set.size() + ", reference count: " + this.referenceCount + ", hash: " + -+ this.hashCode() + ", identity: " + System.identityHashCode(this) + " map: " + this.set.toString(); -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/ChunkProviderServer.java -+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java -@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider { - this.world.getMethodProfiler().enter("naturalSpawnCount"); - this.world.timings.countNaturalMobs.startTiming(); // Paper - timings - int l = this.chunkMapDistance.b(); -- SpawnerCreature.d spawnercreature_d = SpawnerCreature.a(l, this.world.z(), this::a); -+ // Paper start - per player mob spawning -+ SpawnerCreature.d spawnercreature_d; // moved down -+ if (this.playerChunkMap.playerMobDistanceMap != null) { -+ // update distance map -+ this.world.timings.playerMobDistanceMapUpdate.startTiming(); -+ this.playerChunkMap.playerMobDistanceMap.update(this.world.players, this.playerChunkMap.viewDistance); -+ this.world.timings.playerMobDistanceMapUpdate.stopTiming(); -+ // re-set mob counts -+ for (EntityPlayer player : this.world.players) { -+ Arrays.fill(player.mobCounts, 0); -+ } -+ spawnercreature_d = SpawnerCreature.countMobs(l, this.world.z(), this::a, true); -+ } else { -+ spawnercreature_d = SpawnerCreature.countMobs(l, this.world.z(), this::a, false); -+ } -+ // Paper end - this.world.timings.countNaturalMobs.stopTiming(); // Paper - timings - - this.p = spawnercreature_d; -diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/EntityPlayer.java -+++ b/src/main/java/net/minecraft/server/EntityPlayer.java -@@ -0,0 +0,0 @@ public class EntityPlayer extends EntityHuman implements ICrafting { - public boolean queueHealthUpdatePacket = false; - public net.minecraft.server.PacketPlayOutUpdateHealth queuedHealthUpdatePacket; - // Paper end -+ // Paper start - mob spawning rework -+ public static final int ENUMCREATURETYPE_TOTAL_ENUMS = EnumCreatureType.values().length; -+ public final int[] mobCounts = new int[ENUMCREATURETYPE_TOTAL_ENUMS]; // Paper -+ public final com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet cachedSingleMobDistanceMap; -+ // Paper end - - // CraftBukkit start - public String displayName; -@@ -0,0 +0,0 @@ public class EntityPlayer extends EntityHuman implements ICrafting { - this.displayName = this.getName(); - this.canPickUpLoot = true; - this.maxHealthCache = this.getMaxHealth(); -+ this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper - } - - // Yes, this doesn't match Vanilla, but it's the best we can do for now. -@@ -0,0 +0,0 @@ public class EntityPlayer extends EntityHuman implements ICrafting { - - } - -+ public final SectionPosition getPlayerMapSection() { return this.N(); } // Paper - OBFHELPER - public SectionPosition N() { - return this.cq; - } -diff --git a/src/main/java/net/minecraft/server/EntityTypes.java b/src/main/java/net/minecraft/server/EntityTypes.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/EntityTypes.java -+++ b/src/main/java/net/minecraft/server/EntityTypes.java -@@ -0,0 +0,0 @@ public class EntityTypes { - return this.bk; - } - -+ public final EnumCreatureType getEnumCreatureType() { return this.e(); } // Paper - OBFHELPER - public EnumCreatureType e() { - return this.bf; - } -diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/PlayerChunkMap.java -+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java -@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { - public final Int2ObjectMap trackedEntities; - private final Long2ByteMap z; - private final Queue A; private final Queue getUnloadQueueTasks() { return this.A; } // Paper - OBFHELPER -- private int viewDistance; -+ int viewDistance; // Paper - private -> package private -+ public final com.destroystokyo.paper.util.PlayerMobDistanceMap playerMobDistanceMap; // Paper - - // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() - public final CallbackExecutor callbackExecutor = new CallbackExecutor(); -@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { - this.l = supplier; - this.m = new VillagePlace(new File(this.w, "poi"), datafixer, flag, this.world); // Paper - this.setViewDistance(i); -+ this.playerMobDistanceMap = this.world.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper -+ } -+ -+ public void updatePlayerMobTypeMap(Entity entity) { -+ if (!this.world.paperConfig.perPlayerMobSpawns) { -+ return; -+ } -+ int chunkX = (int)Math.floor(entity.locX()) >> 4; -+ int chunkZ = (int)Math.floor(entity.locZ()) >> 4; -+ int index = entity.getEntityType().getEnumCreatureType().ordinal(); -+ -+ for (EntityPlayer player : this.playerMobDistanceMap.getPlayersInRange(chunkX, chunkZ)) { -+ ++player.mobCounts[index]; -+ } -+ } -+ -+ public int getMobCountNear(EntityPlayer entityPlayer, EnumCreatureType enumCreatureType) { -+ return entityPlayer.mobCounts[enumCreatureType.ordinal()]; - } - - private static double a(ChunkCoordIntPair chunkcoordintpair, Entity entity) { -diff --git a/src/main/java/net/minecraft/server/SpawnerCreature.java b/src/main/java/net/minecraft/server/SpawnerCreature.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/SpawnerCreature.java -+++ b/src/main/java/net/minecraft/server/SpawnerCreature.java -@@ -0,0 +0,0 @@ public final class SpawnerCreature { - }); - - public static SpawnerCreature.d a(int i, Iterable iterable, SpawnerCreature.b spawnercreature_b) { -+ // Paper start - add countMobs parameter -+ return countMobs(i, iterable, spawnercreature_b, false); -+ } -+ public static SpawnerCreature.d countMobs(int i, Iterable iterable, SpawnerCreature.b spawnercreature_b, boolean countMobs) { -+ // Paper end - add countMobs parameter - SpawnerCreatureProbabilities spawnercreatureprobabilities = new SpawnerCreatureProbabilities(); - Object2IntOpenHashMap object2intopenhashmap = new Object2IntOpenHashMap(); - Iterator iterator = iterable.iterator(); -@@ -0,0 +0,0 @@ public final class SpawnerCreature { - } - - object2intopenhashmap.addTo(enumcreaturetype, 1); -+ // Paper start -+ if (countMobs) { -+ ((WorldServer)chunk.world).getChunkProvider().playerChunkMap.updatePlayerMobTypeMap(entity); -+ } -+ // Paper end - }); - } - } -@@ -0,0 +0,0 @@ public final class SpawnerCreature { - continue; - } - -+ // Paper start - only allow spawns upto the limit per chunk and update count afterwards -+ int currEntityCount = spawnercreature_d.getEntityCountsByType().getInt(enumcreaturetype); -+ int k1 = limit * spawnercreature_d.getSpawnerChunks() / SpawnerCreature.b; -+ int difference = k1 - currEntityCount; -+ -+ if (worldserver.paperConfig.perPlayerMobSpawns) { -+ int minDiff = Integer.MAX_VALUE; -+ for (EntityPlayer entityplayer : worldserver.getChunkProvider().playerChunkMap.playerMobDistanceMap.getPlayersInRange(chunk.getPos())) { -+ minDiff = Math.min(limit - worldserver.getChunkProvider().playerChunkMap.getMobCountNear(entityplayer, enumcreaturetype), minDiff); -+ } -+ difference = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff; -+ } -+ // Paper end -+ -+ if (difference > 0) { // Paper - if ((flag || !enumcreaturetype.d()) && (flag1 || enumcreaturetype.d()) && (flag2 || !enumcreaturetype.e()) && spawnercreature_d.a(enumcreaturetype, limit)) { - // CraftBukkit end -- a(enumcreaturetype, worldserver, chunk, (entitytypes, blockposition, ichunkaccess) -> { -+ int spawnCount = spawnMobs(enumcreaturetype, worldserver, chunk, (entitytypes, blockposition, ichunkaccess) -> { - return spawnercreature_d.a(entitytypes, blockposition, ichunkaccess); - }, (entityinsentient, ichunkaccess) -> { - spawnercreature_d.a(entityinsentient, ichunkaccess); -+ }, -+ limit, worldserver.paperConfig.perPlayerMobSpawns ? worldserver.getChunkProvider().playerChunkMap::updatePlayerMobTypeMap : null); -+ spawnercreature_d.getEntityCountsByType().mergeInt(enumcreaturetype, 0, (keyInMap, valueInMap) -> { -+ return Integer.valueOf(spawnCount + valueInMap.intValue()); - }); -+ } // Paper - } - } - -@@ -0,0 +0,0 @@ public final class SpawnerCreature { - } - - public static void a(EnumCreatureType enumcreaturetype, WorldServer worldserver, Chunk chunk, SpawnerCreature.c spawnercreature_c, SpawnerCreature.a spawnercreature_a) { -+ // Paper start - add parameters and int ret type -+ spawnMobs(enumcreaturetype, worldserver, chunk, spawnercreature_c, spawnercreature_a, Integer.MAX_VALUE, null); -+ } -+ public static int spawnMobs(EnumCreatureType enumcreaturetype, WorldServer worldserver, Chunk chunk, SpawnerCreature.c spawnercreature_c, SpawnerCreature.a spawnercreature_a, int maxSpawns, Consumer trackEntity) { -+ // Paper end - add parameters and int ret type - BlockPosition blockposition = getRandomPosition(worldserver, chunk); - - if (blockposition.getY() >= 1) { -- a(enumcreaturetype, worldserver, (IChunkAccess) chunk, blockposition, spawnercreature_c, spawnercreature_a); -+ return spawnMobsInternal(enumcreaturetype, worldserver, (IChunkAccess) chunk, blockposition, spawnercreature_c, spawnercreature_a, maxSpawns, trackEntity); - } -+ return 0; // Paper - } - - public static void a(EnumCreatureType enumcreaturetype, WorldServer worldserver, IChunkAccess ichunkaccess, BlockPosition blockposition, SpawnerCreature.c spawnercreature_c, SpawnerCreature.a spawnercreature_a) { -+ // Paper start - add maxSpawns parameter and return spawned mobs -+ spawnMobsInternal(enumcreaturetype, worldserver, ichunkaccess, blockposition, spawnercreature_c, spawnercreature_a, Integer.MAX_VALUE, null); -+ } -+ public static int spawnMobsInternal(EnumCreatureType enumcreaturetype, WorldServer worldserver, IChunkAccess ichunkaccess, BlockPosition blockposition, SpawnerCreature.c spawnercreature_c, SpawnerCreature.a spawnercreature_a, int maxSpawns, Consumer trackEntity) { -+ // Paper end - add maxSpawns parameter and return spawned mobs - StructureManager structuremanager = worldserver.getStructureManager(); - ChunkGenerator chunkgenerator = worldserver.getChunkProvider().getChunkGenerator(); - int i = blockposition.getY(); - IBlockData iblockdata = worldserver.getTypeIfLoadedAndInBounds(blockposition); // Paper - don't load chunks for mob spawn -+ int j = 0; // Paper - moved up - - if (iblockdata != null && !iblockdata.isOccluding(ichunkaccess, blockposition)) { // Paper - don't load chunks for mob spawn - BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); -- int j = 0; -+ // Paper - moved up - int k = 0; - - while (k < 3) { -@@ -0,0 +0,0 @@ public final class SpawnerCreature { - // Paper start - Boolean doSpawning = a(worldserver, enumcreaturetype, structuremanager, chunkgenerator, biomebase_biomemeta, blockposition_mutableblockposition, d2); - if (doSpawning == null) { -- return; -+ return j; // Paper - } - if (doSpawning.booleanValue() && spawnercreature_c.test(biomebase_biomemeta.c, blockposition_mutableblockposition, ichunkaccess)) { // Paper end - EntityInsentient entityinsentient = a(worldserver, biomebase_biomemeta.c); - - if (entityinsentient == null) { -- return; -+ return j; // Paper - } - - entityinsentient.setPositionRotation(d0, (double) i, d1, worldserver.random.nextFloat() * 360.0F, 0.0F); -@@ -0,0 +0,0 @@ public final class SpawnerCreature { - groupdataentity = entityinsentient.prepare(worldserver, worldserver.getDamageScaler(entityinsentient.getChunkCoordinates()), EnumMobSpawn.NATURAL, groupdataentity, (NBTTagCompound) null); - // CraftBukkit start - if (worldserver.addEntity(entityinsentient, SpawnReason.NATURAL)) { -- ++j; -+ ++j; // Paper - force diff on name change - we expect this to be the total amount spawned - ++k1; - spawnercreature_a.run(entityinsentient, ichunkaccess); -+ // Paper start -+ if (trackEntity != null) { -+ trackEntity.accept(entityinsentient); -+ } -+ // Paper end - } - // CraftBukkit end -- if (j >= entityinsentient.getMaxSpawnGroup()) { -- return; -+ if (j >= entityinsentient.getMaxSpawnGroup() || j >= maxSpawns) { // Paper -+ return j; // Paper - } - - if (entityinsentient.c(k1)) { -@@ -0,0 +0,0 @@ public final class SpawnerCreature { - } - - } -+ return j; // Paper - } - - private static boolean a(WorldServer worldserver, IChunkAccess ichunkaccess, BlockPosition.MutableBlockPosition blockposition_mutableblockposition, double d0) { -@@ -0,0 +0,0 @@ public final class SpawnerCreature { - - public static class d { - -- private final int a; -+ private final int a; final int getSpawnerChunks() { return this.a; } // Paper - OBFHELPER - private final Object2IntOpenHashMap b; - private final SpawnerCreatureProbabilities c; -- private final Object2IntMap d; -+ private final Object2IntMap d; final Object2IntMap getEntityCountsByType() { return this.d; } // Paper - OBFHELPER - @Nullable - private BlockPosition e; - @Nullable -@@ -0,0 +0,0 @@ public final class SpawnerCreature { - - // CraftBukkit start - private boolean a(EnumCreatureType enumcreaturetype, int limit) { -- int i = limit * this.a / SpawnerCreature.b; -+ int i = limit * this.a / SpawnerCreature.b; // Paper - diff on change, needed in the spawn method - // CraftBukkit end - - return this.b.getInt(enumcreaturetype) < i;