1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
|
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
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());
}
- // 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) {
+ 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]);
+ }
}
- // 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;
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;
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
// 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);
+ }
+
+ 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
});
}
}
@@ -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
+ 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);
+ }
+ }
+
+ maxSpawns = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff;
+ canSpawn = maxSpawns > 0;
+ } else {
+ canSpawn = spawnState.canSpawnForCategoryLocal(mobCategory, chunk.getPos());
+ }
+ 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
}
}
@@ -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);
+ }
+ 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
}
}
@@ -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);
+ }
+ 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() {
|