mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-19 15:09:25 +00:00
* Unify comment format * More configurable * Remove one extra execute mid-tick task call in level tick when PWT is disabled This may cause extremely rare, weird, strange, magic, mysterious issues with plugins, or potentially more. One example is that it may cause boss mob duplication issue when `ONE MOB ONLY` was enabled in plugin SupremeBosses
624 lines
38 KiB
Diff
624 lines
38 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: hayanesuru <hayanesuru@outlook.jp>
|
|
Date: Tue, 3 Jun 2025 15:20:59 +0900
|
|
Subject: [PATCH] optimize mob spawning
|
|
|
|
Dreeam TODO (Note): This patch needs to replace puffer's patch, or move to there
|
|
|
|
Avoid getChunk calls if its position is same as the chunk used for mob spawning
|
|
|
|
Don't sync load chunks for mob spawning checks
|
|
|
|
Fix data race in async mob spawning
|
|
by adding chunk position to the mob count map
|
|
then apply result on server thread.
|
|
|
|
Generally faster than the non-async approach
|
|
|
|
iterate over all entities, get their chunk, and increment the count
|
|
|
|
diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
|
|
index 5c369b3d94e369c3f240821ad90b9d96223f24ca..9803c395fce103cb7bc746f43a017ff9ed99728c 100644
|
|
--- a/net/minecraft/server/level/ChunkMap.java
|
|
+++ b/net/minecraft/server/level/ChunkMap.java
|
|
@@ -278,6 +278,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
}
|
|
// Paper end - per player mob count backoff
|
|
public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) {
|
|
+ // Leaf - diff - async mob spawning - optimize mob spawning
|
|
return player.mobCounts[mobCategory.ordinal()] + player.mobBackoffCounts[mobCategory.ordinal()]; // Paper - per player mob count backoff
|
|
}
|
|
// Paper end - Optional per player mob spawns
|
|
diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
|
|
index 908cd08e33fed1c4cd4bd34c3e63cbbe84ffead4..f5b58c181726536bedabd4b6f64769e312ef4257 100644
|
|
--- a/net/minecraft/server/level/ServerChunkCache.java
|
|
+++ b/net/minecraft/server/level/ServerChunkCache.java
|
|
@@ -70,11 +70,11 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
|
|
private final long[] lastChunkPos = new long[4];
|
|
private final ChunkStatus[] lastChunkStatus = new ChunkStatus[4];
|
|
private final ChunkAccess[] lastChunk = new ChunkAccess[4];
|
|
- private final List<LevelChunk> spawningChunks = new ObjectArrayList<>();
|
|
+ private final List<LevelChunk> spawningChunks = it.unimi.dsi.fastutil.objects.ReferenceArrayList.wrap(new LevelChunk[0]); // Leaf - optimize mob spawning
|
|
private final Set<ChunkHolder> chunkHoldersToBroadcast = new ReferenceOpenHashSet<>();
|
|
@Nullable
|
|
@VisibleForDebug
|
|
- private NaturalSpawner.SpawnState lastSpawnState;
|
|
+ private volatile NaturalSpawner.SpawnState lastSpawnState; // Leaf - optimize mob spawning
|
|
// Paper start
|
|
public final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<net.minecraft.world.level.chunk.LevelChunk> fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>();
|
|
public int getFullChunksCount() {
|
|
@@ -499,6 +499,23 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
|
|
}
|
|
|
|
private void tickChunks() {
|
|
+ // Leaf start - optimize mob spawning
|
|
+ if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled && this.level.tickRateManager().runsNormally()) {
|
|
+ for (ServerPlayer player : this.level.players) {
|
|
+ // Paper start - per player mob spawning backoff
|
|
+ for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) {
|
|
+ player.mobCounts[ii] = 0;
|
|
+
|
|
+ int newBackoff = player.mobBackoffCounts[ii] - 1; // TODO make configurable bleed // TODO use nonlinear algorithm?
|
|
+ if (newBackoff < 0) {
|
|
+ newBackoff = 0;
|
|
+ }
|
|
+ player.mobBackoffCounts[ii] = newBackoff;
|
|
+ }
|
|
+ // Paper end - per player mob spawning backoff
|
|
+ }
|
|
+ }
|
|
+ // Leaf end - optimize mob spawning
|
|
long gameTime = this.level.getGameTime();
|
|
long l = gameTime - this.lastInhabitedUpdate;
|
|
this.lastInhabitedUpdate = gameTime;
|
|
@@ -512,8 +529,8 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
|
|
}
|
|
|
|
// Pufferfish start - optimize mob spawning
|
|
- if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled) {
|
|
- for (ServerPlayer player : this.level.players) {
|
|
+ if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled && this.level.tickRateManager().runsNormally()) {
|
|
+ /*for (ServerPlayer player : this.level.players) {
|
|
// Paper start - per player mob spawning backoff
|
|
for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) {
|
|
player.mobCounts[ii] = 0;
|
|
@@ -525,34 +542,27 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
|
|
player.mobBackoffCounts[ii] = newBackoff;
|
|
}
|
|
// Paper end - per player mob spawning backoff
|
|
- }
|
|
+ }*/
|
|
if (firstRunSpawnCounts) {
|
|
firstRunSpawnCounts = false;
|
|
_pufferfish_spawnCountsReady.set(true);
|
|
}
|
|
if (_pufferfish_spawnCountsReady.getAndSet(false)) {
|
|
+ final int mapped = distanceManager.getNaturalSpawnChunkCount();
|
|
+ final Iterable<Entity> entities = this.level.getAllEntities();
|
|
net.minecraft.server.MinecraftServer.getServer().mobSpawnExecutor.submit(() -> {
|
|
- int mapped = distanceManager.getNaturalSpawnChunkCount();
|
|
- ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet.Iterator<Entity> objectiterator =
|
|
- level.entityTickList.entities.iterator(ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS);
|
|
- try {
|
|
- gg.pufferfish.pufferfish.util.IterableWrapper<Entity> wrappedIterator =
|
|
- new gg.pufferfish.pufferfish.util.IterableWrapper<>(objectiterator);
|
|
- // Fix: Use proper mob cap calculator based on configuration
|
|
- LocalMobCapCalculator mobCapCalculator = !level.paperConfig().entities.spawning.perPlayerMobSpawns ?
|
|
- new LocalMobCapCalculator(chunkMap) : null;
|
|
-
|
|
- // This ensures the caps are properly enforced by using the correct calculator
|
|
- lastSpawnState = NaturalSpawner.createState(
|
|
- mapped,
|
|
- wrappedIterator,
|
|
- ServerChunkCache.this::getFullChunk,
|
|
- mobCapCalculator, // This is the key fix - was previously null
|
|
- level.paperConfig().entities.spawning.perPlayerMobSpawns
|
|
- );
|
|
- } finally {
|
|
- objectiterator.finishedIterating();
|
|
- }
|
|
+ // Fix: Use proper mob cap calculator based on configuration
|
|
+ LocalMobCapCalculator mobCapCalculator = !level.paperConfig().entities.spawning.perPlayerMobSpawns ?
|
|
+ new LocalMobCapCalculator(chunkMap) : null;
|
|
+
|
|
+ // This ensures the caps are properly enforced by using the correct calculator
|
|
+ lastSpawnState = NaturalSpawner.createState1( // Leaf - optimize mob spawning
|
|
+ mapped,
|
|
+ entities,
|
|
+ this.level, // Leaf - optimize mob spawning
|
|
+ mobCapCalculator, // This is the key fix - was previously null
|
|
+ level.paperConfig().entities.spawning.perPlayerMobSpawns
|
|
+ );
|
|
_pufferfish_spawnCountsReady.set(true);
|
|
});
|
|
}
|
|
@@ -611,6 +621,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
|
|
chunkRange = Math.min(chunkRange, 8);
|
|
entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange);
|
|
entityPlayer.playerNaturallySpawnedEvent.callEvent();
|
|
+ this.level.natureSpawnChunkMap.addPlayer(entityPlayer); // Leaf - optimize mob spawning
|
|
}
|
|
// Paper end - PlayerNaturallySpawnCreaturesEvent
|
|
boolean flag = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
|
|
@@ -622,16 +633,34 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
|
|
List<LevelChunk> list = this.spawningChunks;
|
|
|
|
try {
|
|
- this.chunkMap.collectSpawningChunks(list);
|
|
+ // Leaf start - optimize mob spawning
|
|
+ // this.chunkMap.collectSpawningChunks(list);
|
|
+ this.level.natureSpawnChunkMap.build();
|
|
+ this.level.natureSpawnChunkMap.collectSpawningChunks(this.level.moonrise$getPlayerTickingChunks(), list);
|
|
// Paper start - chunk tick iteration optimisation
|
|
this.shuffleRandom.setSeed(this.level.random.nextLong());
|
|
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
|
|
// Paper end - chunk tick iteration optimisation
|
|
|
|
- for (LevelChunk levelChunk : list) {
|
|
- this.tickSpawningChunk(levelChunk, timeInhabited, filteredSpawningCategories, lastSpawnState); // Pufferfish
|
|
+ if (!org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled || _pufferfish_spawnCountsReady.get()) {
|
|
+ NaturalSpawner.SpawnState currentState = lastSpawnState;
|
|
+ if (currentState != null) {
|
|
+ currentState.applyPerPlayerMobCount(level);
|
|
+ if (list instanceof it.unimi.dsi.fastutil.objects.ReferenceArrayList<LevelChunk> list1) {
|
|
+ LevelChunk[] raw = list1.elements();
|
|
+ for (int i = 0, j = list1.size(); i < j; i++) {
|
|
+ this.tickSpawningChunk(raw[i], timeInhabited, filteredSpawningCategories, currentState); // Pufferfish
|
|
+ }
|
|
+ } else {
|
|
+ for (LevelChunk levelChunk : list) {
|
|
+ this.tickSpawningChunk(levelChunk, timeInhabited, filteredSpawningCategories, currentState); // Pufferfish
|
|
+ }
|
|
+ }
|
|
+ }
|
|
}
|
|
+ // Leaf end - optimize mob spawning
|
|
} finally {
|
|
+ this.level.natureSpawnChunkMap.clear(); // Leaf - optimize mob spawning
|
|
list.clear();
|
|
}
|
|
|
|
@@ -649,7 +678,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
|
|
}
|
|
|
|
if (!spawnCategories.isEmpty()) {
|
|
- if (this.level.getWorldBorder().isWithinBounds(pos) && (!org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled || _pufferfish_spawnCountsReady.get())) { // Paper - rewrite chunk system // Pufferfish
|
|
+ if (this.level.getWorldBorder().isWithinBounds(pos)) { // Paper - rewrite chunk system
|
|
NaturalSpawner.spawnForChunk(this.level, chunk, spawnState, spawnCategories);
|
|
}
|
|
}
|
|
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
|
|
index 209d18228aa28b063f8f3e60df6a3e2c5aa770fc..549b1112e246c2c01601c13b35aa31f89c813956 100644
|
|
--- a/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/net/minecraft/server/level/ServerLevel.java
|
|
@@ -1102,6 +1102,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
|
|
private int currentIceAndSnowTick = 0; protected void resetIceAndSnowTick() { this.currentIceAndSnowTick = this.simpleRandom.nextInt(16); } // Gale - Airplane - optimize random calls in chunk ticking
|
|
|
|
public final org.dreeam.leaf.world.DespawnMap despawnMap = new org.dreeam.leaf.world.DespawnMap(paperConfig()); // Leaf - optimize despawn
|
|
+ public final org.dreeam.leaf.world.NatureSpawnChunkMap natureSpawnChunkMap = new org.dreeam.leaf.world.NatureSpawnChunkMap(); // Leaf - optimize mob spawning
|
|
public void tickChunk(LevelChunk chunk, int randomTickSpeed) {
|
|
final net.minecraft.world.level.levelgen.BitRandomSource simpleRandom = this.simpleRandom; // Paper - optimise random ticking // Leaf - Faster random generator - upcasting
|
|
ChunkPos pos = chunk.getPos();
|
|
diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java
|
|
index bb655318f49242858e2c25d5469705c0c314ed85..a015f0bbff3bb58fd4d28c59620f75dbb125f869 100644
|
|
--- a/net/minecraft/world/level/NaturalSpawner.java
|
|
+++ b/net/minecraft/world/level/NaturalSpawner.java
|
|
@@ -68,6 +68,7 @@ public final class NaturalSpawner {
|
|
return createState(spawnableChunkCount, entities, chunkGetter, calculator, false);
|
|
}
|
|
|
|
+ @Deprecated // Leaf - optimize mob spawning
|
|
public static NaturalSpawner.SpawnState createState(
|
|
int spawnableChunkCount, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkGetter, LocalMobCapCalculator calculator, final boolean countMobs
|
|
) {
|
|
@@ -108,9 +109,71 @@ public final class NaturalSpawner {
|
|
}
|
|
}
|
|
|
|
- return new NaturalSpawner.SpawnState(spawnableChunkCount, map, potentialCalculator, calculator);
|
|
+ return new NaturalSpawner.SpawnState(spawnableChunkCount, map, potentialCalculator, calculator, new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>()); // Leaf - optimize mob spawning
|
|
}
|
|
|
|
+ // Leaf start - optimize mob spawning
|
|
+ public static NaturalSpawner.SpawnState createState1(
|
|
+ int spawnableChunkCount, Iterable<Entity> entities, ServerLevel level, LocalMobCapCalculator calculator, final boolean countMobs
|
|
+ ) {
|
|
+ // Paper end - Optional per player mob spawns
|
|
+ PotentialCalculator potentialCalculator = new PotentialCalculator();
|
|
+ Object2IntOpenHashMap<MobCategory> map = new Object2IntOpenHashMap<>();
|
|
+ it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<int[]> chunkCap = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>();
|
|
+ boolean countAllMobsForSpawning = level.paperConfig().entities.spawning.countAllMobsForSpawning;
|
|
+ for (Entity entity : entities) {
|
|
+ if (!(entity instanceof Mob mob && (mob.isPersistenceRequired() || mob.requiresCustomPersistence()))) {
|
|
+ MobCategory category = entity.getType().getCategory();
|
|
+ if (category != MobCategory.MISC) {
|
|
+ // Paper start - Only count natural spawns
|
|
+ if (!countAllMobsForSpawning &&
|
|
+ !(entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL ||
|
|
+ entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) {
|
|
+ continue;
|
|
+ }
|
|
+ // Paper end - Only count natural spawns
|
|
+ BlockPos blockPos = entity.blockPosition();
|
|
+ LevelChunk chunk = level.chunkSource.fullChunks.get(ChunkPos.asLong(blockPos));
|
|
+ if (chunk != null) {
|
|
+ MobSpawnSettings.MobSpawnCost mobSpawnCost = getRoughBiome(blockPos, chunk).getMobSettings().getMobSpawnCost(entity.getType());
|
|
+ if (mobSpawnCost != null) {
|
|
+ potentialCalculator.addCharge(entity.blockPosition(), mobSpawnCost.charge());
|
|
+ }
|
|
+
|
|
+ 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) {
|
|
+ final int index = entity.getType().getCategory().ordinal();
|
|
+ ++chunkCap.computeIfAbsent(chunk.getPos().longKey, k -> new int[net.minecraft.server.level.ServerPlayer.MOBCATEGORY_TOTAL_ENUMS])[index];
|
|
+ /*
|
|
+ final int index = entity.getType().getCategory().ordinal();
|
|
+ final var inRange = level.moonrise$getNearbyPlayers().getPlayers(entity.chunkPosition(), NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE);
|
|
+ if (inRange == null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final net.minecraft.server.level.ServerPlayer[] backingSet = inRange.getRawDataUnchecked();
|
|
+ for (int i = 0, len = inRange.size(); i < len; i++) {
|
|
+ final net.minecraft.server.level.ServerPlayer player = backingSet[i];
|
|
+ if (player == null) continue;
|
|
+ ++playerCap.computeIfAbsent(player, k -> new int[net.minecraft.server.level.ServerPlayer.MOBCATEGORY_TOTAL_ENUMS])[index];
|
|
+ }
|
|
+ */
|
|
+ }
|
|
+ // Paper end - Optional per player mob spawns
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return new NaturalSpawner.SpawnState(spawnableChunkCount, map, potentialCalculator, calculator, chunkCap);
|
|
+ }
|
|
+ // Leaf end - optimize mob spawning
|
|
+
|
|
static Biome getRoughBiome(BlockPos pos, ChunkAccess chunk) {
|
|
return chunk.getNoiseBiome(QuartPos.fromBlock(pos.getX()), QuartPos.fromBlock(pos.getY()), QuartPos.fromBlock(pos.getZ())).value();
|
|
}
|
|
@@ -265,28 +328,68 @@ public final class NaturalSpawner {
|
|
MobCategory category, ServerLevel level, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final @Nullable Consumer<Entity> trackEntity, final boolean nothing
|
|
// Paper PR end - throttle failed spawn attempts
|
|
) {
|
|
+ // Leaf start - optimize mob spawning
|
|
+ if (!(chunk instanceof LevelChunk levelChunk)) {
|
|
+ // unreachable
|
|
+ return 0;
|
|
+ }
|
|
+ // Leaf end - optimize mob spawning
|
|
// Paper end - Optional per player mob spawns
|
|
StructureManager structureManager = level.structureManager();
|
|
ChunkGenerator generator = level.getChunkSource().getGenerator();
|
|
int y = pos.getY();
|
|
+ // Leaf start - optimize mob spawning
|
|
+ int posX = pos.getX();
|
|
+ int posZ = pos.getZ();
|
|
int i = 0; // Paper PR - throttle failed spawn attempts
|
|
- BlockState blockState = level.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn
|
|
+ BlockState blockState = level.getWorldBorder().isWithinBounds(pos) ? (ChunkPos.asLong(pos) == levelChunk.getPos().longKey ? levelChunk.getBlockStateFinal(posX, y, posZ) : level.getBlockStateIfLoaded(pos)) : null; // Paper - don't load chunks for mob spawn // Leaf
|
|
if (blockState != null && !blockState.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn
|
|
BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
|
|
//int i = 0; // Paper PR - throttle failed spawn attempts - move up
|
|
|
|
+ // 3 * (2 + 3 * [1, 4] * 4)
|
|
+ long rand = level.random.nextLong();
|
|
+ int bits = 0;
|
|
for (int i1 = 0; i1 < 3; i1++) {
|
|
- int x = pos.getX();
|
|
- int z = pos.getZ();
|
|
- int i2 = 6;
|
|
+ int x = posX;
|
|
+ int z = posZ;
|
|
+ //int i2 = 6;
|
|
MobSpawnSettings.SpawnerData spawnerData = null;
|
|
SpawnGroupData spawnGroupData = null;
|
|
- int ceil = Mth.ceil(level.random.nextFloat() * 4.0F);
|
|
+ //int ceil = Mth.ceil(level.random.nextFloat() * 4.0F);
|
|
+ int ceil = (int) (((rand >>> bits) & 0x3L) + 1);
|
|
+ bits += 2;
|
|
+ if (bits >= 62) {
|
|
+ rand = level.random.nextLong();
|
|
+ bits = 0;
|
|
+ }
|
|
int i3 = 0;
|
|
|
|
for (int i4 = 0; i4 < ceil; i4++) {
|
|
- x += level.random.nextInt(6) - level.random.nextInt(6);
|
|
- z += level.random.nextInt(6) - level.random.nextInt(6);
|
|
+ int rand1=0,rand2=0,rand3=0,rand4=0,valuesNeeded=4;
|
|
+ while (valuesNeeded > 0) {
|
|
+ // [0, 61] 3 remains
|
|
+ int threeBits = (int) ((rand >>> bits) & 0x7L);
|
|
+ bits += 3;
|
|
+ if (threeBits != 7 && threeBits != 6) {
|
|
+ switch (valuesNeeded) {
|
|
+ case 1 -> rand4 = threeBits;
|
|
+ case 2 -> rand3 = threeBits;
|
|
+ case 3 -> rand2 = threeBits;
|
|
+ case 4 -> rand1 = threeBits;
|
|
+ }
|
|
+ valuesNeeded--;
|
|
+ }
|
|
+ if (bits >= 62) {
|
|
+ rand = level.random.nextLong();
|
|
+ bits = 0;
|
|
+ }
|
|
+ }
|
|
+ x += rand1 - rand2;
|
|
+ z += rand3 - rand4;
|
|
+ // x += level.random.nextInt(6) - level.random.nextInt(6);
|
|
+ // z += level.random.nextInt(6) - level.random.nextInt(6);
|
|
+ // Leaf end - optimize mob spawning
|
|
mutableBlockPos.set(x, y, z);
|
|
double d = x + 0.5;
|
|
double d1 = z + 0.5;
|
|
@@ -295,8 +398,8 @@ public final class NaturalSpawner {
|
|
double d2 = nearestPlayer.distanceToSqr(d, y, d1);
|
|
if (level.isLoadedAndInBounds(mutableBlockPos) && isRightDistanceToPlayerAndSpawnPoint(level, chunk, mutableBlockPos, d2)) { // Paper - don't load chunks for mob spawn
|
|
if (spawnerData == null) {
|
|
- Optional<MobSpawnSettings.SpawnerData> randomSpawnMobAt = getRandomSpawnMobAt(
|
|
- level, structureManager, generator, category, level.random, mutableBlockPos
|
|
+ Optional<MobSpawnSettings.SpawnerData> randomSpawnMobAt = getRandomSpawnMobAtWithChunk( // Leaf - optimize mob spawning
|
|
+ level, structureManager, generator, category, level.random, mutableBlockPos, levelChunk // Leaf - optimize mob spawning
|
|
);
|
|
if (randomSpawnMobAt.isEmpty()) {
|
|
break;
|
|
@@ -307,7 +410,7 @@ public final class NaturalSpawner {
|
|
}
|
|
|
|
// Paper start - PreCreatureSpawnEvent
|
|
- PreSpawnStatus doSpawning = isValidSpawnPostitionForType(level, category, structureManager, generator, spawnerData, mutableBlockPos, d2);
|
|
+ PreSpawnStatus doSpawning = isValidSpawnPostitionForTypeWithChunk(level, category, structureManager, generator, spawnerData, mutableBlockPos, d2, levelChunk); // Leaf
|
|
// Paper start - per player mob count backoff
|
|
if (doSpawning == PreSpawnStatus.ABORT || doSpawning == PreSpawnStatus.CANCELLED) {
|
|
level.getChunkSource().chunkMap.updateFailurePlayerMobTypeMap(mutableBlockPos.getX() >> 4, mutableBlockPos.getZ() >> 4, category);
|
|
@@ -414,6 +517,44 @@ public final class NaturalSpawner {
|
|
&& level.noCollision(entityType.getSpawnAABB(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5));
|
|
return success ? PreSpawnStatus.SUCCESS : PreSpawnStatus.FAIL; // Paper - PreCreatureSpawnEvent
|
|
}
|
|
+ // Leaf start - optimize mob spawning
|
|
+ private static PreSpawnStatus isValidSpawnPostitionForTypeWithChunk(
|
|
+ // Paper end - PreCreatureSpawnEvent
|
|
+ ServerLevel level,
|
|
+ MobCategory category,
|
|
+ StructureManager structureManager,
|
|
+ ChunkGenerator generator,
|
|
+ MobSpawnSettings.SpawnerData data,
|
|
+ BlockPos.MutableBlockPos pos,
|
|
+ double distance,
|
|
+ LevelChunk chunk
|
|
+ ) {
|
|
+ EntityType<?> entityType = data.type();
|
|
+ // Paper start - PreCreatureSpawnEvent
|
|
+ com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent(
|
|
+ org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level),
|
|
+ org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(entityType), org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL
|
|
+ );
|
|
+ if (!event.callEvent()) {
|
|
+ if (event.shouldAbortSpawn()) {
|
|
+ return PreSpawnStatus.ABORT;
|
|
+ }
|
|
+ return PreSpawnStatus.CANCELLED;
|
|
+ }
|
|
+ final boolean success = entityType.getCategory() != MobCategory.MISC
|
|
+ // Paper end - PreCreatureSpawnEvent
|
|
+ && (
|
|
+ entityType.canSpawnFarFromPlayer()
|
|
+ || !(distance > entityType.getCategory().getDespawnDistance() * entityType.getCategory().getDespawnDistance())
|
|
+ )
|
|
+ && entityType.canSummon()
|
|
+ && mobsAtWithChunk(level, structureManager, generator, category, pos, null, chunk).contains(data)
|
|
+ && SpawnPlacements.isSpawnPositionOk(entityType, level, pos)
|
|
+ && SpawnPlacements.checkSpawnRules(entityType, level, EntitySpawnReason.NATURAL, pos, level.random)
|
|
+ && level.noCollision(entityType.getSpawnAABB(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5));
|
|
+ return success ? PreSpawnStatus.SUCCESS : PreSpawnStatus.FAIL; // Paper - PreCreatureSpawnEvent
|
|
+ }
|
|
+ // Leaf end - optimize mob spawning
|
|
|
|
@Nullable
|
|
private static Mob getMobForSpawn(ServerLevel level, EntityType<?> entityType) {
|
|
@@ -449,6 +590,17 @@ public final class NaturalSpawner {
|
|
: mobsAt(level, structureManager, generator, category, pos, biome).getRandom(random);
|
|
}
|
|
|
|
+ // Leaf start - optimize mob spawning
|
|
+ private static Optional<MobSpawnSettings.SpawnerData> getRandomSpawnMobAtWithChunk(
|
|
+ ServerLevel level, StructureManager structureManager, ChunkGenerator generator, MobCategory category, RandomSource random, BlockPos pos, LevelChunk chunk
|
|
+ ) {
|
|
+ Holder<Biome> biome = org.dreeam.leaf.config.modules.opt.OptimizeBiome.mobSpawn ? level.getBiomeCached(chunk, pos) : level.getBiome(pos); // Leaf - cache getBiome
|
|
+ return category == MobCategory.WATER_AMBIENT && biome.is(BiomeTags.REDUCED_WATER_AMBIENT_SPAWNS) && random.nextFloat() < 0.98F
|
|
+ ? Optional.empty()
|
|
+ : mobsAtWithChunk(level, structureManager, generator, category, pos, biome, chunk).getRandom(random);
|
|
+ }
|
|
+ // Leaf end - optimize mob spawning
|
|
+
|
|
private static boolean canSpawnMobAt(
|
|
ServerLevel level, StructureManager structureManager, ChunkGenerator generator, MobCategory category, MobSpawnSettings.SpawnerData data, BlockPos pos
|
|
) {
|
|
@@ -463,8 +615,22 @@ public final class NaturalSpawner {
|
|
: generator.getMobsAt(biome != null ? biome : (org.dreeam.leaf.config.modules.opt.OptimizeBiome.mobSpawn ? level.getBiomeCached(null, pos) : level.getBiome(pos)), structureManager, cetagory, pos); // Leaf - cache getBiome
|
|
}
|
|
|
|
+ // Leaf start - optimize mob spawning
|
|
+ private static WeightedList<MobSpawnSettings.SpawnerData> mobsAtWithChunk(
|
|
+ ServerLevel level, StructureManager structureManager, ChunkGenerator generator, MobCategory cetagory, BlockPos pos, @Nullable Holder<Biome> biome, LevelChunk chunk
|
|
+ ) {
|
|
+ return isInNetherFortressBoundsChunk(pos, level, cetagory, structureManager, chunk)
|
|
+ ? NetherFortressStructure.FORTRESS_ENEMIES
|
|
+ : generator.getMobsAtChunk(biome != null ? biome : (org.dreeam.leaf.config.modules.opt.OptimizeBiome.mobSpawn ? level.getBiomeCached(chunk, pos) : level.getBiome(pos)), structureManager, cetagory, pos, chunk); // Leaf - cache getBiome
|
|
+ }
|
|
+ // Leaf end - optimize mob spawning
|
|
+
|
|
public static boolean isInNetherFortressBounds(BlockPos pos, ServerLevel level, MobCategory category, StructureManager structureManager) {
|
|
- if (category == MobCategory.MONSTER && level.getBlockState(pos.below()).is(Blocks.NETHER_BRICKS)) {
|
|
+ // Leaf start - optimize mob spawning
|
|
+ if (category == MobCategory.MONSTER) {
|
|
+ BlockState blockState = level.getBlockStateIfLoaded(pos.below());
|
|
+ if (blockState == null || !blockState.is(Blocks.NETHER_BRICKS)) return false;
|
|
+ // Leaf end - optimize mob spawning
|
|
Structure structure = structureManager.registryAccess().lookupOrThrow(Registries.STRUCTURE).getValue(BuiltinStructures.FORTRESS);
|
|
return structure != null && structureManager.getStructureAt(pos, structure).isValid();
|
|
} else {
|
|
@@ -472,6 +638,19 @@ public final class NaturalSpawner {
|
|
}
|
|
}
|
|
|
|
+ // Leaf start - optimize mob spawning
|
|
+ public static boolean isInNetherFortressBoundsChunk(BlockPos pos, ServerLevel level, MobCategory category, StructureManager structureManager, LevelChunk chunk) {
|
|
+ if (category == MobCategory.MONSTER) {
|
|
+ @Nullable BlockState blockState = chunk.getPos().longKey == ChunkPos.asLong(pos) ? chunk.getBlockStateFinal(pos.getX(), pos.getY() - 1, pos.getZ()) : level.getBlockStateIfLoaded(pos.below());
|
|
+ if (blockState == null || !blockState.is(Blocks.NETHER_BRICKS)) return false;
|
|
+ Structure structure = structureManager.registryAccess().lookupOrThrow(Registries.STRUCTURE).getValue(BuiltinStructures.FORTRESS);
|
|
+ return structure != null && structureManager.getStructureAt(pos, structure).isValid();
|
|
+ } else {
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+ // Leaf end - optimize mob spawning
|
|
+
|
|
private static BlockPos getRandomPosWithin(Level level, LevelChunk chunk) {
|
|
ChunkPos pos = chunk.getPos();
|
|
int i = pos.getMinBlockX() + level.random.nextInt(16);
|
|
@@ -612,18 +791,21 @@ public final class NaturalSpawner {
|
|
@Nullable
|
|
private EntityType<?> lastCheckedType;
|
|
private double lastCharge;
|
|
+ public final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<int[]> chunkCap; // Leaf - optimize mob spawning
|
|
|
|
SpawnState(
|
|
int spawnableChunkCount,
|
|
Object2IntOpenHashMap<MobCategory> mobCategoryCounts,
|
|
PotentialCalculator spawnPotential,
|
|
- LocalMobCapCalculator localMobCapCalculator
|
|
+ LocalMobCapCalculator localMobCapCalculator,
|
|
+ it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<int[]> playerCap // Leaf - optimize mob spawning
|
|
) {
|
|
this.spawnableChunkCount = spawnableChunkCount;
|
|
this.mobCategoryCounts = mobCategoryCounts;
|
|
this.spawnPotential = spawnPotential;
|
|
this.localMobCapCalculator = localMobCapCalculator;
|
|
this.unmodifiableMobCategoryCounts = Object2IntMaps.unmodifiable(mobCategoryCounts);
|
|
+ this.chunkCap = playerCap; // Leaf - optimize mob spawning
|
|
}
|
|
|
|
private boolean canSpawn(EntityType<?> entityType, BlockPos pos, ChunkAccess chunk) {
|
|
@@ -680,5 +862,32 @@ public final class NaturalSpawner {
|
|
boolean canSpawnForCategoryLocal(MobCategory category, ChunkPos chunkPos) {
|
|
return this.localMobCapCalculator.canSpawn(category, chunkPos);
|
|
}
|
|
+
|
|
+ // Leaf start - optimize mob spawning
|
|
+ public void applyPerPlayerMobCount(ServerLevel level) {
|
|
+ if (chunkCap.isEmpty()) {
|
|
+ return;
|
|
+ }
|
|
+ final var iterator = chunkCap.long2ObjectEntrySet().fastIterator();
|
|
+ final ca.spottedleaf.moonrise.common.misc.NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers();
|
|
+ while (iterator.hasNext()) {
|
|
+ var entry = iterator.next();
|
|
+ long chunk = entry.getLongKey();
|
|
+ int[] cap = entry.getValue();
|
|
+ ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerPlayer> players = nearbyPlayers.getPlayersByChunk(ChunkPos.getX(chunk), ChunkPos.getZ(chunk), ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE);
|
|
+ if (players == null) {
|
|
+ continue;
|
|
+ }
|
|
+ int playersSize = players.size();
|
|
+ net.minecraft.server.level.ServerPlayer[] playersRawDataUnchecked = players.getRawDataUnchecked();
|
|
+ for (int i = 0; i < playersSize; i++) {
|
|
+ int[] p = playersRawDataUnchecked[i].mobCounts;
|
|
+ for (int j = 0; j < net.minecraft.server.level.ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; j++) {
|
|
+ p[j] += cap[j];
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Leaf end - optimize mob spawning
|
|
}
|
|
}
|
|
diff --git a/net/minecraft/world/level/StructureManager.java b/net/minecraft/world/level/StructureManager.java
|
|
index fbe93098ce0366054a6da857cd808af1431b6612..57de70773d2766a8f3a41e61efc16ceb7a9f80c8 100644
|
|
--- a/net/minecraft/world/level/StructureManager.java
|
|
+++ b/net/minecraft/world/level/StructureManager.java
|
|
@@ -90,6 +90,7 @@ public class StructureManager {
|
|
|
|
@Nullable
|
|
public StructureStart getStartForStructure(SectionPos sectionPos, Structure structure, StructureAccess structureAccess) {
|
|
+ // Leaf - optimize mob spawning - diff
|
|
return structureAccess.getStartForStructure(structure);
|
|
}
|
|
|
|
@@ -181,6 +182,12 @@ public class StructureManager {
|
|
//SectionPos sectionPos = SectionPos.of(pos); // Leaf - optimise ChunkGenerator#getMobsAt
|
|
return this.level.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences(); // Leaf - optimise ChunkGenerator#getMobsAt
|
|
}
|
|
+ // Leaf start - optimize mob spawning
|
|
+ public Map<Structure, LongSet> getAllStructuresAtChunk(net.minecraft.world.level.chunk.ChunkAccess chunk) {
|
|
+ //SectionPos sectionPos = SectionPos.of(pos); // Leaf - optimise ChunkGenerator#getMobsAt
|
|
+ return chunk.getAllReferences(); // Leaf - optimise ChunkGenerator#getMobsAt
|
|
+ }
|
|
+ // Leaf end - optimize mob spawning
|
|
|
|
public StructureCheckResult checkStructurePresence(ChunkPos chunkPos, Structure structure, StructurePlacement placement, boolean skipKnownStructures) {
|
|
return this.structureCheck.checkStart(chunkPos, structure, placement, skipKnownStructures);
|
|
diff --git a/net/minecraft/world/level/biome/MobSpawnSettings.java b/net/minecraft/world/level/biome/MobSpawnSettings.java
|
|
index db3b8a237d63255aa9ffd70c88a093002a6bd770..4a69f404eee00d8972e9501a76031d4339136b6f 100644
|
|
--- a/net/minecraft/world/level/biome/MobSpawnSettings.java
|
|
+++ b/net/minecraft/world/level/biome/MobSpawnSettings.java
|
|
@@ -52,7 +52,7 @@ public class MobSpawnSettings {
|
|
Map<EntityType<?>, MobSpawnSettings.MobSpawnCost> mobSpawnCosts
|
|
) {
|
|
this.creatureGenerationProbability = creatureGenerationProbability;
|
|
- this.spawners = ImmutableMap.copyOf(spawners);
|
|
+ this.spawners = com.google.common.collect.Maps.immutableEnumMap(spawners); // Leaf - optimize mob spawning
|
|
this.mobSpawnCosts = ImmutableMap.copyOf(mobSpawnCosts);
|
|
}
|
|
|
|
diff --git a/net/minecraft/world/level/chunk/ChunkGenerator.java b/net/minecraft/world/level/chunk/ChunkGenerator.java
|
|
index fb77cd58542c1a690cbeaa6ed3a4d657de6e619d..e0267e84c8fd2267b047590d712728e963d165f2 100644
|
|
--- a/net/minecraft/world/level/chunk/ChunkGenerator.java
|
|
+++ b/net/minecraft/world/level/chunk/ChunkGenerator.java
|
|
@@ -516,6 +516,35 @@ public abstract class ChunkGenerator {
|
|
return biome.value().getMobSettings().getMobs(category);
|
|
}
|
|
|
|
+ // Leaf start - optimize mob spawning
|
|
+ public WeightedList<MobSpawnSettings.SpawnerData> getMobsAtChunk(Holder<Biome> biome, StructureManager structureManager, MobCategory category, BlockPos pos, ChunkAccess chunk) {
|
|
+ Map<Structure, LongSet> allStructuresAt = ChunkPos.asLong(pos) == chunk.getPos().longKey ? structureManager.getAllStructuresAtChunk(chunk) : structureManager.getAllStructuresAt(pos);
|
|
+
|
|
+ for (Entry<Structure, LongSet> entry : allStructuresAt.entrySet()) {
|
|
+ Structure structure = entry.getKey();
|
|
+ StructureSpawnOverride structureSpawnOverride = structure.spawnOverrides().get(category);
|
|
+ if (structureSpawnOverride != null) {
|
|
+ // Leaf start - optimise ChunkGenerator#getMobsAt
|
|
+ for (long l : entry.getValue()) {
|
|
+ StructureStart startForStructure = structureManager.getStartForStructure(
|
|
+ null, structure, chunk.getPos().longKey == l ? chunk : structureManager.level.getChunk(ChunkPos.getX(l), ChunkPos.getZ(l), ChunkStatus.STRUCTURE_STARTS)
|
|
+ );
|
|
+ if (startForStructure != null && startForStructure.isValid()) {
|
|
+ if (structureSpawnOverride.boundingBox() == StructureSpawnOverride.BoundingBoxType.PIECE
|
|
+ ? structureManager.structureHasPieceAt(pos, startForStructure)
|
|
+ : startForStructure.getBoundingBox().isInside(pos)) {
|
|
+ return structureSpawnOverride.spawns();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Leaf end - optimise ChunkGenerator#getMobsAt
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return biome.value().getMobSettings().getMobs(category);
|
|
+ }
|
|
+ // Leaf end - optimize mob spawning
|
|
+
|
|
public void createStructures(
|
|
RegistryAccess registryAccess,
|
|
ChunkGeneratorStructureState structureState,
|