Compare commits

...

14 Commits

Author SHA1 Message Date
Helvetica Volubi 37f1b4e5f1 fix: fix some async bugs 2025-12-07 18:42:46 +08:00
mrhua269 af2af944bf Fix issues of photographer with folia region threading 2025-12-06 23:25:37 +08:00
mrhua269 54688d8e34 Fix issues of fakeplayer with folia region threading 2025-12-06 22:35:51 +08:00
Helvetica Volubi 7b7f0b6d1f [ci skip] fix up branch in qodana 2025-12-06 21:58:02 +08:00
Helvetica Volubi 0ab3983145 Update Luminol
We need to check folia-supported,so we update it now
2025-12-06 16:52:56 +08:00
Helvetica Volubi 24a9d5cea8 fix https://github.com/MC-XiaoHei/ISeeYou/issues/68#issuecomment-3619619509 2025-12-06 15:45:34 +08:00
Helvetica Volubi 8ee77c3354 [ci skip] remove unused check
we have checked in protocol manager
2025-12-06 15:45:12 +08:00
qodana-cloud[bot] 98df254bad [ci skip]Add qodana CI checks (#100)
* Add qodana.yaml file

* Add github workflow file

---------

Co-authored-by: Qodana Application <qodana-support@jetbrains.com>
2025-12-04 14:19:47 +08:00
Helvetica Volubi 19f001ad1b [ci skip] remove unused data 2025-12-04 14:05:46 +08:00
Helvetica Volubi 106dfeaa38 fix: fix global entity count max limit problem 2025-12-04 13:37:48 +08:00
Helvetica Volubi 403ae2b2d0 feat: chunkCount 2025-12-04 12:13:14 +08:00
Helvetica Volubi 96c99519d5 fix: fix an async bug
if latest task is not done, skip this tick so that we can not block thread and run the task have not done, after it done, the new data will be processed in the next tick
2025-12-03 20:33:13 +08:00
Helvetica Volubi 5c4031b836 [ci skip] fixup template & comment 2025-12-03 20:20:59 +08:00
Helvetica Volubi 376857ff97 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
2025-12-03 16:31:34 +08:00
17 changed files with 445 additions and 44 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:
+29
View File
@@ -0,0 +1,29 @@
name: Qodana
on:
workflow_dispatch:
pull_request:
push:
branches: # Specify your branches here
- ver/* # Stable branch
- exp/* # Experimental branch
- dev/* # Development Branch
jobs:
qodana:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
checks: write
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit
fetch-depth: 0 # a full history is required for pull request analysis
- name: 'Qodana Scan'
uses: JetBrains/qodana-action@v2025.2
with:
pr-mode: false
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN_1658551725 }}
QODANA_ENDPOINT: 'https://qodana.cloud'
+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=46252869190560b69e97c474e91aaa07ac480436
org.gradle.configuration-cache=true
org.gradle.caching=true
@@ -20,7 +20,7 @@ index a82d84283632342bd30bc3449983431ba43583e0..f59526f6bfa1b4af5b474f0b438513c9
Set<CriterionTrigger.Listener<T>> set = (Set) advancements.criterionData.get(this); // Paper - fix PlayerAdvancements leak
if (set != null && !set.isEmpty()) {
diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
index 66ec0424a46dcd49cf44467357d80b1a2d84d3b2..04ae8de63af0a8abe578f14c8ef85fd4beca0969 100644
index 66ec0424a46dcd49cf44467357d80b1a2d84d3b2..58694e28b3ff85ef038fe2ada2672e6d3b074fa4 100644
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -96,7 +96,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
@@ -28,7 +28,7 @@ index 66ec0424a46dcd49cf44467357d80b1a2d84d3b2..04ae8de63af0a8abe578f14c8ef85fd4
private volatile PacketListener disconnectListener;
@Nullable
- private volatile PacketListener packetListener;
+ protected volatile PacketListener packetListener; // Leaves - private -> protected
+ public volatile PacketListener packetListener; // Leaves - private -> protected // Lophine protected -> public
@Nullable
private DisconnectionDetails disconnectionDetails;
private boolean encrypted;
@@ -257,7 +257,7 @@ index 70740381c6501c1a518c52b24381edd16792507f..8ad6d5a2522cfbe316fec2c3b918e2e1
}
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 89d72f242d0fc224701bdcdf245d948415e7c820..5efe7d94a364cec1bd18c4d8d62c19075d52f3e8 100644
index 7fb095c544294796768ce19c4409416e2848e296..3393fb585747aaf3fa270c4bfe06ed4e9174d519 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -207,6 +207,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -288,7 +288,7 @@ index f5ba0c9a4c3f9eaa38eeb689de915c25c7165433..24bbc32bc17802edbd9cc14310fe8141
this.setListData(list);
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 5efe7d94a364cec1bd18c4d8d62c19075d52f3e8..a4ee2bcdb31f889c7edbbc422636782c0d5d0af6 100644
index 3393fb585747aaf3fa270c4bfe06ed4e9174d519..c58b88f09e6f08d178fcb17a13dd790c3cb62aae 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -2836,7 +2836,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -323,7 +323,7 @@ index 4b2dba8b30b72fb3acc00957b3e19d6fc178c99e..afd607df83fda3de6f53f55ad305c8f6
private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32;
private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10;
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index 11c1eb6995ec3aa896815efa59477096c39089d1..54e7ddf0b3949e02d2705ff7626010c947965938 100644
index 11c1eb6995ec3aa896815efa59477096c39089d1..762728e490a152d159684324e200ab0d2852ac7f 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -130,6 +130,7 @@ public abstract class PlayerList {
@@ -334,7 +334,7 @@ index 11c1eb6995ec3aa896815efa59477096c39089d1..54e7ddf0b3949e02d2705ff7626010c9
// CraftBukkit start
private org.bukkit.craftbukkit.CraftServer cserver;
@@ -213,6 +214,122 @@ public abstract class PlayerList {
@@ -213,6 +214,123 @@ public abstract class PlayerList {
abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor
@@ -384,6 +384,7 @@ index 11c1eb6995ec3aa896815efa59477096c39089d1..54e7ddf0b3949e02d2705ff7626010c9
+ this.playersByUUID.put(player.getUUID(), player);
+
+ player.supressTrackerForLogin = true;
+ worldserver1.getCurrentWorldData().connections.add(player.connection.connection);
+ worldserver1.addNewPlayer(player);
+ this.server.getCustomBossEvents().onPlayerConnect(player);
+ org.bukkit.craftbukkit.entity.CraftPlayer bukkitPlayer = player.getBukkitEntity();
@@ -457,7 +458,7 @@ index 11c1eb6995ec3aa896815efa59477096c39089d1..54e7ddf0b3949e02d2705ff7626010c9
public void loadSpawnForNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie cookie, org.apache.commons.lang3.mutable.MutableObject<net.minecraft.util.ProblemReporter.ScopedCollector> scopedCollectorStore, org.apache.commons.lang3.mutable.MutableObject<ValueInput> data, org.apache.commons.lang3.mutable.MutableObject<String> lastKnownName, ca.spottedleaf.concurrentutil.completable.CallbackCompletable<org.bukkit.Location> toComplete) { // Folia - region threading - rewrite login process
player.isRealPlayer = true; // Paper
player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed
@@ -398,6 +515,7 @@ public abstract class PlayerList {
@@ -398,6 +516,7 @@ public abstract class PlayerList {
// player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); // CraftBukkit - replaced with loop below
this.players.add(player);
@@ -465,7 +466,7 @@ index 11c1eb6995ec3aa896815efa59477096c39089d1..54e7ddf0b3949e02d2705ff7626010c9
this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot
this.playersByUUID.put(player.getUUID(), player);
// this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player))); // CraftBukkit - replaced with loop below
@@ -601,6 +719,7 @@ public abstract class PlayerList {
@@ -601,6 +720,7 @@ public abstract class PlayerList {
}
protected void save(ServerPlayer player) {
@@ -473,7 +474,7 @@ index 11c1eb6995ec3aa896815efa59477096c39089d1..54e7ddf0b3949e02d2705ff7626010c9
if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit
player.lastSave = System.nanoTime(); // Folia - region threading - changed to nanoTime tracking
this.playerIo.save(player);
@@ -615,6 +734,43 @@ public abstract class PlayerList {
@@ -615,6 +735,44 @@ public abstract class PlayerList {
}
}
@@ -497,6 +498,7 @@ index 11c1eb6995ec3aa896815efa59477096c39089d1..54e7ddf0b3949e02d2705ff7626010c9
+ }
+ }
+
+ worldserver.getCurrentWorldData().connections.remove(entityplayer.connection.connection);
+ worldserver.removePlayerImmediately(entityplayer, Entity.RemovalReason.UNLOADED_WITH_PLAYER);
+ entityplayer.retireScheduler();
+ entityplayer.getAdvancements().stopListening();
@@ -517,7 +519,7 @@ index 11c1eb6995ec3aa896815efa59477096c39089d1..54e7ddf0b3949e02d2705ff7626010c9
public @Nullable net.kyori.adventure.text.Component remove(ServerPlayer player) { // CraftBukkit - return string // Paper - return Component
// Paper start - Fix kick event leave message not being sent
return this.remove(player, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? player.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(player.getDisplayName())));
@@ -688,6 +844,7 @@ public abstract class PlayerList {
@@ -688,6 +846,7 @@ public abstract class PlayerList {
player.retireScheduler(); // Paper - Folia schedulers
player.getAdvancements().stopListening();
this.players.remove(player);
@@ -525,7 +527,7 @@ index 11c1eb6995ec3aa896815efa59477096c39089d1..54e7ddf0b3949e02d2705ff7626010c9
this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot
this.server.getCustomBossEvents().onPlayerDisconnect(player);
UUID uuid = player.getUUID();
@@ -1044,15 +1201,15 @@ public abstract class PlayerList {
@@ -1044,15 +1203,15 @@ public abstract class PlayerList {
}
public String[] getPlayerNamesArray() {
@@ -0,0 +1,136 @@
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..7cc41630a39c1e29840d70eee78473d06b7c11cd 100644
--- a/io/papermc/paper/threadedregions/RegionizedWorldData.java
+++ b/io/papermc/paper/threadedregions/RegionizedWorldData.java
@@ -520,6 +520,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 RegionizedWorldData(final ServerLevel world) {
this.world = world;
this.blockLevelTicks = new LevelTicks<>(world::isPositionTickingWithEntitiesLoaded, world, true);
@@ -529,6 +532,15 @@ public final class RegionizedWorldData {
this.wireHandler = new alternate.current.wire.WireHandler(world);
this.turbo = new io.papermc.paper.redstone.RedstoneWireTurbo((RedStoneWireBlock)Blocks.REDSTONE_WIRE);
+ // Lophine start - Global Entities Counter
+ if (fun.bm.lophine.config.modules.experiment.GlobalEntitiesCounter.enabled) {
+ uniqueId = fun.bm.lophine.utils.EntitiesCounterUtil.generateUniqueId();
+ fun.bm.lophine.utils.EntitiesCounterUtil.reportAreaMap(world, spawnChunkTracker, uniqueId);
+ 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 05bad0366bd36b8134290a0cceb4d4dfcbbd0544..604681c8fda696dcc824b1b36b53c0d8434ec5c2 100644
--- a/net/minecraft/server/level/ServerChunkCache.java
+++ b/net/minecraft/server/level/ServerChunkCache.java
@@ -516,6 +516,25 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
this.chunkMap.tick(hasTimeLeft);
profilerFiller.pop();
this.clearCache();
+ // Lophine start - Global Entities Counter
+ if (fun.bm.lophine.config.modules.experiment.GlobalEntitiesCounter.enabled) {
+ 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()) {
+ 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() {
@@ -580,7 +599,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, 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 +614,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 +671,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..0b0d9322632ef1a751add633bdcb87ec84f82704 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) {
+ 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 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,165 @@
package fun.bm.lophine.utils;
import ca.spottedleaf.moonrise.common.list.ReferenceList;
import ca.spottedleaf.moonrise.common.misc.PositionCountingAreaMap;
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.server.level.ServerPlayer;
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.event.entity.CreatureSpawnEvent;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.Set;
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, WeakHashMap<Integer, ReferenceList<Entity>>> globalLoadedEntities = new WeakHashMap<>();
private static final WeakHashMap<ServerLevel, Object2IntOpenHashMap<MobCategory>> mobsMap = new WeakHashMap<>();
private static final WeakHashMap<ServerLevel, WeakHashMap<Integer, PositionCountingAreaMap<ServerPlayer>>> mobsAreaMap = new WeakHashMap<>();
private static final WeakHashMap<ServerLevel, Integer> spawnableChunkCount = new WeakHashMap<>();
private static final WeakHashMap<ServerLevel, CompletableFuture<Void>> tasks = new WeakHashMap<>();
private static final Set<Integer> UniqueIds = new HashSet<>();
private static int lastUsedId = 0;
public static int generateUniqueId() {
synchronized (UniqueIds) {
int id = lastUsedId;
while (UniqueIds.contains(id)) {
id++;
}
lastUsedId = id;
UniqueIds.add(id);
return id;
}
}
public static void onWorldDataUnload(ServerLevel level, int uniqueId) {
synchronized (UniqueIds) {
UniqueIds.remove(uniqueId);
}
synchronized (globalLoadedEntities) {
globalLoadedEntities.get(level).remove(uniqueId);
}
synchronized (mobsMap) {
mobsAreaMap.get(level).remove(uniqueId);
}
}
public static void addDataToLoaded(ServerLevel level, ReferenceList<Entity> data, int uniqueId) {
WeakHashMap<Integer, ReferenceList<Entity>> data0 = globalLoadedEntities.computeIfAbsent(level, k -> new WeakHashMap<>());
if (data0.containsKey(uniqueId)) return;
data0.put(uniqueId, data);
}
public static void reportAreaMap(ServerLevel level, PositionCountingAreaMap<ServerPlayer> areaMap, int uniqueId) {
WeakHashMap<Integer, PositionCountingAreaMap<ServerPlayer>> areaMap0 = mobsAreaMap.computeIfAbsent(level, k -> new WeakHashMap<>());
if (areaMap0.containsKey(uniqueId)) return;
areaMap0.put(uniqueId, areaMap);
}
public static int getTotalChunkCount(ServerLevel level) {
return spawnableChunkCount.getOrDefault(level, 0);
}
public static @Nullable Object2IntOpenHashMap<MobCategory> getMobsMap(ServerLevel level) {
return mobsMap.get(level);
}
public static boolean canRunNewTask(ServerLevel level) {
CompletableFuture<Void> task = tasks.get(level);
return task == null || task.isDone();
}
public static void tick(ServerLevel level) {
Runnable task = () -> {
WeakHashMap<Integer, ReferenceList<Entity>> data0 = globalLoadedEntities.get(level);
Object2IntOpenHashMap<MobCategory> map = new Object2IntOpenHashMap<>();
for (ReferenceList<Entity> data : data0.values()) {
for (Entity entity : GlobalEntitiesCounter.enabled ? data.copy() : data) {
if (entity == null || entity.isRemoved() || !entity.isAlive()) continue;
// 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;
}
// Paper end - Only count natural spawns
map.addTo(category, 1);
}
// Lophine start - Copy from net/minecraft/world/level/NaturalSpawner
}
}
mobsMap.put(level, map);
for (ServerLevel world : mobsAreaMap.keySet()) {
int count = 0;
WeakHashMap<Integer, PositionCountingAreaMap<ServerPlayer>> collection = mobsAreaMap.get(level);
if (collection == null) continue;
for (PositionCountingAreaMap<ServerPlayer> areaMap : collection.values()) {
count += areaMap.getTotalPositions();
}
spawnableChunkCount.put(world, count);
}
};
if (GlobalEntitiesCounter.async) {
tasks.put(level, CompletableFuture.runAsync(task));
} else {
task.run();
}
}
public static NaturalSpawner.SpawnState runRemainingTasks(
ServerLevel level, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkGetter, LocalMobCapCalculator calculator, final boolean countMobs
) {
Object2IntOpenHashMap<MobCategory> map = getMobsMap(level);
if (map == null) return null; // skip if no data
// Lophine start - Copy from net/minecraft/world/level/NaturalSpawner
PotentialCalculator potentialCalculator = new PotentialCalculator();
for (Entity entity : entities) {
if (entity == null || entity.isRemoved() || !entity.isAlive()) continue;
// 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;
}
// Paper end - Only count natural spawns
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(getTotalChunkCount(level), map, potentialCalculator, calculator);
// Lophine end - Copy from net/minecraft/world/level/NaturalSpawner
}
}
@@ -121,7 +121,7 @@ public class BotList {
}
ServerBot bot = new ServerBot(this.server, this.server.getLevel(Level.OVERWORLD), new GameProfile(uuid, realName));
bot.connection = new ServerBotPacketListenerImpl(this.server, bot);
bot.connection = new ServerBotPacketListenerImpl(this.server, bot, new ServerBotPacketListenerImpl.BotConnection());
Optional<ValueInput> optional;
try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(bot.problemPath(), LOGGER)) {
optional = playerIO.load(bot, scopedCollector);
@@ -154,7 +154,7 @@ public class BotList {
bot.isRealPlayer = true;
bot.loginTime = System.currentTimeMillis();
bot.connection = new ServerBotPacketListenerImpl(this.server, bot);
bot.connection = new ServerBotPacketListenerImpl(this.server, bot, new ServerBotPacketListenerImpl.BotConnection());
bot.setServerLevel(world);
BotSpawnLocationEvent event = new BotSpawnLocationEvent(bot.getBukkitEntity(), location);
@@ -175,22 +175,24 @@ public class BotList {
bot.supressTrackerForLogin = true;
if (TickThread.isTickThreadFor(world, net.minecraft.util.Mth.floor(location.getX()) >> 4, net.minecraft.util.Mth.floor(location.getZ()) >> 4)) {
summonBot(bot, world);
} else {
RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
world, net.minecraft.util.Mth.floor(location.getX()) >> 4, net.minecraft.util.Mth.floor(location.getZ()) >> 4,
() -> summonBot(bot, world),
ca.spottedleaf.concurrentutil.util.Priority.HIGHER);
}
optional.ifPresent(nbt -> {
bot.loadAndSpawnEnderPearls(nbt);
bot.loadAndSpawnParentVehicle(nbt);
});
if (TickThread.isTickThreadFor(world, location.blockX() >> 4, location.blockZ() >> 4)) {
summonBot(bot, world);
} else {
RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
world, location.getBlockX() >> 4, location.blockZ() >> 4,
() -> summonBot(bot, world));
}
return bot;
}
private ServerBot summonBot(ServerBot bot, ServerLevel world) {
world.getCurrentWorldData().connections.add(bot.connection.connection);
world.addNewPlayer(bot);
BotJoinEvent event1 = new BotJoinEvent(bot.getBukkitEntity(), PaperAdventure.asAdventure(Component.translatable("multiplayer.player.joined", bot.getDisplayName())).style(Style.style(NamedTextColor.YELLOW)));
@@ -277,7 +279,10 @@ public class BotList {
}
}
bot.level().getCurrentWorldData().connections.remove(bot.connection.connection);
bot.level().removePlayerImmediately(bot, Entity.RemovalReason.UNLOADED_WITH_PLAYER);
bot.retireScheduler();
this.bots.remove(bot);
this.botsByName.remove(bot.getScoreboardName().toLowerCase(Locale.ROOT));
@@ -299,6 +304,7 @@ public class BotList {
if (removeMessage != null && !removeMessage.equals(net.kyori.adventure.text.Component.empty())) {
this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(removeMessage), false);
}
return true;
}
@@ -29,9 +29,12 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ServerBotPacketListenerImpl extends ServerGamePacketListenerImpl {
public final Connection fakeConnection;
public ServerBotPacketListenerImpl(MinecraftServer server, ServerBot bot) {
super(server, BotConnection.INSTANCE, bot, CommonListenerCookie.createInitial(bot.gameProfile, false));
public ServerBotPacketListenerImpl(MinecraftServer server, ServerBot bot, BotConnection botConnection) {
super(server, botConnection, bot, CommonListenerCookie.createInitial(bot.gameProfile, false));
this.fakeConnection = botConnection;
this.fakeConnection.packetListener = this;
}
@Override
@@ -53,8 +56,6 @@ public class ServerBotPacketListenerImpl extends ServerGamePacketListenerImpl {
public static class BotConnection extends Connection {
private static final BotConnection INSTANCE = new BotConnection();
public BotConnection() {
super(PacketFlow.SERVERBOUND);
}
@@ -61,7 +61,6 @@ public class ServuxHudDataProtocol implements LeavesProtocol {
private static final Table<DataLogger.Type, ServerPlayer, Tag> DATA = HashBasedTable.create();
public static boolean refreshSpawnMetadata = false;
private long lastAcceptTime = 0;
@ProtocolHandler.Init
private static void initializeLoggers() {
@@ -240,8 +239,6 @@ public class ServuxHudDataProtocol implements LeavesProtocol {
}
long currentTime = System.currentTimeMillis() / 50;
if (currentTime == lastAcceptTime) return;
lastAcceptTime = currentTime;
if (currentTime % ServuxProtocolConfig.hudUpdateInterval == 0) {
MinecraftServer server = MinecraftServer.getServer();
@@ -26,7 +26,7 @@ public class RecordMetaData {
public static final int CURRENT_FILE_FORMAT_VERSION = 14;
public boolean singleplayer = false;
public String serverName = "Leaves";
public String serverName = "Lophine";
public int duration = 0;
public long date;
public String mcversion;
@@ -37,4 +37,22 @@ public class RecordMetaData {
public int selfId = -1;
public Set<UUID> players = new HashSet<>();
public RecordMetaData copy() {
RecordMetaData ret = new RecordMetaData();
synchronized (this) {
ret.singleplayer = this.singleplayer;
ret.serverName = this.serverName;
ret.duration = this.duration;
ret.date = this.date;
ret.mcversion = this.mcversion;
ret.fileFormat = this.fileFormat;
ret.fileFormatVersion = this.fileFormatVersion;
ret.protocol = this.protocol;
ret.generator = this.generator;
ret.selfId = this.selfId;
ret.players = new HashSet<>(this.players);
}
return ret;
}
}
@@ -195,7 +195,9 @@ public class Recorder extends Connection {
}
case ClientboundAddEntityPacket packet1 -> {
if (packet1.getType() == EntityType.PLAYER) {
metaData.players.add(packet1.getUUID());
synchronized (metaData) {
metaData.players.add(packet1.getUUID());
}
saveMetadata();
}
}
@@ -27,7 +27,7 @@ import java.util.List;
public class RecorderOption {
public int recordDistance = -1;
public String serverName = "Leaves";
public String serverName = "Lophine";
public RecordWeather forceWeather = null;
public int forceDayTime = -1;
public boolean ignoreChat = false;
@@ -123,9 +123,10 @@ public class ReplayFile {
data.fileFormatVersion = RecordMetaData.CURRENT_FILE_FORMAT_VERSION;
data.protocol = SharedConstants.getCurrentVersion().protocolVersion();
data.generator = ProtocolUtils.buildProtocolVersion("replay");
RecordMetaData dataCopy = data.copy();
try (Writer writer = new OutputStreamWriter(new FileOutputStream(metaFile), StandardCharsets.UTF_8)) {
writer.write(META_GSON.toJson(data));
writer.write(META_GSON.toJson(dataCopy));
}
}
@@ -84,9 +84,8 @@ public class ServerPhotographer extends ServerPlayer {
placePhotographer(server, photographer, world, state);
} else {
RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
world, net.minecraft.util.Mth.floor(state.loc.getX()) >> 4, net.minecraft.util.Mth.floor(state.loc.getZ()) >> 4,
() -> placePhotographer(server, photographer, world, state),
ca.spottedleaf.concurrentutil.util.Priority.HIGHER);
world, state.loc.blockX() >> 4, state.loc.blockZ() >> 4,
() -> placePhotographer(server, photographer, world, state));
}
photographers.add(photographer);
@@ -116,11 +115,13 @@ public class ServerPhotographer extends ServerPlayer {
if (this.followPlayer != null) {
if (this.getCamera() == this || this.getCamera().level() != this.level()) {
this.getBukkitPlayer().teleportAsync(this.getCamera().getBukkitEntity().getLocation());
this.setCamera(followPlayer);
}
if (lastPosVec3.distanceToSqr(this.position()) > 1024D) {
this.getBukkitPlayer().teleportAsync(this.getCamera().getBukkitEntity().getLocation());
((CraftPhotographer) this.getBukkitPlayer()).taskScheduler.schedule(ent -> {
this.getBukkitPlayer().teleportAsync(this.getCamera().getBukkitEntity().getLocation());
}, null, 1L);
}
}
@@ -158,13 +159,12 @@ public class ServerPhotographer extends ServerPlayer {
}
public void remove(boolean async, boolean save) {
super.remove(RemovalReason.KILLED);
photographers.remove(this);
this.recorder.stop();
this.getServer().getPlayerList().removePhotographer(this);
LOGGER.info("Photographer {} removed", createState.id);
this.recorder.stop();
photographers.remove(this);
MinecraftServer.getServer().getPlayerList().removePhotographer(this);
if (!recorder.isSaved()) {
CompletableFuture<Void> future = recorder.saveRecording(saveFile, save);
if (!async) {
+10
View File
@@ -0,0 +1,10 @@
####################################################################################################################
# WARNING: Do not store sensitive information in this file, as its contents will be included in the Qodana report. #
####################################################################################################################
version: "1.0"
linter: jetbrains/qodana-jvm:2025.2
profile:
name: qodana.recommended
include:
- name: CheckDependencyLicenses