PaperMC/feature-patches/1071-Optional-per-player-mob-spawns.patch

237 lines
14 KiB
Diff
Raw Normal View History

2021-06-14 04:41:44 +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
diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
index 809f3fe1285e347f18709c2368923fcc8f953ded..6c5b2eb411fb60babbb0c74d5c075696ef70b38d 100644
--- a/net/minecraft/server/level/ChunkMap.java
+++ b/net/minecraft/server/level/ChunkMap.java
@@ -237,11 +237,29 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.chunksToEagerlySave.add(chunkPos.toLong());
}
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
- public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) {
- return -1;
+ // Paper start - Optional per player mob spawns
+ public void updatePlayerMobTypeMap(final Entity entity) {
+ if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) {
2021-06-14 04:41:44 +02:00
+ return;
+ }
+
+ final int index = entity.getType().getCategory().ordinal();
+ final ca.spottedleaf.moonrise.common.list.ReferenceList<ServerPlayer> inRange =
+ this.level.moonrise$getNearbyPlayers().getPlayers(entity.chunkPosition(), ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE);
+ if (inRange == null) {
+ return;
+ }
+
+ final ServerPlayer[] backingSet = inRange.getRawDataUnchecked();
+ for (int i = 0, len = inRange.size(); i < len; i++) {
+ ++(backingSet[i].mobCounts[index]);
2021-06-14 04:41:44 +02:00
+ }
}
- // Paper end
+
+ public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) {
+ return player.mobCounts[mobCategory.ordinal()];
+ }
+ // Paper end - Optional per player mob spawns
protected ChunkGenerator generator() {
return this.worldGenContext.generator();
diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
index 2f49dbc919f7f5eea9abce6106723c72f5ae45fb..078f208e104a652ce48458150389d19ede6808ef 100644
--- a/net/minecraft/server/level/ServerChunkCache.java
+++ b/net/minecraft/server/level/ServerChunkCache.java
@@ -435,7 +435,7 @@ public class ServerChunkCache extends ChunkSource {
profilerFiller.push("filteringTickingChunks");
this.collectTickingChunks(list);
profilerFiller.popPush("shuffleChunks");
- Util.shuffle(list, this.level.random);
+ if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) Util.shuffle(list, this.shuffleRandom); // Paper - Optional per player mob spawns; do not need this when per-player is enabled
this.tickChunks(profilerFiller, l, list);
profilerFiller.pop();
} finally {
@@ -474,9 +474,18 @@ public class ServerChunkCache extends ChunkSource {
private void tickChunks(ProfilerFiller profiler, long timeInhabited, List<LevelChunk> chunks) {
profiler.popPush("naturalSpawnCount");
int naturalSpawnChunkCount = this.distanceManager.getNaturalSpawnChunkCount();
- NaturalSpawner.SpawnState spawnState = NaturalSpawner.createState(
- naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap)
- );
+ // Paper start - Optional per player mob spawns
+ NaturalSpawner.SpawnState spawnState;
+ 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);
+ }
+ spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true);
+ } else {
+ spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false);
+ }
+ // Paper end - Optional per player mob spawns
this.lastSpawnState = spawnState;
2024-10-27 18:11:15 +01:00
profiler.popPush("spawnAndTick");
boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 0f00db82e85c9e510c2e4fe4065291971c408dad..dab58457ed02d3f8153c07de101262b1a0182d71 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -368,6 +368,10 @@ public class ServerPlayer extends Player {
public boolean queueHealthUpdatePacket;
2021-06-14 04:41:44 +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 end - Optional per player mob spawns
2021-06-14 04:41:44 +02:00
// CraftBukkit start
public org.bukkit.craftbukkit.entity.CraftPlayer.TransferCookieConnection transferCookieConnection;
public String displayName;
diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java
index 6e6e028621ccc4597b2c24f54f53cb7f3de603e2..14e99450a8522b79e4c3805bd91439a950bc8f99 100644
--- a/net/minecraft/world/level/NaturalSpawner.java
+++ b/net/minecraft/world/level/NaturalSpawner.java
@@ -72,6 +72,14 @@ public final class NaturalSpawner {
public static NaturalSpawner.SpawnState createState(
int spawnableChunkCount, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkGetter, LocalMobCapCalculator calculator
) {
+ // Paper start - Optional per player mob spawns
+ return createState(spawnableChunkCount, entities, chunkGetter, calculator, false);
2021-06-14 04:41:44 +02:00
+ }
+
+ public static NaturalSpawner.SpawnState createState(
+ int spawnableChunkCount, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkGetter, LocalMobCapCalculator calculator, final boolean countMobs
+ ) {
+ // Paper end - Optional per player mob spawns
PotentialCalculator potentialCalculator = new PotentialCalculator();
Object2IntOpenHashMap<MobCategory> map = new Object2IntOpenHashMap<>();
@@ -93,11 +101,16 @@ public final class NaturalSpawner {
potentialCalculator.addCharge(entity.blockPosition(), mobSpawnCost.charge());
}
- if (entity instanceof Mob) {
+ if (calculator != null && entity instanceof Mob) { // Paper - Optional per player mob spawns
calculator.addMob(chunk.getPos(), category);
}
map.addTo(category, 1);
+ // Paper start - Optional per player mob spawns
+ if (countMobs) {
+ chunk.level.getChunkSource().chunkMap.updatePlayerMobTypeMap(entity);
+ }
+ // Paper end - Optional per player mob spawns
});
}
2021-06-14 04:41:44 +02:00
}
@@ -135,7 +148,7 @@ public final class NaturalSpawner {
if ((spawnFriendlies || !mobCategory.isFriendly())
&& (spawnEnemies || mobCategory.isFriendly())
&& (spawnPassives || !mobCategory.isPersistent())
- && spawnState.canSpawnForCategoryGlobal(mobCategory, limit)) { // Paper - Optional per player mob spawns; remove global check, check later during the local one
+ && (level.paperConfig().entities.spawning.perPlayerMobSpawns || spawnState.canSpawnForCategoryGlobal(mobCategory, limit))) { // Paper - Optional per player mob spawns; remove global check, check later during the local one
list.add(mobCategory);
// CraftBukkit end
}
@@ -149,8 +162,37 @@ public final class NaturalSpawner {
profilerFiller.push("spawner");
for (MobCategory mobCategory : categories) {
- if (spawnState.canSpawnForCategoryLocal(mobCategory, chunk.getPos())) {
- spawnCategoryForChunk(mobCategory, level, chunk, spawnState::canSpawn, spawnState::afterSpawn);
+ // Paper start - Optional per player mob spawns
+ final boolean canSpawn;
+ int maxSpawns = Integer.MAX_VALUE;
+ if (level.paperConfig().entities.spawning.perPlayerMobSpawns) {
+ // Copied from getFilteredSpawningCategories
+ int limit = mobCategory.getMaxInstancesPerChunk();
+ SpawnCategory spawnCategory = CraftSpawnCategory.toBukkit(mobCategory);
+ if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
+ limit = level.getWorld().getSpawnLimit(spawnCategory);
+ }
+
+ // Apply per-player limit
2021-06-14 04:41:44 +02:00
+ int minDiff = Integer.MAX_VALUE;
+ final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerPlayer> inRange =
+ level.moonrise$getNearbyPlayers().getPlayers(chunk.getPos(), ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE);
+ if (inRange != null) {
+ final net.minecraft.server.level.ServerPlayer[] backingSet = inRange.getRawDataUnchecked();
+ for (int k = 0, len = inRange.size(); k < len; k++) {
+ minDiff = Math.min(limit - level.getChunkSource().chunkMap.getMobCountNear(backingSet[k], mobCategory), minDiff);
+ }
2021-06-14 04:41:44 +02:00
+ }
+
+ maxSpawns = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff;
+ canSpawn = maxSpawns > 0;
+ } else {
+ canSpawn = spawnState.canSpawnForCategoryLocal(mobCategory, chunk.getPos());
2021-06-14 04:41:44 +02:00
+ }
+ if (canSpawn) {
+ spawnCategoryForChunk(mobCategory, level, chunk, spawnState::canSpawn, spawnState::afterSpawn,
+ maxSpawns, level.paperConfig().entities.spawning.perPlayerMobSpawns ? level.getChunkSource().chunkMap::updatePlayerMobTypeMap : null);
+ // Paper end - Optional per player mob spawns
2021-06-14 04:41:44 +02:00
}
}
@@ -170,9 +212,16 @@ public final class NaturalSpawner {
public static void spawnCategoryForChunk(
MobCategory category, ServerLevel level, LevelChunk chunk, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback
) {
+ // Paper start - Optional per player mob spawns
+ spawnCategoryForChunk(category, level, chunk, filter, callback, Integer.MAX_VALUE, null);
2021-06-14 04:41:44 +02:00
+ }
+ public static void spawnCategoryForChunk(
+ MobCategory category, ServerLevel level, LevelChunk chunk, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final Consumer<Entity> trackEntity
+ ) {
+ // Paper end - Optional per player mob spawns
BlockPos randomPosWithin = getRandomPosWithin(level, chunk);
if (randomPosWithin.getY() >= level.getMinY() + 1) {
- spawnCategoryForPosition(category, level, chunk, randomPosWithin, filter, callback);
+ spawnCategoryForPosition(category, level, chunk, randomPosWithin, filter, callback, maxSpawns, trackEntity); // Paper - Optional per player mob spawns
2021-06-14 04:41:44 +02:00
}
}
@@ -189,6 +238,12 @@ public final class NaturalSpawner {
NaturalSpawner.SpawnPredicate filter,
NaturalSpawner.AfterSpawnCallback callback
) {
+ spawnCategoryForPosition(category, level, chunk, pos, filter, callback, Integer.MAX_VALUE, null);
2021-06-14 04:41:44 +02:00
+ }
+ public static void spawnCategoryForPosition(
+ MobCategory category, ServerLevel level, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final @Nullable Consumer<Entity> trackEntity
+ ) {
+ // Paper end - Optional per player mob spawns
StructureManager structureManager = level.structureManager();
ChunkGenerator generator = level.getChunkSource().getGenerator();
int y = pos.getY();
@@ -252,9 +307,14 @@ public final class NaturalSpawner {
++i;
++i3;
callback.run(mobForSpawn, chunk);
+ // Paper start - Optional per player mob spawns
+ if (trackEntity != null) {
+ trackEntity.accept(mobForSpawn);
+ }
+ // Paper end - Optional per player mob spawns
}
// CraftBukkit end
- if (i >= mobForSpawn.getMaxSpawnClusterSize()) {
+ if (i >= mobForSpawn.getMaxSpawnClusterSize() || i >= maxSpawns) { // Paper - Optional per player mob spawns
return;
}
@@ -565,7 +625,7 @@ public final class NaturalSpawner {
this.spawnPotential.addCharge(blockPos, d);
MobCategory category = type.getCategory();
this.mobCategoryCounts.addTo(category, 1);
- this.localMobCapCalculator.addMob(new ChunkPos(blockPos), category);
+ if (this.localMobCapCalculator != null) this.localMobCapCalculator.addMob(new ChunkPos(blockPos), category); // Paper - Optional per player mob spawns
}
public int getSpawnableChunkCount() {