mirror of
https://github.com/PaperMC/Paper.git
synced 2024-11-09 12:19:56 +01:00
599 lines
28 KiB
Diff
599 lines
28 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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
index 68ae6664541d1dbc611520e7e8612b4f5162c696..6b0391743cd9e249c66796e7fe7a4da8c6b81b2e 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
@@ -613,4 +613,12 @@ public class PaperWorldConfig {
|
|
}
|
|
}
|
|
}
|
|
+
|
|
+ public boolean perPlayerMobSpawns = false;
|
|
+ private void perPlayerMobSpawns() {
|
|
+ if (PaperConfig.version < 22) {
|
|
+ set("per-player-mob-spawns", Boolean.TRUE);
|
|
+ }
|
|
+ perPlayerMobSpawns = getBoolean("per-player-mob-spawns", true);
|
|
+ }
|
|
}
|
|
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..11de56afaf059b00fa5bec293516bcdce7c4b2b9
|
|
--- /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/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
index 75381712377c1b77289a6b5877a657af8eb39a41..056e35b00bd840d8c62c4dc978cbbe5bb8c37887 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
@@ -151,6 +151,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
private final Long2ByteMap chunkTypeCache;
|
|
private final Queue<Runnable> unloadQueue;
|
|
int viewDistance;
|
|
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobDistanceMap; // Paper
|
|
|
|
// CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
|
|
public final CallbackExecutor callbackExecutor = new CallbackExecutor();
|
|
@@ -180,16 +181,31 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
int chunkX = MCUtil.getChunkCoordinate(player.getX());
|
|
int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
|
|
// Note: players need to be explicitly added to distance maps before they can be updated
|
|
+ // Paper start - per player mob spawning
|
|
+ if (this.playerMobDistanceMap != null) {
|
|
+ this.playerMobDistanceMap.add(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance());
|
|
+ }
|
|
+ // Paper end - per player mob spawning
|
|
}
|
|
|
|
void removePlayerFromDistanceMaps(ServerPlayer player) {
|
|
|
|
+ // Paper start - per player mob spawning
|
|
+ if (this.playerMobDistanceMap != null) {
|
|
+ this.playerMobDistanceMap.remove(player);
|
|
+ }
|
|
+ // Paper end - per player mob spawning
|
|
}
|
|
|
|
void updateMaps(ServerPlayer player) {
|
|
int chunkX = MCUtil.getChunkCoordinate(player.getX());
|
|
int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
|
|
// Note: players need to be explicitly added to distance maps before they can be updated
|
|
+ // Paper start - per player mob spawning
|
|
+ if (this.playerMobDistanceMap != null) {
|
|
+ this.playerMobDistanceMap.update(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance());
|
|
+ }
|
|
+ // Paper end - per player mob spawning
|
|
}
|
|
// Paper end
|
|
// Paper start
|
|
@@ -263,6 +279,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
this.dataRegionManager = new io.papermc.paper.chunk.SingleThreadChunkRegionManager(this.level, 2, (1.0 / 3.0), 1, 6, "Data", DataRegionData::new, DataRegionSectionData::new);
|
|
this.regionManagers.add(this.dataRegionManager);
|
|
// Paper end
|
|
+ this.playerMobDistanceMap = this.level.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets) : null; // Paper
|
|
}
|
|
|
|
protected ChunkGenerator generator() {
|
|
@@ -280,6 +297,31 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
});
|
|
}
|
|
|
|
+ // Paper start
|
|
+ public void updatePlayerMobTypeMap(Entity entity) {
|
|
+ if (!this.level.paperConfig.perPlayerMobSpawns) {
|
|
+ return;
|
|
+ }
|
|
+ int index = entity.getType().getCategory().ordinal();
|
|
+
|
|
+ final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> inRange = this.playerMobDistanceMap.getObjectsInRange(entity.chunkPosition());
|
|
+ if (inRange == null) {
|
|
+ return;
|
|
+ }
|
|
+ final Object[] backingSet = inRange.getBackingSet();
|
|
+ for (int i = 0; i < backingSet.length; i++) {
|
|
+ if (!(backingSet[i] instanceof final ServerPlayer player)) {
|
|
+ continue;
|
|
+ }
|
|
+ ++player.mobCounts[index];
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public int getMobCountNear(ServerPlayer entityPlayer, net.minecraft.world.entity.MobCategory mobCategory) {
|
|
+ return entityPlayer.mobCounts[mobCategory.ordinal()];
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) {
|
|
double d0 = (double) SectionPos.sectionToBlockCoord(pos.x, 8);
|
|
double d1 = (double) SectionPos.sectionToBlockCoord(pos.z, 8);
|
|
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
index f0dac1f596911eb2109192ef16a619f8ae71d1f7..07b616d9d7cde77c001f5c627daef0731d883e61 100644
|
|
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
@@ -323,6 +323,12 @@ public abstract class DistanceManager {
|
|
|
|
}
|
|
|
|
+ // Paper start
|
|
+ public int getSimulationDistance() {
|
|
+ return this.simulationDistance;
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
public int getNaturalSpawnChunkCount() {
|
|
this.naturalSpawnChunkCounter.runAllUpdates();
|
|
return this.naturalSpawnChunkCounter.chunks.size();
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
index 7b391d6ab84eeaed7bdd27ea70d5e3f9690a0abf..0d380bba07c5c7b6b25fa2ce554418ef8ba9fdb1 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
@@ -916,7 +916,18 @@ public class ServerChunkCache extends ChunkSource {
|
|
gameprofilerfiller.push("naturalSpawnCount");
|
|
this.level.timings.countNaturalMobs.startTiming(); // Paper - timings
|
|
int l = this.distanceManager.getNaturalSpawnChunkCount();
|
|
- NaturalSpawner.SpawnState spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap));
|
|
+ // Paper start - per player mob spawning
|
|
+ NaturalSpawner.SpawnState spawnercreature_d; // moved down
|
|
+ if ((this.spawnFriendlies || this.spawnEnemies) && this.chunkMap.playerMobDistanceMap != null) { // don't count mobs when animals and monsters are disabled
|
|
+ // re-set mob counts
|
|
+ for (ServerPlayer player : this.level.players) {
|
|
+ Arrays.fill(player.mobCounts, 0);
|
|
+ }
|
|
+ spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, null, true);
|
|
+ } else {
|
|
+ spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, this.chunkMap.playerMobDistanceMap == null ? new LocalMobCapCalculator(this.chunkMap) : null, false);
|
|
+ }
|
|
+ // Paper end
|
|
this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings
|
|
|
|
this.lastSpawnState = spawnercreature_d;
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
index b193f8dfbe7b61c919ad5eb452d29885982e25e4..01b9edc8aaf472650f171f1b88229807bcfdc145 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
@@ -227,6 +227,11 @@ public class ServerPlayer extends Player {
|
|
public boolean queueHealthUpdatePacket = false;
|
|
public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket;
|
|
// Paper end
|
|
+ // Paper start - mob spawning rework
|
|
+ public static final int MOBCATEGORY_TOTAL_ENUMS = net.minecraft.world.entity.MobCategory.values().length;
|
|
+ public final int[] mobCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper
|
|
+ public final com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> cachedSingleMobDistanceMap;
|
|
+ // Paper end
|
|
|
|
// CraftBukkit start
|
|
public String displayName;
|
|
@@ -316,6 +321,7 @@ public class ServerPlayer extends Player {
|
|
this.adventure$displayName = net.kyori.adventure.text.Component.text(this.getScoreboardName()); // Paper
|
|
this.bukkitPickUpLoot = 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.
|
|
diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
|
|
index 6f63f471c2c9a3b85c6fc92bdee31a5ff9714ff5..c0fb5d445ed18fbf52df2be754eab0d8fb6e9239 100644
|
|
--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
|
|
+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
|
|
@@ -65,7 +65,13 @@ public final class NaturalSpawner {
|
|
|
|
private NaturalSpawner() {}
|
|
|
|
+ // Paper start - add countMobs parameter
|
|
public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkSource, LocalMobCapCalculator localmobcapcalculator) {
|
|
+ return createState(spawningChunkCount, entities, chunkSource, localmobcapcalculator, false);
|
|
+ }
|
|
+
|
|
+ public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkSource, LocalMobCapCalculator localmobcapcalculator, boolean countMobs) {
|
|
+ // Paper end
|
|
PotentialCalculator spawnercreatureprobabilities = new PotentialCalculator();
|
|
Object2IntOpenHashMap<MobCategory> object2intopenhashmap = new Object2IntOpenHashMap();
|
|
Iterator iterator = entities.iterator();
|
|
@@ -101,11 +107,16 @@ public final class NaturalSpawner {
|
|
spawnercreatureprobabilities.addCharge(entity.blockPosition(), biomesettingsmobs_b.getCharge());
|
|
}
|
|
|
|
- if (entity instanceof Mob) {
|
|
+ if (localmobcapcalculator != null && entity instanceof Mob) { // Paper
|
|
localmobcapcalculator.addMob(chunk.getPos(), enumcreaturetype);
|
|
}
|
|
|
|
object2intopenhashmap.addTo(enumcreaturetype, 1);
|
|
+ // Paper start
|
|
+ if (countMobs) {
|
|
+ chunk.level.getChunkSource().chunkMap.updatePlayerMobTypeMap(entity);
|
|
+ }
|
|
+ // Paper end
|
|
});
|
|
}
|
|
}
|
|
@@ -169,13 +180,37 @@ public final class NaturalSpawner {
|
|
continue;
|
|
}
|
|
|
|
- if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (rareSpawn || !enumcreaturetype.isPersistent()) && info.canSpawnForCategory(enumcreaturetype, chunk.getPos(), limit)) {
|
|
+ // Paper start - only allow spawns upto the limit per chunk and update count afterwards
|
|
+ int currEntityCount = info.mobCategoryCounts.getInt(enumcreaturetype);
|
|
+ int k1 = limit * info.getSpawnableChunkCount() / NaturalSpawner.MAGIC_NUMBER;
|
|
+ int difference = k1 - currEntityCount;
|
|
+
|
|
+ if (world.paperConfig.perPlayerMobSpawns) {
|
|
+ int minDiff = Integer.MAX_VALUE;
|
|
+ final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> inRange = world.getChunkSource().chunkMap.playerMobDistanceMap.getObjectsInRange(chunk.getPos());
|
|
+ if (inRange != null) {
|
|
+ final Object[] backingSet = inRange.getBackingSet();
|
|
+ for (int k = 0; k < backingSet.length; k++) {
|
|
+ if (!(backingSet[k] instanceof final net.minecraft.server.level.ServerPlayer player)) {
|
|
+ continue;
|
|
+ }
|
|
+ minDiff = Math.min(limit - world.getChunkSource().chunkMap.getMobCountNear(player, enumcreaturetype), minDiff);
|
|
+ }
|
|
+ }
|
|
+ difference = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff;
|
|
+ }
|
|
+ if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (rareSpawn || !enumcreaturetype.isPersistent()) && difference > 0) {
|
|
+ // Paper end
|
|
// CraftBukkit end
|
|
Objects.requireNonNull(info);
|
|
NaturalSpawner.SpawnPredicate spawnercreature_c = info::canSpawn;
|
|
|
|
Objects.requireNonNull(info);
|
|
- NaturalSpawner.spawnCategoryForChunk(enumcreaturetype, world, chunk, spawnercreature_c, info::afterSpawn);
|
|
+ // Paper start
|
|
+ int spawnCount = NaturalSpawner.spawnCategoryForChunk(enumcreaturetype, world, chunk, spawnercreature_c, info::afterSpawn,
|
|
+ difference, world.paperConfig.perPlayerMobSpawns ? world.getChunkSource().chunkMap::updatePlayerMobTypeMap : null);
|
|
+ info.mobCategoryCounts.mergeInt(enumcreaturetype, spawnCount, Integer::sum);
|
|
+ // Paper end
|
|
}
|
|
}
|
|
|
|
@@ -183,12 +218,18 @@ public final class NaturalSpawner {
|
|
world.getProfiler().pop();
|
|
}
|
|
|
|
+ // Paper start - add parameters and int ret type
|
|
public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) {
|
|
+ spawnCategoryForChunk(group, world, chunk, checker, runner);
|
|
+ }
|
|
+ public static int spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner, int maxSpawns, Consumer<Entity> trackEntity) {
|
|
+ // Paper end - add parameters and int ret type
|
|
BlockPos blockposition = NaturalSpawner.getRandomPosWithin(world, chunk);
|
|
|
|
if (blockposition.getY() >= world.getMinBuildHeight() + 1) {
|
|
- NaturalSpawner.spawnCategoryForPosition(group, world, chunk, blockposition, checker, runner);
|
|
+ return NaturalSpawner.spawnCategoryForPosition(group, world, chunk, blockposition, checker, runner, maxSpawns, trackEntity); // Paper
|
|
}
|
|
+ return 0; // Paper
|
|
}
|
|
|
|
@VisibleForDebug
|
|
@@ -199,15 +240,21 @@ public final class NaturalSpawner {
|
|
});
|
|
}
|
|
|
|
+ // Paper start - add maxSpawns parameter and return spawned mobs
|
|
public static void spawnCategoryForPosition(MobCategory group, ServerLevel world, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) {
|
|
+ spawnCategoryForPosition(group, world,chunk, pos, checker, runner);
|
|
+ }
|
|
+ public static int spawnCategoryForPosition(MobCategory group, ServerLevel world, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner, int maxSpawns, Consumer<Entity> trackEntity) {
|
|
+ // Paper end - add maxSpawns parameter and return spawned mobs
|
|
StructureFeatureManager structuremanager = world.structureFeatureManager();
|
|
ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator();
|
|
int i = pos.getY();
|
|
BlockState iblockdata = world.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn
|
|
+ int j = 0; // Paper - moved up
|
|
|
|
if (iblockdata != null && !iblockdata.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn
|
|
BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
|
|
- int j = 0;
|
|
+ //int j = 0; // Paper - moved up
|
|
int k = 0;
|
|
|
|
while (k < 3) {
|
|
@@ -249,14 +296,14 @@ public final class NaturalSpawner {
|
|
// Paper start
|
|
Boolean doSpawning = isValidSpawnPostitionForType(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2);
|
|
if (doSpawning == null) {
|
|
- return;
|
|
+ return j; // Paper
|
|
}
|
|
if (doSpawning && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) {
|
|
// Paper end
|
|
Mob entityinsentient = NaturalSpawner.getMobForSpawn(world, biomesettingsmobs_c.type);
|
|
|
|
if (entityinsentient == null) {
|
|
- return;
|
|
+ return j; // Paper
|
|
}
|
|
|
|
entityinsentient.moveTo(d0, (double) i, d1, world.random.nextFloat() * 360.0F, 0.0F);
|
|
@@ -268,10 +315,15 @@ public final class NaturalSpawner {
|
|
++j;
|
|
++k1;
|
|
runner.run(entityinsentient, chunk);
|
|
+ // Paper start
|
|
+ if (trackEntity != null) {
|
|
+ trackEntity.accept(entityinsentient);
|
|
+ }
|
|
+ // Paper end
|
|
}
|
|
// CraftBukkit end
|
|
- if (j >= entityinsentient.getMaxSpawnClusterSize()) {
|
|
- return;
|
|
+ if (j >= entityinsentient.getMaxSpawnClusterSize() || j >= maxSpawns) { // Paper
|
|
+ return j; // Paper
|
|
}
|
|
|
|
if (entityinsentient.isMaxGroupSizeReached(k1)) {
|
|
@@ -293,6 +345,7 @@ public final class NaturalSpawner {
|
|
}
|
|
|
|
}
|
|
+ return j; // Paper
|
|
}
|
|
|
|
private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel world, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double squaredDistance) {
|
|
@@ -572,7 +625,7 @@ public final class NaturalSpawner {
|
|
MobCategory enumcreaturetype = entitytypes.getCategory();
|
|
|
|
this.mobCategoryCounts.addTo(enumcreaturetype, 1);
|
|
- this.localMobCapCalculator.addMob(new ChunkPos(blockposition), enumcreaturetype);
|
|
+ if (this.localMobCapCalculator != null) this.localMobCapCalculator.addMob(new ChunkPos(blockposition), enumcreaturetype); // Paper
|
|
}
|
|
|
|
public int getSpawnableChunkCount() {
|
|
@@ -588,6 +641,7 @@ public final class NaturalSpawner {
|
|
int i = limit * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER;
|
|
// CraftBukkit end
|
|
|
|
+ if (this.localMobCapCalculator == null) return this.mobCategoryCounts.getInt(enumcreaturetype) < i; // Paper
|
|
return this.mobCategoryCounts.getInt(enumcreaturetype) >= i ? false : this.localMobCapCalculator.canSpawn(enumcreaturetype, chunkcoordintpair);
|
|
}
|
|
}
|