PaperMC/patches/server/1003-Optional-per-player-mob-spawns.patch

256 lines
16 KiB
Diff
Raw Normal View History

2021-06-11 14:02:28 +02:00
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] Optional per player mob spawns
2021-06-14 04:41:44 +02:00
2021-06-11 14:02:28 +02:00
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 3c24a700cb417f98bf94631d4c87e66cbdbbba57..b4611b21beb31b109cb5ecfa5aa03ac15e16335a 100644
2021-06-11 14:02:28 +02:00
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -288,9 +288,28 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
});
}
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ // Paper start - Optional per player mob spawns
+ public void updatePlayerMobTypeMap(final Entity entity) {
+ if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) {
2021-06-11 14:02:28 +02:00
+ return;
+ }
2021-06-14 04:41:44 +02:00
+ int index = entity.getType().getCategory().ordinal();
2021-06-11 14:02:28 +02:00
+
+ final com.destroystokyo.paper.util.maplist.ReferenceList<ServerPlayer> inRange =
+ this.getNearbyPlayers().getPlayers(entity.chunkPosition(), io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE);
+ if (inRange == null) {
+ return;
+ }
+ final Object[] backingSet = inRange.getRawData();
+ for (int i = 0, len = inRange.size(); i < len; i++) {
+ ++((ServerPlayer)backingSet[i]).mobCounts[index];
2021-06-11 14:02:28 +02:00
+ }
+ }
+
public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) {
- return -1;
+ return player.mobCounts[mobCategory.ordinal()];
}
+ // Paper end - Optional per player mob spawns
2021-06-11 14:02:28 +02:00
private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) {
2021-06-14 04:41:44 +02:00
double d0 = (double) SectionPos.sectionToBlockCoord(pos.x, 8);
2021-06-11 14:02:28 +02:00
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 35f463e5237d12e42f7a8b8c177af3236d2e7986..25009accd401fc3860c57cac1ef628b233e746e7 100644
2021-06-11 14:02:28 +02:00
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -533,7 +533,19 @@ public class ServerChunkCache extends ChunkSource {
2023-12-05 23:32:41 +01:00
gameprofilerfiller.popPush("naturalSpawnCount");
this.level.timings.countNaturalMobs.startTiming(); // Paper - timings
int k = this.distanceManager.getNaturalSpawnChunkCount();
- NaturalSpawner.SpawnState spawnercreature_d = NaturalSpawner.createState(k, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap));
+ // Paper start - Optional per player mob spawns
2023-12-05 23:32:41 +01:00
+ int naturalSpawnChunkCount = k;
+ NaturalSpawner.SpawnState spawnercreature_d; // moved down
+ if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // 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(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true);
+ } else {
+ spawnercreature_d = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false);
2021-06-11 14:02:28 +02:00
+ }
+ // Paper end - Optional per player mob spawns
2023-12-05 23:32:41 +01:00
this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings
2021-06-11 14:02:28 +02:00
2023-12-05 23:32:41 +01:00
this.lastSpawnState = spawnercreature_d;
2021-06-11 14:02:28 +02:00
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index e03d2d3d35470cb3971606c66da382672eef1f82..4bfc12a50a262f49c0262b06b39faa8116b3807f 100644
2021-06-11 14:02:28 +02:00
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -256,6 +256,10 @@ public class ServerPlayer extends Player {
public boolean queueHealthUpdatePacket;
2021-06-11 14:02:28 +02:00
public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket;
// Paper end - cancellable death event
+ // Paper start - Optional per player mob spawns
+ public static final int MOBCATEGORY_TOTAL_ENUMS = net.minecraft.world.entity.MobCategory.values().length;
+ public final int[] mobCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper
+ // Paper end - Optional per player mob spawns
2021-06-11 14:02:28 +02:00
// CraftBukkit start
public String displayName;
diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
index 28ec1cc4dec6d12627761a58d635fd51dbc398b3..da9f091e1c2dd8b025e806f53a4708b623ee4cd8 100644
2021-06-11 14:02:28 +02:00
--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
2022-12-07 20:22:28 +01:00
@@ -70,6 +70,12 @@ public final class NaturalSpawner {
2021-06-14 04:41:44 +02:00
private NaturalSpawner() {}
2022-11-12 21:57:41 +01:00
public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkSource, LocalMobCapCalculator densityCapper) {
+ // Paper start - Optional per player mob spawns
2022-11-12 21:57:41 +01:00
+ return createState(spawningChunkCount, entities, chunkSource, densityCapper, false);
2021-06-11 14:02:28 +02:00
+ }
+
2022-11-12 21:57:41 +01:00
+ public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkSource, LocalMobCapCalculator densityCapper, boolean countMobs) {
+ // Paper end - Optional per player mob spawns
2021-06-11 14:02:28 +02:00
PotentialCalculator spawnercreatureprobabilities = new PotentialCalculator();
Object2IntOpenHashMap<MobCategory> object2intopenhashmap = new Object2IntOpenHashMap();
2021-06-14 04:41:44 +02:00
Iterator iterator = entities.iterator();
2022-12-07 20:22:28 +01:00
@@ -104,11 +110,16 @@ public final class NaturalSpawner {
2023-03-14 20:54:57 +01:00
spawnercreatureprobabilities.addCharge(entity.blockPosition(), biomesettingsmobs_b.charge());
}
- if (entity instanceof Mob) {
+ if (densityCapper != null && entity instanceof Mob) { // Paper - Optional per player mob spawns
2022-11-12 21:57:41 +01:00
densityCapper.addMob(chunk.getPos(), enumcreaturetype);
2021-06-11 14:02:28 +02:00
}
object2intopenhashmap.addTo(enumcreaturetype, 1);
+ // Paper start - Optional per player mob spawns
2021-06-11 14:02:28 +02:00
+ if (countMobs) {
2021-06-14 04:41:44 +02:00
+ chunk.level.getChunkSource().chunkMap.updatePlayerMobTypeMap(entity);
2021-06-11 14:02:28 +02:00
+ }
+ // Paper end - Optional per player mob spawns
2021-06-11 14:02:28 +02:00
});
}
}
@@ -143,13 +154,35 @@ public final class NaturalSpawner {
2021-06-11 14:02:28 +02:00
continue;
}
- if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (rareSpawn || !enumcreaturetype.isPersistent()) && info.canSpawnForCategory(enumcreaturetype, chunk.getPos(), limit)) {
+ // Paper start - Optional per player mob spawns; only allow spawns upto the limit per chunk and update count afterwards
2021-06-14 06:29:25 +02:00
+ int currEntityCount = info.mobCategoryCounts.getInt(enumcreaturetype);
2021-06-14 04:41:44 +02:00
+ int k1 = limit * info.getSpawnableChunkCount() / NaturalSpawner.MAGIC_NUMBER;
2021-06-11 14:02:28 +02:00
+ int difference = k1 - currEntityCount;
+
+ if (world.paperConfig().entities.spawning.perPlayerMobSpawns) {
2021-06-11 14:02:28 +02:00
+ int minDiff = Integer.MAX_VALUE;
+ final com.destroystokyo.paper.util.maplist.ReferenceList<net.minecraft.server.level.ServerPlayer> inRange =
+ world.chunkSource.chunkMap.getNearbyPlayers().getPlayers(chunk.getPos(), io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE);
+ if (inRange != null) {
+ final Object[] backingSet = inRange.getRawData();
+ for (int k = 0, len = inRange.size(); k < len; k++) {
+ minDiff = Math.min(limit - world.getChunkSource().chunkMap.getMobCountNear((net.minecraft.server.level.ServerPlayer)backingSet[k], enumcreaturetype), minDiff);
+ }
2021-06-11 14:02:28 +02:00
+ }
+ difference = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff;
+ }
+ if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (rareSpawn || !enumcreaturetype.isPersistent()) && difference > 0) {
+ // Paper end - Optional per player mob spawns
2021-06-11 14:02:28 +02:00
// CraftBukkit end
2021-06-14 04:41:44 +02:00
Objects.requireNonNull(info);
NaturalSpawner.SpawnPredicate spawnercreature_c = info::canSpawn;
Objects.requireNonNull(info);
- NaturalSpawner.spawnCategoryForChunk(enumcreaturetype, world, chunk, spawnercreature_c, info::afterSpawn);
+ // Paper start - Optional per player mob spawns
2021-06-14 04:41:44 +02:00
+ int spawnCount = NaturalSpawner.spawnCategoryForChunk(enumcreaturetype, world, chunk, spawnercreature_c, info::afterSpawn,
+ difference, world.paperConfig().entities.spawning.perPlayerMobSpawns ? world.getChunkSource().chunkMap::updatePlayerMobTypeMap : null);
2021-06-14 06:29:25 +02:00
+ info.mobCategoryCounts.mergeInt(enumcreaturetype, spawnCount, Integer::sum);
+ // Paper end - Optional per player mob spawns
2021-06-11 14:02:28 +02:00
}
}
@@ -168,11 +201,17 @@ public final class NaturalSpawner {
// Paper end - Add mobcaps commands
2021-06-11 14:02:28 +02:00
public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) {
+ // Paper start - Optional per player mob spawns
+ spawnCategoryForChunk(group, world, chunk, checker, runner, Integer.MAX_VALUE, null);
2021-06-11 14:02:28 +02:00
+ }
2021-06-14 04:41:44 +02:00
+ public static int spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner, int maxSpawns, Consumer<Entity> trackEntity) {
+ // Paper end - Optional per player mob spawns
2021-06-14 04:41:44 +02:00
BlockPos blockposition = NaturalSpawner.getRandomPosWithin(world, chunk);
2021-06-11 14:02:28 +02:00
2021-06-14 04:41:44 +02:00
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 - Optional per player mob spawns
2021-06-11 14:02:28 +02:00
}
+ return 0; // Paper - Optional per player mob spawns
2021-06-11 14:02:28 +02:00
}
2021-06-14 04:41:44 +02:00
@VisibleForDebug
@@ -183,15 +222,21 @@ public final class NaturalSpawner {
2021-06-14 04:41:44 +02:00
});
}
+ // Paper start - Optional per player mob spawns
2021-06-11 14:02:28 +02:00
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);
2021-06-11 14:02:28 +02:00
+ }
2021-06-14 04:41:44 +02:00
+ 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 - Optional per player mob spawns
2022-06-08 06:06:41 +02:00
StructureManager structuremanager = world.structureManager();
2021-06-14 04:41:44 +02:00
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 - Optional per player mob spawns; moved up
2021-06-14 04:41:44 +02:00
if (iblockdata != null && !iblockdata.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn
2021-06-11 14:02:28 +02:00
BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
- int j = 0;
+ //int j = 0; // Paper - Optional per player mob spawns; moved up
2021-06-11 14:02:28 +02:00
int k = 0;
while (k < 3) {
@@ -233,14 +278,14 @@ public final class NaturalSpawner {
// Paper start - PreCreatureSpawnEvent
PreSpawnStatus doSpawning = isValidSpawnPostitionForType(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2);
if (doSpawning == PreSpawnStatus.ABORT) {
2021-06-14 08:45:29 +02:00
- return;
+ return j; // Paper - Optional per player mob spawns
2021-06-14 08:45:29 +02:00
}
if (doSpawning == PreSpawnStatus.SUCCESS && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) {
// Paper end - PreCreatureSpawnEvent
2021-06-14 04:41:44 +02:00
Mob entityinsentient = NaturalSpawner.getMobForSpawn(world, biomesettingsmobs_c.type);
2021-06-11 14:02:28 +02:00
if (entityinsentient == null) {
- return;
+ return j; // Paper - Optional per player mob spawns
2021-06-11 14:02:28 +02:00
}
2021-06-14 04:41:44 +02:00
entityinsentient.moveTo(d0, (double) i, d1, world.random.nextFloat() * 360.0F, 0.0F);
@@ -253,10 +298,15 @@ public final class NaturalSpawner {
2021-06-14 04:41:44 +02:00
++j;
2021-06-11 14:02:28 +02:00
++k1;
2021-06-14 04:41:44 +02:00
runner.run(entityinsentient, chunk);
+ // Paper start - Optional per player mob spawns
2021-06-11 14:02:28 +02:00
+ if (trackEntity != null) {
+ trackEntity.accept(entityinsentient);
+ }
+ // Paper end - Optional per player mob spawns
2021-06-11 14:02:28 +02:00
}
// CraftBukkit end
- if (j >= entityinsentient.getMaxSpawnClusterSize()) {
- return;
+ if (j >= entityinsentient.getMaxSpawnClusterSize() || j >= maxSpawns) { // Paper - Optional per player mob spawns
+ return j; // Paper - Optional per player mob spawns
2021-06-11 14:02:28 +02:00
}
if (entityinsentient.isMaxGroupSizeReached(k1)) {
@@ -278,6 +328,7 @@ public final class NaturalSpawner {
2021-06-11 14:02:28 +02:00
}
}
+ return j; // Paper - Optional per player mob spawns
2021-06-11 14:02:28 +02:00
}
private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel world, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double squaredDistance) {
@@ -577,7 +628,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 - Optional per player mob spawns
}
public int getSpawnableChunkCount() {
@@ -593,6 +644,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 - Optional per player mob spawns
return this.mobCategoryCounts.getInt(enumcreaturetype) >= i ? false : this.localMobCapCalculator.canSpawn(enumcreaturetype, chunkcoordintpair);
}
}