07073cb4a3
* Add PCA sync protocol support Introduce PCA sync protocol and integrate it into Carpet/Leaves. - Added org/leavesmc/leaves/protocol/PcaSyncProtocol.java: full implementation of the PCA sync protocol (handlers for sync requests, watcher maps, update payloads, player/block watch management, scheduling, and config-driven behavior). - Added fun/bm/lophine/config/modules/function/protocol/PcaSyncProtocolConfig.java and enum PcaPlayerEntityType.java to drive enabling and player-entity sync policy. - Integrated protocol into Carpet: register new carpet rules (pcaSyncProtocol, pcaSyncPlayerEntity) in CarpetCompatSync and added batching to avoid excessive updates when rules change. - Enhanced CarpetServerProtocol to track active players, batch rule updates, and send server rule data safely on the server thread. - Added a Minecraft patch (0046-PCA-sync-protocol.patch) to trigger syncs from setChanged() in containers, chest boats, minecarts and BlockEntity updates so watched clients receive entity/block-entity updates. This enables PCA clients to watch and receive synchronized block-entity and entity state on demand, while keeping updates gated by config and player permissions and improving rule-update performance via batching. * Add Vanilla-like experience config and updates Introduce a new VanillaLikeExperienceConfig module and a patch (0047) that gates multiple Paper/Luminol behavior changes behind this toggle to restore more vanilla-like technical behavior when enabled. The patch injects conditional checks across many server classes (entity merging, phantoms, pistons, hoppers, TNT handling, explosions, portal/piston protections, item merging, teleportation, spawn counting, etc.). Also adjust PCA sync protocol references in the existing 0046 patch to use the updated fun.bm.lophine package path. Includes the new fun.bm.lophine.config.modules.fixes.VanillaLikeExperienceConfig.java file. * Restore vanilla ender pearl loading on Folia Add patch (features/0048) that restores vanilla ender-pearl loading behavior on Folia. Reintroduces ender-pearl tracking and persistence: ServerPlayer now uses a thread-safe enderPearls set and saves pearl tags, ThrownEnderpearl maintains a lastSavedEnderPearlTag (updated on construction, owner changes, spawn and restore) and updates/clears it on tick/removal, and EnderpearlItem updates the saved tag when spawning a pearl. PlayerList and BotList now schedule pearl removal via the Bukkit taskScheduler when players/bots quit. The changes preserve Paper's legacyEnderPearlBehavior checks where applicable and add logging and safer tag handling to avoid saving invalid snapshots. * Restore vanilla ender pearl loading Restore vanilla ender-pearl loading behavior on Folia by preserving the last saved NBT for thrown pearls. ThrownEnderpearl.java: add a volatile CompoundTag lastSavedEnderPearlTag, provide a defensive-copying getter getLastSavedEnderPearlTag(), adjust @Nullable annotation placement, and update logger/comment. Minor index/format updates in ServerPlayer.java, PlayerList.java and EnderpearlItem.java. * remove extra import * Add Precise mob cap and adjust ender pearl patch Introduce a Precise mob cap feature and integrate it into Paper/Folia world/region lifecycle. Adds new patch features/0049-Precise-mob-cap.patch which modifies RegionizedWorldData, ServerChunkCache and ServerLevel to support an incremental, event-driven mob counting system that can supersede the existing GlobalEntitiesCounter. Adds PreciseMobCapConfig (toggle and behavior flags) and PreciseMobCapCounter (unique id management, area map registration, per-level global counters, spawnable-chunk tracking, spawn state creation, and cleanup on unload). Also updates the existing 0048 ender-pearl loading patch file (minor import/index adjustments). The Precise mob cap counts only relevant mobs (configurable to match vanilla or Paper-optimized behavior), hooks into entity tracking start/end, and requires per-player-mob-spawns=false to be set in Paper world defaults when enabled. * remove extra import * pre merge config * transform config * merge config * rebuild patches * fix a bug in new counter * simplify code --------- Co-authored-by: Helvetica Volubi <suisuroru@blue-millennium.fun> Co-authored-by: Helvetica Volubi <88063803+Suisuroru@users.noreply.github.com>
224 lines
15 KiB
Diff
224 lines
15 KiB
Diff
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 eedbeb005d3c8090333c36b9857c837dfb75dbd4..7ea91dbfb988aff3215a91cf62c9beae94461484 100644
|
|
--- a/io/papermc/paper/threadedregions/RegionizedWorldData.java
|
|
+++ b/io/papermc/paper/threadedregions/RegionizedWorldData.java
|
|
@@ -558,6 +558,9 @@ public final class RegionizedWorldData {
|
|
public final Map<Long, ChunkSectionItemEntityMovementTracker> itemEntityMovementTrackerMap = new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>();
|
|
// Luminol end
|
|
|
|
+ public int uniqueId = -1; // Lophine - Global Entities Counter
|
|
+ public boolean underGlobalEntitiesCounter = false; // Lophine - Global Entities Counter
|
|
+
|
|
public final TickRegions.TickRegionData regionData;
|
|
|
|
public RegionizedWorldData(final ServerLevel world, final TickRegions.TickRegionData regionData) {
|
|
@@ -570,6 +573,15 @@ public final class RegionizedWorldData {
|
|
this.turbo = new io.papermc.paper.redstone.RedstoneWireTurbo((RedStoneWireBlock)Blocks.REDSTONE_WIRE);
|
|
this.regionData = regionData;
|
|
|
|
+ // Lophine start - Global Entities Counter
|
|
+ if (fun.bm.lophine.config.modules.experiment.GlobalEntitiesCounter.isEnabled()) {
|
|
+ uniqueId = fun.bm.lophine.utils.EntitiesCounterUtil.generateUniqueId();
|
|
+ fun.bm.lophine.utils.EntitiesCounterUtil.reportAreaMap(world, spawnChunkTracker, uniqueId);
|
|
+ if (!fun.bm.lophine.config.modules.experiment.GlobalEntitiesCounter.isDefaultModule()) fun.bm.lophine.utils.EntitiesCounterUtil.addDataToLoaded(world, loadedEntities, uniqueId);
|
|
+ underGlobalEntitiesCounter = true;
|
|
+ }
|
|
+ // Lophine end - 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 27389ff98942e27c2a877ea5b671caa31353d18c..76fa17ce0afc7ff1c0b2b84aa2baddd2ba18c1a5 100644
|
|
--- a/net/minecraft/server/level/ServerChunkCache.java
|
|
+++ b/net/minecraft/server/level/ServerChunkCache.java
|
|
@@ -513,6 +513,26 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
|
|
this.chunkMap.tick(haveTime);
|
|
profiler.pop();
|
|
this.clearCache();
|
|
+ // Lophine start - Global Entities Counter
|
|
+ if (fun.bm.lophine.config.modules.experiment.GlobalEntitiesCounter.isEnabled()) {
|
|
+ io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.level.getCurrentWorldData();
|
|
+ if (regionizedWorldData != null) {
|
|
+ if (regionizedWorldData.underGlobalEntitiesCounter) {
|
|
+ if (regionizedWorldData.getLocalPlayers().isEmpty()) {
|
|
+ fun.bm.lophine.utils.EntitiesCounterUtil.onWorldDataUnload(level, regionizedWorldData.uniqueId);
|
|
+ regionizedWorldData.underGlobalEntitiesCounter = false;
|
|
+ }
|
|
+ } else {
|
|
+ if (!regionizedWorldData.getLocalPlayers().isEmpty()) {
|
|
+ regionizedWorldData.uniqueId = fun.bm.lophine.utils.EntitiesCounterUtil.generateUniqueId();
|
|
+ fun.bm.lophine.utils.EntitiesCounterUtil.reportAreaMap(level, regionizedWorldData.spawnChunkTracker, regionizedWorldData.uniqueId);
|
|
+ fun.bm.lophine.utils.EntitiesCounterUtil.addDataToLoaded(level, (ca.spottedleaf.moonrise.common.list.ReferenceList<Entity>) regionizedWorldData.getLoadedEntities(), regionizedWorldData.uniqueId);
|
|
+ regionizedWorldData.underGlobalEntitiesCounter = true;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Lophine end - Global Entities Counter
|
|
}
|
|
|
|
private void tickChunks() {
|
|
@@ -577,7 +597,13 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
|
|
}
|
|
spawnCookie = NaturalSpawner.createState(chunkCount, regionizedWorldData.getLoadedEntities(), this::getFullChunk, null, true); // Folia - region threading - note: function only cares about loaded entities, doesn't need all
|
|
} else {
|
|
- spawnCookie = NaturalSpawner.createState(chunkCount, 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 start - Global Entities Counter
|
|
+ if (fun.bm.lophine.config.modules.experiment.GlobalEntitiesCounter.isEnabled()) {
|
|
+ spawnCookie = fun.bm.lophine.utils.EntitiesCounterUtil.createSpawnState(level, regionizedWorldData.getLoadedEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false);
|
|
+ } else {
|
|
+ spawnCookie = NaturalSpawner.createState(chunkCount, 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
|
|
@@ -585,7 +611,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
|
|
boolean doMobSpawning = this.level.getGameRules().get(GameRules.SPAWN_MOBS) && !this.level.getLocalPlayers().isEmpty(); // CraftBukkit // Folia - region threading
|
|
int tickSpeed = this.level.getGameRules().get(GameRules.RANDOM_TICK_SPEED);
|
|
List<MobCategory> spawningCategories;
|
|
- if (doMobSpawning && (this.spawnEnemies || this.spawnFriendlies)) { // Paper
|
|
+ if (spawnCookie != null && doMobSpawning && (this.spawnEnemies || this.spawnFriendlies)) { // Paper // Lophine - Global Entities Counter
|
|
// Paper start - PlayerNaturallySpawnCreaturesEvent
|
|
for (ServerPlayer player : this.level.getLocalPlayers()) { // Folia - region threading
|
|
int chunkRange = Math.min(level.spigotConfig.mobSpawnRange, player.getBukkitEntity().getViewDistance());
|
|
@@ -645,7 +671,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
|
|
this.level.tickThunder(chunk);
|
|
}
|
|
|
|
- if (!spawningCategories.isEmpty()) {
|
|
+ if (spawnCookie != null && !spawningCategories.isEmpty()) { // Lophine - Global Entities Counter
|
|
if (this.level.getWorldBorder().isWithinBounds(chunkPos)) { // Paper - rewrite chunk system
|
|
NaturalSpawner.spawnForChunk(this.level, chunk, spawnCookie, spawningCategories);
|
|
}
|
|
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
|
|
index a7cebb797973cde399dc30c696b064efd40874f2..1d60c20649b2a6469bfceb5b44c38bfd133e21eb 100644
|
|
--- a/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/net/minecraft/server/level/ServerLevel.java
|
|
@@ -808,6 +808,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
|
|
}
|
|
|
|
public void tick(BooleanSupplier haveTime, final io.papermc.paper.threadedregions.TickRegions.TickRegionData region) { // Folia - regionised ticking
|
|
+ // Lophine start - Global Entities Counter
|
|
+ if (fun.bm.lophine.config.modules.experiment.GlobalEntitiesCounter.isEnabled()) {
|
|
+ if (fun.bm.lophine.utils.EntitiesCounterUtil.canRunNewTask(this)) 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 foliaProfiler = io.papermc.paper.threadedregions.TickRegionScheduler.getProfiler(); // Folia - profiler
|
|
ProfilerFiller profiler = Profiler.get();
|
|
@@ -2902,6 +2907,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
|
|
public void close() throws IOException {
|
|
super.close();
|
|
//this.entityManager.close(); // Paper - rewrite chunk system
|
|
+ // Lophine start - Precise mob cap
|
|
+ if (!fun.bm.lophine.config.modules.experiment.GlobalEntitiesCounter.isDefaultModule()) {
|
|
+ fun.bm.lophine.utils.EntitiesCounterUtil.onWorldUnload(this);
|
|
+ }
|
|
+ // Lophine end - Precise mob cap
|
|
}
|
|
|
|
@Override
|
|
@@ -3136,6 +3146,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
|
|
entity.inWorld = true; // CraftBukkit - Mark entity as in world
|
|
entity.valid = true; // CraftBukkit
|
|
ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server
|
|
+ // Lophine start - Precise mob cap
|
|
+ if (fun.bm.lophine.config.modules.experiment.GlobalEntitiesCounter.isEnabled() && !fun.bm.lophine.config.modules.experiment.GlobalEntitiesCounter.isDefaultModule()) {
|
|
+ fun.bm.lophine.utils.EntitiesCounterUtil.onEntityTrackingStart(ServerLevel.this, entity);
|
|
+ }
|
|
+ // Lophine end - Precise mob cap
|
|
// Paper start - Entity origin API
|
|
if (entity.origin == null) {
|
|
entity.origin = entity.position();
|
|
@@ -3151,6 +3166,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
|
|
@Override
|
|
public void onTrackingEnd(final Entity entity) {
|
|
org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot
|
|
+ // Lophine start - Precise mob cap
|
|
+ if (fun.bm.lophine.config.modules.experiment.GlobalEntitiesCounter.isEnabled() && !fun.bm.lophine.config.modules.experiment.GlobalEntitiesCounter.isDefaultModule()) {
|
|
+ fun.bm.lophine.utils.EntitiesCounterUtil.onEntityTrackingEnd(ServerLevel.this, entity);
|
|
+ }
|
|
+ // Lophine end - Precise mob cap
|
|
ServerLevel.this.getCurrentWorldData().removeLoadedEntity(entity); // Folia - region threading
|
|
// Spigot start // TODO I don't think this is needed anymore
|
|
if (entity instanceof Player player) {
|
|
diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java
|
|
index fc29654dd7ee3329edf1ccf705f45631d0ecc079..042f026f707e1a75b4ec7dc231e0eeb1b5afacc3 100644
|
|
--- a/net/minecraft/world/entity/Mob.java
|
|
+++ b/net/minecraft/world/entity/Mob.java
|
|
@@ -813,6 +813,44 @@ public abstract class Mob extends LivingEntity implements Targeting, EquipmentUs
|
|
}
|
|
}
|
|
} else {
|
|
+ // Lophine start - fix some bug if player is cross scheduler >> to remove monster if the region don't have any valid player
|
|
+ if (fun.bm.lophine.config.modules.experiment.GlobalEntitiesCounter.isEnabled()) {
|
|
+ List<net.minecraft.server.level.ServerPlayer> playerList = this.level().getServer().getPlayerList().getPlayers();
|
|
+ if (!playerList.isEmpty()) {
|
|
+ double d = -1.0;
|
|
+ Vec3 vec3 = null;
|
|
+ for (net.minecraft.server.level.ServerPlayer player1 : playerList) {
|
|
+ if (player1.level() != this.level()) continue;
|
|
+ double d1 = player1.distanceToSqr(this.position());
|
|
+ if (d == -1.0 || d1 < d) {
|
|
+ d = d1;
|
|
+ vec3 = player1.position();
|
|
+ }
|
|
+ }
|
|
+ if (vec3 != null) {
|
|
+ // copy from PaperMC
|
|
+ // Paper start - Configurable despawn distances
|
|
+ final io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DespawnRangePair despawnRangePair = this.level().paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory());
|
|
+ final io.papermc.paper.configuration.type.DespawnRange.Shape shape = this.level().paperConfig().entities.spawning.despawnRangeShape;
|
|
+ final double dy = Math.abs(vec3.y - this.getY());
|
|
+ final double dySqr = Mth.square(dy);
|
|
+ final double dxSqr = Mth.square(vec3.x - this.getX());
|
|
+ final double dzSqr = Mth.square(vec3.z - this.getZ());
|
|
+ final double distanceSquared = dxSqr + dzSqr + dySqr;
|
|
+ // Despawn if hard/soft limit is exceeded
|
|
+ if (despawnRangePair.hard().shouldDespawn(shape, dxSqr, dySqr, dzSqr, dy) && this.removeWhenFarAway(distanceSquared)) {
|
|
+ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
|
|
+ }
|
|
+
|
|
+ if (despawnRangePair.soft().shouldDespawn(shape, dxSqr, dySqr, dzSqr, dy)) {
|
|
+ if (this.noActionTime > 600 && this.random.nextInt(800) == 0 && this.removeWhenFarAway(distanceSquared)) {
|
|
+ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Lophine end - fix some bug if player is cross scheduler >> to remove monster if the region don't have any valid player
|
|
this.noActionTime = 0;
|
|
}
|
|
}
|
|
diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java
|
|
index b3b8160927147468c1dd4a30fac51f96a9d60c9a..cd20d10ee4cd2fe98131c9a93367ae133f96b8d4 100644
|
|
--- a/net/minecraft/world/level/NaturalSpawner.java
|
|
+++ b/net/minecraft/world/level/NaturalSpawner.java
|
|
@@ -121,7 +121,7 @@ public final class NaturalSpawner {
|
|
return new NaturalSpawner.SpawnState(spawnableChunkCount, mobCounts, spawnPotential, localMobCapCalculator);
|
|
}
|
|
|
|
- private static Biome getRoughBiome(final BlockPos pos, final ChunkAccess chunk) {
|
|
+ public static Biome getRoughBiome(final BlockPos pos, final ChunkAccess chunk) { // Lophine - protected -> public
|
|
return chunk.getNoiseBiome(QuartPos.fromBlock(pos.getX()), QuartPos.fromBlock(pos.getY()), QuartPos.fromBlock(pos.getZ())).value();
|
|
}
|
|
|
|
@@ -628,7 +628,7 @@ public final class NaturalSpawner {
|
|
private @Nullable EntityType<?> lastCheckedType;
|
|
private double lastCharge;
|
|
|
|
- private SpawnState(
|
|
+ public SpawnState( // Lophine - protected -> public
|
|
final int spawnableChunkCount,
|
|
final Object2IntOpenHashMap<MobCategory> mobCategoryCounts,
|
|
final PotentialCalculator spawnPotential,
|