feat: global entities counter

We provided a new way to use entities counter to spawn mobs, maybe have some bugs, so we set this to experiment config

 - Update file update
 - Update Luminol
This commit is contained in:
Helvetica Volubi
2025-12-03 16:31:34 +08:00
parent fa1ab90579
commit 376857ff97
5 changed files with 229 additions and 2 deletions
+15 -1
View File
@@ -25,7 +25,7 @@ body:
LophineMC服务端发行版本 LophineMC server release version (contains the git commit's hash)<br>
通过执行`/version`命令将获取到的内容复制到这里。 Run `/version` on your server and paste the full, unmodified output here.
placeholder: >
e.g. git-Lophine-"0635a89" (MC: 1.20.4)
e.g. git-Lophine-"fa1ab90" (MC: 1.21.8)
validations:
required: true
- type: textarea
@@ -65,6 +65,20 @@ body:
To list plugins, run `/plugins`. For datapacks, run `/datapack list`.
validations:
required: true
- type: textarea
id: config-file-list
attributes:
label: 配置文件列表(如果可以)。 Config File List (if possible).
description:
服务器上的配置文件列表。
All config files on your server.
例如以下文件(或者文件夹):
Example files (or folders)
- \spigot.yml
- \bukkit.yml
- \config\*
- \lophine_config\*
- \luminol_config\*
- type: textarea
id: other
attributes:
+1 -1
View File
@@ -4,7 +4,7 @@ mcVersion=1.21.8
release=2
# 0 for skip release, 1 for pre-release, 2 for release
luminolRef=ae1af655f57cd8666c2ae913fa53d3606019bf7b
luminolRef=1958fdc8b66b75b23a001490288cc618a76808e3
org.gradle.configuration-cache=true
org.gradle.caching=true
@@ -0,0 +1,93 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Wed, 3 Dec 2025 16:28:34 +0800
Subject: [PATCH] Global Entities Counter
diff --git a/io/papermc/paper/threadedregions/RegionizedWorldData.java b/io/papermc/paper/threadedregions/RegionizedWorldData.java
index 3b39b8f7ccb278cf910e3cc4e00da29790465a81..8a985f9b1fed90e167d625815eb5fa6586a00972 100644
--- a/io/papermc/paper/threadedregions/RegionizedWorldData.java
+++ b/io/papermc/paper/threadedregions/RegionizedWorldData.java
@@ -529,6 +529,8 @@ public final class RegionizedWorldData {
this.wireHandler = new alternate.current.wire.WireHandler(world);
this.turbo = new io.papermc.paper.redstone.RedstoneWireTurbo((RedStoneWireBlock)Blocks.REDSTONE_WIRE);
+ fun.bm.lophine.utils.EntitiesCounterUtil.addDataToLoaded(loadedEntities, world); // Lophine - Global Entities Counter
+
// tasks may be drained before the region ticks, so we must set up the tick data early just in case
this.updateTickData();
}
diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
index 05bad0366bd36b8134290a0cceb4d4dfcbbd0544..446dc2e3db99fe7f081b8d3df48ba8a9778ae379 100644
--- a/net/minecraft/server/level/ServerChunkCache.java
+++ b/net/minecraft/server/level/ServerChunkCache.java
@@ -580,7 +580,13 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
}
spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, regionizedWorldData.getLoadedEntities(), this::getFullChunk, null, true); // Folia - region threading - note: function only cares about loaded entities, doesn't need all
} else {
+ // Lophine start - Global Entities Counter
+ if (fun.bm.lophine.config.modules.experiment.GlobalEntitiesCounter.enabled) {
+ spawnState = fun.bm.lophine.utils.EntitiesCounterUtil.runRemainingTasks(level, naturalSpawnChunkCount, regionizedWorldData.getLoadedEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false);
+ } else {
spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, regionizedWorldData.getLoadedEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false); // Folia - region threading - note: function only cares about loaded entities, doesn't need all
+ }
+ // Lophine end - Global Entities Counter
}
} finally { foliaProfiler.stopTimer(ca.spottedleaf.leafprofiler.LProfilerRegistry.MOB_SPAWN_ENTITY_COUNT); } // Folia - profiler
// Paper end - Optional per player mob spawns
@@ -589,7 +595,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.getLocalPlayers().isEmpty(); // CraftBukkit // Folia - region threading
int _int = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
List<MobCategory> filteredSpawningCategories;
- if (_boolean && (this.spawnEnemies || this.spawnFriendlies)) {
+ if (spawnState != null && _boolean && (this.spawnEnemies || this.spawnFriendlies)) { // Lophine - Global Entities Counter
// Paper start - PlayerNaturallySpawnCreaturesEvent
for (ServerPlayer entityPlayer : this.level.getLocalPlayers()) { // Folia - region threading
int chunkRange = Math.min(level.spigotConfig.mobSpawnRange, entityPlayer.getBukkitEntity().getViewDistance());
@@ -646,7 +652,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
this.level.tickThunder(chunk);
}
- if (!spawnCategories.isEmpty()) {
+ if (spawnState != null && !spawnCategories.isEmpty()) { // Lophine - Global Entities Counter
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 c0ba36849da0983117901f0cd04b2c785df0010b..a51ae9326907c8a1ecdbc3112ca6d76520791678 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -729,6 +729,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
}
public void tick(BooleanSupplier hasTimeLeft, io.papermc.paper.threadedregions.TickRegions.TickRegionData region) { // Folia - regionised ticking
+ // Lophine start - Global Entities Counter
+ if (fun.bm.lophine.config.modules.experiment.GlobalEntitiesCounter.enabled) {
+ fun.bm.lophine.utils.EntitiesCounterUtil.tick(this);
+ }
+ // Lophine end - Global Entities Counter
final io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.getCurrentWorldData(); // Folia - regionised ticking
final ca.spottedleaf.leafprofiler.RegionizedProfiler.Handle profiler = io.papermc.paper.threadedregions.TickRegionScheduler.getProfiler(); // Folia - profiler
ProfilerFiller profilerFiller = Profiler.get();
diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java
index 81f4707cf1a7cbdfcfc519cfbe1052cde77aa755..5c53e2d61ce8483d5d9e93e9f3112074c704a546 100644
--- a/net/minecraft/world/level/NaturalSpawner.java
+++ b/net/minecraft/world/level/NaturalSpawner.java
@@ -113,7 +113,7 @@ public final class NaturalSpawner {
return new NaturalSpawner.SpawnState(spawnableChunkCount, map, potentialCalculator, calculator);
}
- static Biome getRoughBiome(BlockPos pos, ChunkAccess chunk) {
+ public static Biome getRoughBiome(BlockPos pos, ChunkAccess chunk) { // Lophine - protected -> public
return chunk.getNoiseBiome(QuartPos.fromBlock(pos.getX()), QuartPos.fromBlock(pos.getY()), QuartPos.fromBlock(pos.getZ())).value();
}
@@ -584,7 +584,7 @@ public final class NaturalSpawner {
private EntityType<?> lastCheckedType;
private double lastCharge;
- SpawnState(
+ public SpawnState( // Lophine - protected -> public
int spawnableChunkCount,
Object2IntOpenHashMap<MobCategory> mobCategoryCounts,
PotentialCalculator spawnPotential,
@@ -0,0 +1,20 @@
package fun.bm.lophine.config.modules.experiment;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.config.flags.HotReloadUnsupported;
import me.earthme.luminol.enums.EnumConfigCategory;
@ConfigClassInfo(name = "global_entities_counter", category = EnumConfigCategory.EXPERIMENT)
public class GlobalEntitiesCounter implements IConfigModule {
@HotReloadUnsupported
@ConfigInfo(name = "enabled", comments = """
Enable global entities counter.
You need to set per-player-mob-spawns to false on paper-world-defaults.yml or paper-world.yml""")
public static boolean enabled = false;
@HotReloadUnsupported
@ConfigInfo(name = "async", comments = "Enable Asynchronous(maybe cause bugs)")
public static boolean async = false;
}
@@ -0,0 +1,100 @@
package fun.bm.lophine.utils;
import ca.spottedleaf.moonrise.common.list.ReferenceList;
import fun.bm.lophine.config.modules.experiment.GlobalEntitiesCounter;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LocalMobCapCalculator;
import net.minecraft.world.level.NaturalSpawner;
import net.minecraft.world.level.PotentialCalculator;
import net.minecraft.world.level.biome.MobSpawnSettings;
import org.bukkit.craftbukkit.util.WeakCollection;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.jetbrains.annotations.Nullable;
import java.util.WeakHashMap;
import java.util.concurrent.CompletableFuture;
import static net.minecraft.world.level.NaturalSpawner.getRoughBiome;
public class EntitiesCounterUtil {
private static final WeakHashMap<ServerLevel, WeakCollection<ReferenceList<Entity>>> globalLoadedEntities = new WeakHashMap<>();
private static final WeakHashMap<ServerLevel, Object2IntOpenHashMap<MobCategory>> mobsMap = new WeakHashMap<>();
private static final WeakHashMap<ServerLevel, CompletableFuture<Void>> tasks = new WeakHashMap<>();
public static void addDataToLoaded(ReferenceList<Entity> data, ServerLevel level) {
WeakCollection<ReferenceList<Entity>> data0 = globalLoadedEntities.computeIfAbsent(level, k -> new WeakCollection<>());
data0.add(data);
}
public static @Nullable Object2IntOpenHashMap<MobCategory> getMobsMap(ServerLevel level) {
return mobsMap.get(level);
}
public static boolean canRunNewTask(ServerLevel level) {
return tasks.get(level).isDone();
}
public static void tick(ServerLevel level) {
Runnable task = () -> {
WeakCollection<ReferenceList<Entity>> data0 = globalLoadedEntities.get(level);
Object2IntOpenHashMap<MobCategory> map = new Object2IntOpenHashMap<>();
for (ReferenceList<Entity> data : data0) {
for (Entity entity : GlobalEntitiesCounter.enabled ? data.copy() : data) {
// Lophine start - Copy from net/minecraft/world/level/NaturalSpawner
MobCategory category = entity.getType().getCategory();
if (category != MobCategory.MISC) {
// Paper start - Only count natural spawns
if (!entity.level().paperConfig().entities.spawning.countAllMobsForSpawning &&
!(entity.spawnReason == CreatureSpawnEvent.SpawnReason.NATURAL ||
entity.spawnReason == CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) {
continue;
}
map.addTo(category, 1);
// Paper end - Only count natural spawns
}
// Lophine start - Copy from net/minecraft/world/level/NaturalSpawner
}
}
mobsMap.put(level, map);
};
if (GlobalEntitiesCounter.async) {
tasks.put(level, CompletableFuture.runAsync(task));
} else {
task.run();
}
}
public static NaturalSpawner.SpawnState runRemainingTasks(
ServerLevel level, int spawnableChunkCount, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkGetter, LocalMobCapCalculator calculator, final boolean countMobs
) {
Object2IntOpenHashMap<MobCategory> map = mobsMap.get(level);
if (map == null) return null; // skip if no data
PotentialCalculator potentialCalculator = new PotentialCalculator();
for (Entity entity : entities) {
BlockPos blockPos = entity.blockPosition();
chunkGetter.query(ChunkPos.asLong(blockPos), chunk -> {
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(), entity.getType().getCategory());
}
// Paper start - Optional per player mob spawns
if (countMobs) {
chunk.level.getChunkSource().chunkMap.updatePlayerMobTypeMap(entity);
}
// Paper end - Optional per player mob spawns
});
}
return new NaturalSpawner.SpawnState(spawnableChunkCount, map, potentialCalculator, calculator);
}
}