mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-02 13:07:06 +01:00
ef0e5a642d
Upstream has released updates that appear to apply and compile correctly. This update has not been tested by PaperMC and as with ANY update, please do your own testing Bukkit Changes: 9ae3f10f SPIGOT-3842: Add Player#fireworkBoost() and expand Firework API 48c0c547 PR-786: Add methods to get sounds from entities CraftBukkit Changes: 5cc9c022a SPIGOT-7152: Handle hand item changing during air interact event 4ffa1acf6 SPIGOT-7154: Players get kicked when interacting with a conversation 4daa21123 SPIGOT-3842: Add Player#fireworkBoost() and expand Firework API e5d6a9bbf PR-1100: Add methods to get sounds from entities b7e9f1c8b SPIGOT-7146: Reduce use of Material switch in ItemMeta Spigot Changes: 4c157bb4 Rebuild patches
578 lines
28 KiB
Diff
578 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/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 0b68e6c7ef63460d596050ed55039a5ba3cefb24..9e940920f6ff6a43d7162d965936c171c55ed570 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
@@ -159,6 +159,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
private final Long2LongMap chunkSaveCooldowns;
|
|
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();
|
|
@@ -199,16 +200,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
|
|
@@ -305,6 +321,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().entities.spawning.perPlayerMobSpawns ? new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets) : null; // Paper
|
|
}
|
|
|
|
protected ChunkGenerator generator() {
|
|
@@ -356,6 +373,30 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
}
|
|
}
|
|
// Paper end
|
|
+ // Paper start
|
|
+ public void updatePlayerMobTypeMap(Entity entity) {
|
|
+ if (!this.level.paperConfig().entities.spawning.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);
|
|
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
index b9b56068cdacd984f873cfb2a06a312e9912893d..9309ea89a440606be3e56ef634f5048a72b0009e 100644
|
|
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
@@ -466,6 +466,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 d1fa959b6e872565b689ee6b4f1bed9ad9e4077e..f982241898c4aeaf50854e67fe188478f2f2b186 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
@@ -764,7 +764,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 b36effc88b516958a7c0a46a7eb3a77a98ad3a20..582e08eaeee8fb6c777ad451ccc0436b02cac000 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
@@ -236,6 +236,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;
|
|
@@ -326,6 +331,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
|
|
}
|
|
// Paper start - Chunk priority
|
|
public BlockPos getPointInFront(double inFront) {
|
|
diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
|
|
index b4098068e674b639e82c07e5d60e4e2120b4305b..fa23e9c476d4edc6176d8b8a6cb13c52d2f66a87 100644
|
|
--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
|
|
+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
|
|
@@ -69,6 +69,12 @@ public final class NaturalSpawner {
|
|
private NaturalSpawner() {}
|
|
|
|
public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkSource, LocalMobCapCalculator localmobcapcalculator) {
|
|
+ // Paper start - add countMobs parameter
|
|
+ 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();
|
|
@@ -103,11 +109,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
|
|
});
|
|
}
|
|
}
|
|
@@ -142,13 +153,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().entities.spawning.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().entities.spawning.perPlayerMobSpawns ? world.getChunkSource().chunkMap::updatePlayerMobTypeMap : null);
|
|
+ info.mobCategoryCounts.mergeInt(enumcreaturetype, spawnCount, Integer::sum);
|
|
+ // Paper end
|
|
}
|
|
}
|
|
|
|
@@ -157,11 +192,17 @@ public final class NaturalSpawner {
|
|
}
|
|
|
|
public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) {
|
|
+ // Paper start - add parameters and int ret type
|
|
+ spawnCategoryForChunk(group, world, chunk, checker, runner, Integer.MAX_VALUE, null);
|
|
+ }
|
|
+ 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
|
|
@@ -172,15 +213,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, Integer.MAX_VALUE, null);
|
|
+ }
|
|
+ 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
|
|
StructureManager structuremanager = world.structureManager();
|
|
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) {
|
|
@@ -222,14 +269,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);
|
|
@@ -241,10 +288,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)) {
|
|
@@ -266,6 +318,7 @@ public final class NaturalSpawner {
|
|
}
|
|
|
|
}
|
|
+ return j; // Paper
|
|
}
|
|
|
|
private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel world, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double squaredDistance) {
|
|
@@ -551,7 +604,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() {
|
|
@@ -567,6 +620,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);
|
|
}
|
|
}
|