mirror of
https://github.com/PaperMC/Paper.git
synced 2024-08-21 20:02:55 +02:00
Use a proper teleport for teleporting to entities in different worlds. Validate that the target entity is valid and deny spectate requests from frozen players. Also, make sure the entity is spawned to the client before sending the camera packet. If the entity isn't spawned clientside when it receives the camera packet, then the client will not spectate the target entity.
802 lines
36 KiB
Diff
802 lines
36 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 98acbfa44dd9042b26fdf719d7748f92d201c928..a94ebf7c76f167d3b66f7d243910c13d4bfaaba3 100644
|
|
--- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java
|
|
+++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
|
|
@@ -56,6 +56,7 @@ public class WorldTimingsHandler {
|
|
|
|
|
|
public final Timing miscMobSpawning;
|
|
+ public final Timing playerMobDistanceMapUpdate;
|
|
|
|
public final Timing poiUnload;
|
|
public final Timing chunkUnload;
|
|
@@ -121,6 +122,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 b1d09eb457cfd1be7590f200abfa8cb2236600ef..515673e0fec1dc5c8a188d594f5c9b698c47d0e1 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
@@ -601,4 +601,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 32bea1dea9ebb05ed94f5b47e6ad2145f6319431..7a4e2c350e78b22dc035471ad0d7191dfd7afede 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
@@ -746,7 +746,22 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
this.world.timings.countNaturalMobs.startTiming(); // Paper - timings
|
|
int l = this.chunkMapDistance.b();
|
|
EnumCreatureType[] aenumcreaturetype = EnumCreatureType.values();
|
|
- Object2IntMap<EnumCreatureType> object2intmap = this.world.l();
|
|
+ // Paper start - per player mob spawning
|
|
+ int[] worldMobCount;
|
|
+ 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);
|
|
+ }
|
|
+ worldMobCount = this.world.countMobs(true);
|
|
+ } else {
|
|
+ worldMobCount = this.world.countMobs(false);
|
|
+ }
|
|
+ // Paper end
|
|
|
|
this.world.timings.countNaturalMobs.stopTiming(); // Paper - timings
|
|
this.world.getMethodProfiler().exit();
|
|
@@ -814,8 +829,23 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
if (enumcreaturetype != EnumCreatureType.MISC && (!enumcreaturetype.c() || this.allowAnimals) && (enumcreaturetype.c() || this.allowMonsters) && (!enumcreaturetype.d() || flag2)) {
|
|
int k1 = limit * l / ChunkProviderServer.b; // CraftBukkit - use per-world limits
|
|
|
|
- if (object2intmap.getInt(enumcreaturetype) <= k1) {
|
|
- SpawnerCreature.a(enumcreaturetype, this.world, chunk, blockposition);
|
|
+ // Paper start - only allow spawns upto the limit per chunk and update count afterwards
|
|
+ int currEntityCount = worldMobCount[enumcreaturetype.ordinal()];
|
|
+ int difference = k1 - currEntityCount;
|
|
+
|
|
+ if (this.world.paperConfig.perPlayerMobSpawns) {
|
|
+ int minDiff = Integer.MAX_VALUE;
|
|
+ for (EntityPlayer entityplayer : this.playerChunkMap.playerMobDistanceMap.getPlayersInRange(chunk.getPos())) {
|
|
+ minDiff = Math.min(limit - this.playerChunkMap.getMobCountNear(entityplayer, enumcreaturetype), minDiff);
|
|
+ }
|
|
+ difference = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff;
|
|
+ }
|
|
+
|
|
+ if (difference > 0) {
|
|
+ int spawnCount = SpawnerCreature.spawnMobs(enumcreaturetype, this.world, chunk, blockposition, difference,
|
|
+ this.world.paperConfig.perPlayerMobSpawns ? this.playerChunkMap::updatePlayerMobTypeMap : null);
|
|
+ worldMobCount[enumcreaturetype.ordinal()] += spawnCount;
|
|
+ // Paper end
|
|
}
|
|
}
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
index 57b393c22448910c3ecc34353c3aa8480c58c074..3db173fb5559e62150192a28dbdccdff16787401 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
@@ -81,6 +81,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;
|
|
@@ -116,6 +121,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.
|
|
@@ -1791,6 +1797,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
|
|
|
|
}
|
|
|
|
+ public SectionPosition getPlayerMapSection() { return this.K(); } // Paper - OBFHELPER
|
|
public SectionPosition K() {
|
|
return this.cs;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/EntityTypes.java b/src/main/java/net/minecraft/server/EntityTypes.java
|
|
index 8427ee2ee8b90d5deb686412bb2eefb9a574b75a..0f04bcc8b7cd5bd0536dd3fccab0f4dae6b0130b 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityTypes.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityTypes.java
|
|
@@ -254,6 +254,7 @@ public class EntityTypes<T extends Entity> {
|
|
return this.bf;
|
|
}
|
|
|
|
+ public EnumCreatureType getEnumCreatureType() { return this.e(); } // Paper - OBFHELPER
|
|
public EnumCreatureType e() {
|
|
return this.bb;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
index 963ce3eeec6f9eea087054ea48b714eeb236bbfa..692388821a795f60ffab8459475a764e1c409e2e 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
@@ -78,7 +78,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
private final PlayerMap playerMap;
|
|
public final Int2ObjectMap<PlayerChunkMap.EntityTracker> trackedEntities;
|
|
private final Queue<Runnable> z;
|
|
- 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();
|
|
@@ -158,6 +159,24 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
this.l = supplier;
|
|
this.m = new VillagePlace(new File(this.w, "poi"), datafixer, 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 fdac5bb3a2d4a73035e1d914979b87fc224b6b20..58bbf2f9d2ec91715051d40e108e16067bb36561 100644
|
|
--- a/src/main/java/net/minecraft/server/SpawnerCreature.java
|
|
+++ b/src/main/java/net/minecraft/server/SpawnerCreature.java
|
|
@@ -3,6 +3,7 @@ package net.minecraft.server;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.Random;
|
|
+import java.util.function.Consumer; // Paper
|
|
import javax.annotation.Nullable;
|
|
import org.apache.logging.log4j.LogManager;
|
|
import org.apache.logging.log4j.Logger;
|
|
@@ -16,9 +17,14 @@ public final class SpawnerCreature {
|
|
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
|
|
+ // Paper start - add maxSpawns parameter and return spawned mobs
|
|
public static void a(EnumCreatureType enumcreaturetype, WorldServer worldserver, Chunk chunk, BlockPosition blockposition) {
|
|
+ spawnMobs(enumcreaturetype, worldserver, chunk, blockposition, Integer.MAX_VALUE, null);
|
|
+ }
|
|
+ public static int spawnMobs(EnumCreatureType enumcreaturetype, WorldServer worldserver, Chunk chunk, BlockPosition blockposition, int maxSpawns, Consumer<Entity> trackEntity) {
|
|
+ // Paper end
|
|
ChunkGenerator<?> chunkgenerator = worldserver.getChunkProvider().getChunkGenerator();
|
|
- int i = 0;
|
|
+ int i = 0; // Paper - force diff on name change
|
|
BlockPosition blockposition1 = getRandomPosition(worldserver, chunk);
|
|
int j = blockposition1.getX();
|
|
int k = blockposition1.getY();
|
|
@@ -88,7 +94,7 @@ public final class SpawnerCreature {
|
|
);
|
|
if (!event.callEvent()) {
|
|
if (event.shouldAbortSpawn()) {
|
|
- return;
|
|
+ return i; // Paper
|
|
}
|
|
++i2;
|
|
continue;
|
|
@@ -107,7 +113,7 @@ public final class SpawnerCreature {
|
|
} catch (Exception exception) {
|
|
SpawnerCreature.LOGGER.warn("Failed to create mob", exception);
|
|
ServerInternalException.reportInternalException(exception); // Paper
|
|
- return;
|
|
+ return i; // Paper
|
|
}
|
|
|
|
entityinsentient.setPositionRotation((double) f, (double) k, (double) f1, worldserver.random.nextFloat() * 360.0F, 0.0F);
|
|
@@ -115,12 +121,16 @@ public final class SpawnerCreature {
|
|
groupdataentity = entityinsentient.prepare(worldserver, worldserver.getDamageScaler(new BlockPosition(entityinsentient)), EnumMobSpawn.NATURAL, groupdataentity, (NBTTagCompound) null);
|
|
// CraftBukkit start
|
|
if (worldserver.addEntity(entityinsentient, SpawnReason.NATURAL)) {
|
|
- ++i;
|
|
+ ++i; // Paper - force diff on name change
|
|
++i2;
|
|
+ if (trackEntity != null) {
|
|
+ trackEntity.accept(entityinsentient); // Paper
|
|
+ }
|
|
}
|
|
+ if (i >= maxSpawns) { return i; } // Paper
|
|
// CraftBukkit end
|
|
if (i >= entityinsentient.getMaxSpawnGroup()) {
|
|
- return;
|
|
+ return i; // Paper
|
|
}
|
|
|
|
if (entityinsentient.c(i2)) {
|
|
@@ -146,6 +156,7 @@ public final class SpawnerCreature {
|
|
|
|
}
|
|
}
|
|
+ return i; // Paper
|
|
}
|
|
|
|
@Nullable
|
|
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
|
|
index 469f4e718ba123c65b9743877555f14d8d218589..1838add259a1a2919a80cc5d773ed89ed5d73c01 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldServer.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldServer.java
|
|
@@ -1031,7 +1031,20 @@ public class WorldServer extends World {
|
|
}
|
|
|
|
public Object2IntMap<EnumCreatureType> l() {
|
|
- Object2IntMap<EnumCreatureType> object2intmap = new Object2IntOpenHashMap();
|
|
+ // Paper start
|
|
+ int[] values = this.countMobs(false);
|
|
+ EnumCreatureType[] byId = EnumCreatureType.values();
|
|
+ Object2IntMap<EnumCreatureType> ret = new Object2IntOpenHashMap<>();
|
|
+
|
|
+ for (int i = 0, len = values.length; i < len; ++i) {
|
|
+ ret.put(byId[i], values[i]);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+ public int[] countMobs(boolean updatePlayerCounts) {
|
|
+ int[] ret = new int[EntityPlayer.ENUMCREATURETYPE_TOTAL_ENUMS];
|
|
+ // Paper end
|
|
ObjectIterator objectiterator = this.entitiesById.values().iterator();
|
|
|
|
while (objectiterator.hasNext()) {
|
|
@@ -1056,11 +1069,16 @@ public class WorldServer extends World {
|
|
continue;
|
|
}
|
|
// Paper end
|
|
- object2intmap.mergeInt(enumcreaturetype, 1, Integer::sum);
|
|
+ // Paper start - rework mob spawning
|
|
+ if (updatePlayerCounts) {
|
|
+ this.getChunkProvider().playerChunkMap.updatePlayerMobTypeMap(entity);
|
|
+ }
|
|
+ ++ret[enumcreaturetype.ordinal()];
|
|
+ // Paper end
|
|
}
|
|
}
|
|
|
|
- return object2intmap;
|
|
+ return ret;
|
|
}
|
|
|
|
@Override
|