Paper/Spigot-Server-Patches/0377-implement-optional-per-player-mob-spawns.patch
Mariell Hoversholm 654b792caf Updated Upstream (CraftBukkit)
Upstream has released updates that appears to apply and compile correctly.
This update has not been tested by PaperMC and as with ANY update, please do your own testing

CraftBukkit Changes:
a339310c #755: Fix NPE when calling getInventory() for virtual EnderChests
2577f9bf Increase outdated build delay
1dabfdc8 #754: Fix pre-1.16 serialized SkullMeta being broken on 1.16+, losing textures
2020-09-27 11:04:51 -04:00

827 lines
38 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: kickash32 <kickash32@gmail.com>
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 a27dc38d1a29ed1d63d2f44b7984c2b65be487d9..96aaaab5b7685c874463505f9d25e8a0a01a6e7c 100644
--- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java
+++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
@@ -57,6 +57,7 @@ public class WorldTimingsHandler {
public final Timing miscMobSpawning;
+ public final Timing playerMobDistanceMapUpdate;
public final Timing poiUnload;
public final Timing chunkUnload;
@@ -122,6 +123,7 @@ 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 8221a9f5af9207b0a984ba2555df0e7690ff50ad..aaa7ff2c861005618f3b2ab8f960a5199fd678ce 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -551,4 +551,9 @@ 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..9ebd7ecb7a0ac73ccacf34c097056598bb5d26e8
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java
@@ -0,0 +1,253 @@
+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<EntityPlayer> EMPTY_SET = new PooledHashSets.PooledObjectLinkedOpenHashSet<>();
+
+ private final Map<EntityPlayer, SectionPosition> players = new HashMap<>();
+ // we use linked for better iteration.
+ private final Long2ObjectOpenHashMap<PooledHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer>> playerMap = new Long2ObjectOpenHashMap<>(32, 0.5f);
+ private int viewDistance;
+
+ private final PooledHashSets<EntityPlayer> pooledHashSets = new PooledHashSets<>();
+
+ public PooledHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> getPlayersInRange(final ChunkCoordIntPair chunkPos) {
+ return this.getPlayersInRange(chunkPos.x, chunkPos.z);
+ }
+
+ public PooledHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> getPlayersInRange(final int chunkX, final int chunkZ) {
+ return this.playerMap.getOrDefault(ChunkCoordIntPair.pair(chunkX, chunkZ), EMPTY_SET);
+ }
+
+ public void update(final List<EntityPlayer> currentPlayers, final int newViewDistance) {
+ AsyncCatcher.catchOp("Distance map update");
+ final ObjectLinkedOpenHashSet<EntityPlayer> 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<PooledHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer>> entry : this.playerMap.long2ObjectEntrySet()) {
+ final long key = entry.getLongKey();
+ final PooledHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> 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<EntityPlayer> 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<EntityPlayer> 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..4f13d3ff8391793a99f067189f854078334499c6
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java
@@ -0,0 +1,241 @@
+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<E> {
+
+ // we really want to avoid that equals() check as much as possible...
+ protected final Object2ObjectOpenHashMap<PooledObjectLinkedOpenHashSet<E>, PooledObjectLinkedOpenHashSet<E>> mapPool = new Object2ObjectOpenHashMap<>(64, 0.25f);
+
+ protected void decrementReferenceCount(final PooledObjectLinkedOpenHashSet<E> 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<E> findMapWith(final PooledObjectLinkedOpenHashSet<E> current, final E object) {
+ final PooledObjectLinkedOpenHashSet<E> 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<E> 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<E> findMapWithout(final PooledObjectLinkedOpenHashSet<E> current, final E object) {
+ if (current.set.size() == 1) {
+ decrementReferenceCount(current);
+ return null;
+ }
+
+ final PooledObjectLinkedOpenHashSet<E> 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<E> 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<E> implements Iterable<E> {
+
+ private static final WeakReference NULL_REFERENCE = new WeakReference(null);
+
+ final ObjectLinkedOpenHashSet<E> set;
+ int referenceCount; // -1 if special
+ int hash; // optimize hashcode
+
+ // add cache
+ WeakReference<E> lastAddObject = NULL_REFERENCE;
+ WeakReference<PooledObjectLinkedOpenHashSet<E>> lastAddMap = NULL_REFERENCE;
+
+ // remove cache
+ WeakReference<E> lastRemoveObject = NULL_REFERENCE;
+ WeakReference<PooledObjectLinkedOpenHashSet<E>> 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<E> 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<E> getAddCache(final E element) {
+ final E currentAdd = this.lastAddObject.get();
+
+ if (currentAdd == null || !(currentAdd == element || currentAdd.equals(element))) {
+ return null;
+ }
+
+ final PooledObjectLinkedOpenHashSet<E> 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<E> getRemoveCache(final E element) {
+ final E currentRemove = this.lastRemoveObject.get();
+
+ if (currentRemove == null || !(currentRemove == element || currentRemove.equals(element))) {
+ return null;
+ }
+
+ final PooledObjectLinkedOpenHashSet<E> 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<E> map) {
+ this.lastAddObject = new WeakReference<>(element);
+ this.lastAddMap = new WeakReference<>(map);
+ }
+
+ public void updateRemoveCache(final E element, final PooledObjectLinkedOpenHashSet<E> 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<E> 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 55feb186e4f3f7985efdf7c4410ed4232e52733e..dce9c62c0cf37a37994537b74dfdc2286c60fae2 100644
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
@@ -745,7 +745,22 @@ 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.A(), 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.A(), this::a, true);
+ } else {
+ spawnercreature_d = SpawnerCreature.countMobs(l, this.world.A(), 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 05d73610dc1e0e0c411f9cd6c1118ab5a6bb81ab..f313571ea06fcc77bef92b2c976f651067c5a8d2 100644
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
@@ -87,6 +87,11 @@ 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<EntityPlayer> cachedSingleMobDistanceMap;
+ // Paper end
// CraftBukkit start
public String displayName;
@@ -122,6 +127,7 @@ 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.
@@ -1901,6 +1907,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
}
+ public final SectionPosition getPlayerMapSection() { return this.O(); } // Paper - OBFHELPER
public SectionPosition O() {
return this.cj;
}
diff --git a/src/main/java/net/minecraft/server/EntityTypes.java b/src/main/java/net/minecraft/server/EntityTypes.java
index add85654783e9c3cebf2a2072cd2e07679de1bae..5c03fb3af34e13bc3b8bc1840c5a7e229cf9d1f7 100644
--- a/src/main/java/net/minecraft/server/EntityTypes.java
+++ b/src/main/java/net/minecraft/server/EntityTypes.java
@@ -280,6 +280,7 @@ public class EntityTypes<T extends Entity> {
return this.bl;
}
+ public final EnumCreatureType getEnumCreatureType() { return this.e(); } // Paper - OBFHELPER
public EnumCreatureType e() {
return this.bg;
}
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
index e7d9674e25c06090d57bba6c8229bc3b75cb67e6..70a291090f022ff7d79a75b94ac54b94ff6db751 100644
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
@@ -81,7 +81,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
public final Int2ObjectMap<PlayerChunkMap.EntityTracker> trackedEntities;
private final Long2ByteMap z;
private final Queue<Runnable> A; private final Queue<Runnable> 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();
@@ -160,6 +161,24 @@ 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 99d4d6b4bdedcba7964ddc99467b8f8e5229b98a..beaea041dc47635411542fe9d06690809e03bf9e 100644
--- a/src/main/java/net/minecraft/server/SpawnerCreature.java
+++ b/src/main/java/net/minecraft/server/SpawnerCreature.java
@@ -29,6 +29,11 @@ public final class SpawnerCreature {
});
public static SpawnerCreature.d a(int i, Iterable<Entity> 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<Entity> iterable, SpawnerCreature.b spawnercreature_b, boolean countMobs) {
+ // Paper end - add countMobs parameter
SpawnerCreatureProbabilities spawnercreatureprobabilities = new SpawnerCreatureProbabilities();
Object2IntOpenHashMap<EnumCreatureType> object2intopenhashmap = new Object2IntOpenHashMap();
Iterator iterator = iterable.iterator();
@@ -66,6 +71,11 @@ public final class SpawnerCreature {
}
object2intopenhashmap.addTo(enumcreaturetype, 1);
+ // Paper start
+ if (countMobs) {
+ ((WorldServer)chunk.world).getChunkProvider().playerChunkMap.updatePlayerMobTypeMap(entity);
+ }
+ // Paper end
});
}
}
@@ -124,13 +134,33 @@ public final class SpawnerCreature {
continue;
}
- if ((flag || !enumcreaturetype.d()) && (flag1 || enumcreaturetype.d()) && (flag2 || !enumcreaturetype.e()) && spawnercreature_d.a(enumcreaturetype, limit)) {
+ // 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
+
+ // Paper start - per player mob spawning
+ if ((flag || !enumcreaturetype.d()) && (flag1 || enumcreaturetype.d()) && (flag2 || !enumcreaturetype.e()) && difference > 0) {
// 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);
+ },
+ difference, worldserver.paperConfig.perPlayerMobSpawns ? worldserver.getChunkProvider().playerChunkMap::updatePlayerMobTypeMap : null);
+ spawnercreature_d.getEntityCountsByType().mergeInt(enumcreaturetype, spawnCount, (keyInMap, valueInMap) -> {
+ return Integer.valueOf(spawnCount + valueInMap.intValue());
});
+ // Paper end - per player mob spawning
}
}
@@ -139,22 +169,34 @@ 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<Entity> 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<Entity> 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) {
@@ -194,7 +236,7 @@ public final class SpawnerCreature {
// Paper start
Boolean doSpawning = a(worldserver, enumcreaturetype, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2);
if (doSpawning == null) {
- return;
+ return j; // Paper
}
if (doSpawning && spawnercreature_c.test(biomesettingsmobs_c.c, blockposition_mutableblockposition, ichunkaccess)) {
// Paper end
@@ -202,7 +244,7 @@ public final class SpawnerCreature {
if (entityinsentient == null) {
- return;
+ return j; // Paper
}
entityinsentient.setPositionRotation(d0, (double) i, d1, worldserver.random.nextFloat() * 360.0F, 0.0F);
@@ -210,13 +252,18 @@ public final class SpawnerCreature {
groupdataentity = entityinsentient.prepare(worldserver, worldserver.getDamageScaler(entityinsentient.getChunkCoordinates()), EnumMobSpawn.NATURAL, groupdataentity, (NBTTagCompound) null);
// CraftBukkit start
if (worldserver.addAllEntities(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)) {
@@ -238,6 +285,7 @@ public final class SpawnerCreature {
}
}
+ return j; // Paper
}
private static boolean a(WorldServer worldserver, IChunkAccess ichunkaccess, BlockPosition.MutableBlockPosition blockposition_mutableblockposition, double d0) {
@@ -478,8 +526,8 @@ public final class SpawnerCreature {
public static class d {
- private final int a;
- private final Object2IntOpenHashMap<EnumCreatureType> b;
+ private final int a; final int getSpawnerChunks() { return this.a; } // Paper - OBFHELPER
+ private final Object2IntOpenHashMap<EnumCreatureType> b; final Object2IntMap<EnumCreatureType> getEntityCountsByType() { return this.b; } // Paper - OBFHELPER
private final SpawnerCreatureProbabilities c;
private final Object2IntMap<EnumCreatureType> d;
@Nullable
@@ -540,7 +588,7 @@ 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;