Compare commits

...

25 Commits

Author SHA1 Message Date
Helvetica Volubi ef3d1625b8 backport: add removed check before all checks start in addEffects 2026-01-13 21:44:57 +08:00
Helvetica Volubi c4acc049d8 fix dragon problem in overworld
origin fixed in Luminol 1.21.11
2026-01-04 18:54:26 +08:00
dependabot[bot] 3dc7b1edb7 [ci skip]build(deps): bump JetBrains/qodana-action from 2025.2 to 2025.3 (#114)
Bumps [JetBrains/qodana-action](https://github.com/jetbrains/qodana-action) from 2025.2 to 2025.3.
- [Release notes](https://github.com/jetbrains/qodana-action/releases)
- [Commits](https://github.com/jetbrains/qodana-action/compare/v2025.2...v2025.3)

---
updated-dependencies:
- dependency-name: JetBrains/qodana-action
  dependency-version: '2025.3'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 00:56:08 +08:00
Helvetica Volubi ab2992a2ef fix some bug caused by 5622d49d49 2025-12-27 20:51:20 +08:00
Helvetica Volubi 5622d49d49 backport: refix waypoint
related issue: https://github.com/LuminolMC/Lophine/issues/110

origin commit: bed0bfff06
2025-12-26 03:49:37 +08:00
Helvetica Volubi 7fdce6aedd backport: fix waypoint module
origin commit: db772aaf62
2025-12-23 10:26:39 +08:00
Helvetica Volubi c26d8d1ee3 [ci skip]before we start to update to 1.21.11, we need push to maven repo 2025-12-18 03:43:04 +08:00
Helvetica Volubi 7bc12d2487 fix: add null check in RegionizedWorldData >> conections
some of our function patches will crash because of get null in conn.getPlayer(), then when we process player.chunkPosition() will get NPE, so we need to fix it.
2025-12-17 00:26:36 +08:00
dependabot[bot] 2c3aa4b68e [ci skip]build(deps): bump actions/upload-artifact from 5 to 6 (#106)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 15:44:44 +08:00
Bacteriawa 91312b7a15 Update Luminol 2025-12-14 00:18:07 +08:00
Helvetica Volubi 77c412bfe8 feat: update concurrent in waypoint 2025-12-11 02:05:57 +08:00
Helvetica Volubi c06d875df7 feat: add optimized concurrent table for waypoint management 2025-12-10 15:22:58 +08:00
Helvetica Volubi 5e96851513 fix a bug in entities counter 2025-12-10 02:52:55 +08:00
Helvetica Volubi 91b3ad527c fix waypoint manager
we use thread safe method to load it
2025-12-10 01:49:10 +08:00
dependabot[bot] 959a1c346c [ci skip]build(deps): bump actions/checkout from 3 to 6 (#102)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-08 15:51:58 +08:00
Helvetica Volubi b594382a6a [ci skip] fixup ci 2025-12-07 19:06:04 +08:00
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
33 changed files with 778 additions and 96 deletions
+8 -7
View File
@@ -14,7 +14,7 @@ on:
required: false
default: ""
force-push:
description: "Force disable push to repo if you enter 2, push to repo if you enter 1, default push if not released else skip push repo"
description: "Force disable push to repo if you enter 2, force push to repo if you enter 1, default push if not released else skip push repo"
required: false
default: ""
comments:
@@ -54,7 +54,7 @@ jobs:
run: ./gradlew --refresh-dependencies createMojmapPaperclipJar
- name: Upload Artifact
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ env.project_id_b }} CI Artifacts
path: lophine-server/build/libs/*-paperclip-*-mojmap.jar
@@ -96,7 +96,7 @@ jobs:
fi
- name: Publish To Repo
if: (( github.event_name != 'pull_request' && !( env.unexpect == 'true' ) && env.flag_release == 'true' && env.release_exists != 'true' && !( inputs.force-release == '2' )) || inputs.force-push == '1') && false # do not push to any repo
if: ( github.event_name != 'pull_request' && !( env.unexpect == 'true' ) && env.flag_release == 'true' && env.release_exists != 'true' && !( inputs.force-release == '2' )) || inputs.force-push == '1'
continue-on-error: true
run: ./gradlew --refresh-dependencies generateDevelopmentBundle publish -PpublishDevBundle=true
env:
@@ -106,11 +106,12 @@ jobs:
- name: Create Release - Pre Process
if: ( github.event_name != 'pull_request' && !( env.unexpect == 'true' ) && env.flag_release == 'true' && env.release_exists != 'true' && !(inputs.force-release == '2')) || inputs.force-release == '1'
run: |
if [ ${{ inputs.comments }} == "" ]; then
echo "No comments provided"
echo "flag_comment=true" >> $GITHUB_ENV
if [ "${{ inputs.comments }}" == "" ]; then
echo "No comments provided"
echo "flag_comment=false" >> $GITHUB_ENV
else
echo "flag_comment=false" >> $GITHUB_ENV
echo "Comments provided: ${{ inputs.comments }}"
echo "flag_comment=true" >> $GITHUB_ENV
fi
- name: Create Release - No Comment
+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@v6
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.3
with:
pr-mode: false
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN_1658551725 }}
QODANA_ENDPOINT: 'https://qodana.cloud'
+2 -2
View File
@@ -62,7 +62,7 @@ repositories {
}
dependencies {
compileOnly("me.earthme.luminol:luminol-api:$VERSION")
compileOnly("fun.bm.lophine:lophine-api:$VERSION")
}
```
@@ -78,7 +78,7 @@ dependencies {
<dependencies>
<dependency>
<groupId>me.earthme.luminol</groupId>
<groupId>fun.bm.lophine</groupId>
<artifactId>luminol-api</artifactId>
<version>$VERSION</version>
</dependency>
+2 -2
View File
@@ -62,7 +62,7 @@ repositories {
}
dependencies {
compileOnly("me.earthme.luminol:luminol-api:$VERSION")
compileOnly("fun.bm.lophine:lophine-api:$VERSION")
}
```
@@ -78,7 +78,7 @@ dependencies {
<dependencies>
<dependency>
<groupId>me.earthme.luminol</groupId>
<groupId>fun.bm.lophine</groupId>
<artifactId>luminol-api</artifactId>
<version>$VERSION</version>
<scope>provided</scope>
+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=1958fdc8b66b75b23a001490288cc618a76808e3
luminolRef=e324edacf6651633674a0abce91ffbd290943dd5
org.gradle.configuration-cache=true
org.gradle.caching=true
@@ -3,6 +3,7 @@ From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Mon, 7 Jul 2025 18:19:00 +0800
Subject: [PATCH] Add config to enable waypoint command & bar
use concurrent method to fix it (now, they are thread safe)
diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java
index 1fb359073342a657e9e493403263b56be5b3393f..451a685782da46f5683ea6707289bbaf3193771f 100644
@@ -22,10 +23,19 @@ index 1fb359073342a657e9e493403263b56be5b3393f..451a685782da46f5683ea6707289bbaf
WorldBorderCommand.register(this.dispatcher);
if (JvmProfiler.INSTANCE.isAvailable()) {
diff --git a/net/minecraft/server/waypoints/ServerWaypointManager.java b/net/minecraft/server/waypoints/ServerWaypointManager.java
index 0f8cacbb8fe55a60e2f0c98bf36c005b29f41a4b..52fd730998535ea071bfc99b7cc2c254b9b656d7 100644
index 0f8cacbb8fe55a60e2f0c98bf36c005b29f41a4b..65a4956ba2f0e38da7ae4f8cd7ed6e4b02c015a9 100644
--- a/net/minecraft/server/waypoints/ServerWaypointManager.java
+++ b/net/minecraft/server/waypoints/ServerWaypointManager.java
@@ -22,7 +22,14 @@ public class ServerWaypointManager implements WaypointManager<WaypointTransmitte
@@ -16,22 +16,31 @@ import net.minecraft.world.waypoints.WaypointManager;
import net.minecraft.world.waypoints.WaypointTransmitter;
public class ServerWaypointManager implements WaypointManager<WaypointTransmitter> {
- private final Set<WaypointTransmitter> waypoints = new HashSet<>();
- private final Set<ServerPlayer> players = new HashSet<>();
- private final Table<ServerPlayer, WaypointTransmitter, WaypointTransmitter.Connection> connections = HashBasedTable.create();
+ private final Set<WaypointTransmitter> waypoints = new java.util.concurrent.CopyOnWriteArraySet<>(); // Lophine - concurrent
+ private final Set<ServerPlayer> players = new java.util.concurrent.CopyOnWriteArraySet<>(); // Lophine - concurrent
+ private final fun.bm.lophine.utils.concurrent.AbstractConcurrentTable<ServerPlayer, WaypointTransmitter, WaypointTransmitter.Connection> connections = fun.bm.lophine.config.modules.optimizations.WayPointOptimizedTableConfig.optimizedTable ? new fun.bm.lophine.utils.concurrent.OptimizedConcurrentTable<>() : new fun.bm.lophine.utils.concurrent.ConcurrentTable<>(); // Lophine - concurrent
@Override
public void trackWaypoint(WaypointTransmitter waypoint) {
@@ -41,7 +51,32 @@ index 0f8cacbb8fe55a60e2f0c98bf36c005b29f41a4b..52fd730998535ea071bfc99b7cc2c254
}
@Override
@@ -50,14 +57,48 @@ public class ServerWaypointManager implements WaypointManager<WaypointTransmitte
public void updateWaypoint(WaypointTransmitter waypoint) {
if (this.waypoints.contains(waypoint)) {
- Map<ServerPlayer, WaypointTransmitter.Connection> map = Tables.transpose(this.connections).row(waypoint);
- SetView<ServerPlayer> set = Sets.difference(this.players, map.keySet());
+ java.util.List<Entry<ServerPlayer, WaypointTransmitter.Connection>> map = this.connections.getXZ(waypoint); // Lophine - concurrent
+ Set<ServerPlayer> players1 = new HashSet<>(); // Lophine - concurrent
+ if (!map.isEmpty()) map.forEach((entry) -> players1.add(entry.getKey())); // Lophine - concurrent
+ SetView<ServerPlayer> set = Sets.difference(this.players, players1); // Lophine - concurrent
- for (Entry<ServerPlayer, WaypointTransmitter.Connection> entry : ImmutableSet.copyOf(map.entrySet())) {
+ for (Entry<ServerPlayer, WaypointTransmitter.Connection> entry : map) { // Lophine - concurrent
this.updateConnection(entry.getKey(), waypoint, entry.getValue());
}
@@ -43,26 +52,65 @@ public class ServerWaypointManager implements WaypointManager<WaypointTransmitte
@Override
public void untrackWaypoint(WaypointTransmitter waypoint) {
- this.connections.column(waypoint).forEach((serverPlayer, connection) -> connection.disconnect());
- Tables.transpose(this.connections).row(waypoint).clear();
+ this.connections.getXZ(waypoint).forEach((entry) -> {
+ entry.getValue().disconnect();
+ this.connections.remove(entry.getKey(), waypoint, entry.getValue());
+ }); // Lophine - concurrent
this.waypoints.remove(waypoint);
}
public void addPlayer(ServerPlayer player) {
// Folia - region threading
@@ -63,10 +98,12 @@ index 0f8cacbb8fe55a60e2f0c98bf36c005b29f41a4b..52fd730998535ea071bfc99b7cc2c254
// Folia - region threading
+ // Lophine start - unsafe waypoint bar
+ if (!fun.bm.lophine.config.modules.experiment.CommandConfig.waypoint) return;
+ Map<WaypointTransmitter, WaypointTransmitter.Connection> map = this.connections.row(player);
+ SetView<WaypointTransmitter> set = Sets.difference(this.waypoints, map.keySet());
+ java.util.List<Entry<WaypointTransmitter, WaypointTransmitter.Connection>> map = this.connections.getYZ(player); // Lophine - concurrent
+ Set<WaypointTransmitter> waypoints1 = new HashSet<>();// Lophine - concurrent
+ if (!map.isEmpty()) map.forEach((entry) -> waypoints1.add(entry.getKey()));// Lophine - concurrent
+ SetView<WaypointTransmitter> set = Sets.difference(this.waypoints, waypoints1); // Lophine - concurrent
+
+ for (Entry<WaypointTransmitter, WaypointTransmitter.Connection> entry : ImmutableSet.copyOf(map.entrySet())) {
+ for (Entry<WaypointTransmitter, WaypointTransmitter.Connection> entry : map) { // Lophine - concurrent
+ this.updateConnection(player, entry.getKey(), entry.getValue());
+ }
+
@@ -80,13 +117,61 @@ index 0f8cacbb8fe55a60e2f0c98bf36c005b29f41a4b..52fd730998535ea071bfc99b7cc2c254
// Folia - region threading
+ // Lophine start - unsafe waypoint bar
+ if (!fun.bm.lophine.config.modules.experiment.CommandConfig.waypoint) return;
+ this.connections.row(player).values().removeIf(connection -> {
+ connection.disconnect();
+ return true;
+ });
+ this.connections.getYZ(player).forEach((entry) -> {
+ entry.getValue().disconnect();
+ this.connections.remove(player, entry.getKey(), entry.getValue());
+ }); // Lophine - concurrent
+ this.untrackWaypoint((WaypointTransmitter)player);
+ this.players.remove(player);
+ // Lophine end - unsafe waypoint bar
}
public void breakAllConnections() {
- this.connections.values().forEach(WaypointTransmitter.Connection::disconnect);
- this.connections.clear();
+ java.util.List<WaypointTransmitter.Connection> map = this.connections.getAllZ(); // Lophine - concurrent
+ if (!map.isEmpty()) map.forEach(WaypointTransmitter.Connection::disconnect); // Lophine - concurrent
+ this.connections.clearAll(); // Lophine - concurrent
}
public void remakeConnections(WaypointTransmitter waypoint) {
@@ -83,13 +131,19 @@ public class ServerWaypointManager implements WaypointManager<WaypointTransmitte
if (player != waypoint) {
if (isLocatorBarEnabledFor(player)) {
waypoint.makeWaypointConnectionWith(player).ifPresentOrElse(connection -> {
- this.connections.put(player, waypoint, connection);
+ this.connections.putOrUpdate(player, waypoint, connection);
connection.connect();
}, () -> {
- WaypointTransmitter.Connection connection = this.connections.remove(player, waypoint);
- if (connection != null) {
- connection.disconnect();
- }
+ // Lophine start - concurrent
+ java.util.List<WaypointTransmitter.Connection> c = this.connections.getZ(player, waypoint);
+ if (c.isEmpty()) return; // Lophine - concurrent
+ c.forEach((connection) -> {
+ this.connections.remove(player, waypoint, connection);
+ if (connection != null) {
+ connection.disconnect();
+ }
+ });
+ // Lophine end - concurrent
});
}
}
@@ -103,10 +157,12 @@ public class ServerWaypointManager implements WaypointManager<WaypointTransmitte
} else {
waypoint.makeWaypointConnectionWith(player).ifPresentOrElse(connection1 -> {
connection1.connect();
- this.connections.put(player, waypoint, connection1);
+ this.connections.putOrUpdate(player, waypoint, connection1);
}, () -> {
connection.disconnect();
- this.connections.remove(player, waypoint);
+ java.util.List<WaypointTransmitter.Connection> c = this.connections.getZ(player, waypoint); // Lophine - concurrent
+ if (c.isEmpty()) return; // Lophine - concurrent
+ c.forEach((connection1) -> this.connections.remove(player, waypoint, connection1)); // Lophine - concurrent
});
}
}
@@ -8,7 +8,7 @@ As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 923aca94520f456a13d4e4eb23e1499b3dbc920f..0ee9e51c5abfd9269bcf0bb29c4b8394b27af02a 100644
index 7584ba257d61bf650d83a2316ff012df928d54f3..4d71cc3b643f6f594c90f2f8f9e5b820ebdff080 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -2901,7 +2901,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -23,7 +23,7 @@ index d0d90a25a10bbecfffceee1992af88c60d14fd87..187e48e78e69c80b25710f193813335e
+ // Leaves end - servux
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 0ee9e51c5abfd9269bcf0bb29c4b8394b27af02a..d1a799199a68771ae3130a30a59625587c11b0b0 100644
index 4d71cc3b643f6f594c90f2f8f9e5b820ebdff080..7fb095c544294796768ce19c4409416e2848e296 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -2368,6 +2368,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -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
@@ -511,7 +511,7 @@ index 8e2775e873c92beed6a0e4e8ce1304ab8ca6e8d1..580f114fd99b017403c61e994d0c947d
public boolean canBypassPlayerLimit(GameProfile profile) {
diff --git a/net/minecraft/server/waypoints/ServerWaypointManager.java b/net/minecraft/server/waypoints/ServerWaypointManager.java
index 52fd730998535ea071bfc99b7cc2c254b9b656d7..0abf6f0265fcb916f2c2c76fb1313bd13444f4ad 100644
index 65a4956ba2f0e38da7ae4f8cd7ed6e4b02c015a9..044ded5ec81905e38efe8812ff6d50259d761385 100644
--- a/net/minecraft/server/waypoints/ServerWaypointManager.java
+++ b/net/minecraft/server/waypoints/ServerWaypointManager.java
@@ -24,6 +24,11 @@ public class ServerWaypointManager implements WaypointManager<WaypointTransmitte
@@ -526,7 +526,7 @@ index 52fd730998535ea071bfc99b7cc2c254b9b656d7..0abf6f0265fcb916f2c2c76fb1313bd1
this.waypoints.add(waypoint);
for (ServerPlayer serverPlayer : this.players) {
@@ -59,6 +64,11 @@ public class ServerWaypointManager implements WaypointManager<WaypointTransmitte
@@ -63,6 +68,11 @@ public class ServerWaypointManager implements WaypointManager<WaypointTransmitte
// Folia - region threading
// Lophine start - unsafe waypoint bar
if (!fun.bm.lophine.config.modules.experiment.CommandConfig.waypoint) return;
@@ -8,7 +8,7 @@ As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
index f8bc2679d9eeaa5d179ab70a4cc5c97522bcc674..a536dc4e09b22b69bcdf59871a9cccfcf8129395 100644
index 0d906776e10605ecfe7c681dbf313cf25080d2e8..1f44a59e46f86ca055e89631d831a38f4c3e238e 100644
--- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
@@ -754,6 +754,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
@@ -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() {
@@ -55,7 +55,7 @@ index 3c94631f468f4ee1b46de9981c17171e5901d745..f7a2a19c0832ca90536d09de23f4ec9a
serverLevel.fillReportDetails(crashReport);
throw new ReportedException(crashReport);
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index a4ee2bcdb31f889c7edbbc422636782c0d5d0af6..42bf4f0430a232a10cffb47d0d33f586afdc2a4d 100644
index c58b88f09e6f08d178fcb17a13dd790c3cb62aae..c0ba36849da0983117901f0cd04b2c785df0010b 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -840,7 +840,20 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -168,7 +168,7 @@ index 9b9842462b210b0407279ee8a8e9ccecf3ead005..1057f3bd32ff4c8f93f09251341c2439
}
diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
index a536dc4e09b22b69bcdf59871a9cccfcf8129395..a557045888b153bb046a25d276d811b9146a7874 100644
index 1f44a59e46f86ca055e89631d831a38f4c3e238e..39c075173b77b5197af5d4ad3fe235ea0d2df756 100644
--- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
@@ -378,7 +378,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
@@ -765,7 +765,7 @@ index e9749b66239d7562ecf22002bfbaa95df197050f..6e3841ed012eee16dcaa61f70edfc1d8
this.getBlock().affectNeighborsAfterRemoval(this.asState(), level, pos, movedByPiston);
}
diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
index a557045888b153bb046a25d276d811b9146a7874..568427a952a24012c17f64f62b6545a9d89c8c8a 100644
index 39c075173b77b5197af5d4ad3fe235ea0d2df756..b8a2ac505d121b46c33db2fbf4d02c5bc60c6c0f 100644
--- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
@@ -401,20 +401,26 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
@@ -46,7 +46,7 @@ index 8f112f382190d606a87f38da8bcdf8dd5f31d1c5..f27915e43df8e627000cda1cb213a953
// CraftBukkit start
if (bukkitState != null) {
diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
index 568427a952a24012c17f64f62b6545a9d89c8c8a..49a740bf17b2bed8010e347747b170b69ac26929 100644
index b8a2ac505d121b46c33db2fbf4d02c5bc60c6c0f..7a8ceb00eb5845a1d5011a518f63a9cabf131f3c 100644
--- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
@@ -971,12 +971,14 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
@@ -0,0 +1,51 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Wed, 17 Dec 2025 00:20:57 +0800
Subject: [PATCH] Add null check in RegionizedWorldData >> conections
some of our function patches will crash because of get null in conn.getPlayer(), then when we process player.chunkPosition() will get NPE, so we need to fix it.
diff --git a/io/papermc/paper/threadedregions/RegionizedWorldData.java b/io/papermc/paper/threadedregions/RegionizedWorldData.java
index 7cc41630a39c1e29840d70eee78473d06b7c11cd..d0ab09a77953c1ccd309637ba80c47a37114f082 100644
--- a/io/papermc/paper/threadedregions/RegionizedWorldData.java
+++ b/io/papermc/paper/threadedregions/RegionizedWorldData.java
@@ -230,14 +230,17 @@ public final class RegionizedWorldData {
regionToData.get(CoordinateUtils.getChunkKey(pos.x >> chunkToRegionShift, pos.z >> chunkToRegionShift)).itemEntityMovementTrackerMap.put(key, tracker);
}
// Luminol end
+ Set<Connection> cons = new java.util.HashSet<>(from.connections.size()); // Lophine - save all connections we have processed in connection process
// connections
for (final Connection conn : from.connections) {
final ServerPlayer player = conn.getPlayer();
+ if (player == null) continue; // Lophine - skip null player connections
final ChunkPos pos = player.chunkPosition();
// Note: It is impossible for an entity in the world to _not_ be in an entity chunk, which means
// the chunk holder must _exist_, and so the region section exists.
regionToData.get(CoordinateUtils.getChunkKey(pos.x >> chunkToRegionShift, pos.z >> chunkToRegionShift))
.connections.add(conn);
+ cons.add(conn); // Lophine - save all connections we have processed in connection process
}
// entities
for (final ServerPlayer player : from.localPlayers) {
@@ -247,6 +250,13 @@ public final class RegionizedWorldData {
final RegionizedWorldData into = regionToData.get(CoordinateUtils.getChunkKey(pos.x >> chunkToRegionShift, pos.z >> chunkToRegionShift));
into.localPlayers.add(player);
into.nearbyPlayers.addPlayer(player);
+ // Lophine start - we need to process the connection if player's connection is not processed, if connection.player is null, next tick will clean up it
+ final Connection conn = player.connection.connection;
+ if (!cons.contains(conn)) {
+ regionToData.get(CoordinateUtils.getChunkKey(pos.x >> chunkToRegionShift, pos.z >> chunkToRegionShift))
+ .connections.add(conn);
+ }
+ // Lophine end
}
for (final Entity entity : from.allEntities) {
final ChunkPos pos = entity.chunkPosition();
@@ -577,6 +587,7 @@ public final class RegionizedWorldData {
private static void cleanUpConnection(final Connection conn) {
// note: ALL connections HERE have a player
final ServerPlayer player = conn.getPlayer();
+ if (player == null) return; // Lophine - skip null player connections
// now that the connection is removed, we can allow this region to die
player.level().moonrise$getChunkTaskScheduler().chunkHolderManager.removeTicketAtLevel(
ServerGamePacketListenerImpl.DISCONNECT_TICKET, player.connection.disconnectPos,
@@ -0,0 +1,18 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Tue, 13 Jan 2026 21:04:59 +0800
Subject: [PATCH] Add removed check before all checks start in addEffect
diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
index 205e2648569b7c18f488aa4d3d9ec944bb1babf4..c6ee14f14e051b30012e608b84a055ff2468850d 100644
--- a/net/minecraft/world/entity/LivingEntity.java
+++ b/net/minecraft/world/entity/LivingEntity.java
@@ -1174,6 +1174,7 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
public boolean addEffect(MobEffectInstance effectInstance, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause, boolean fireEvent) {
// Paper end - Don't fire sync event during generation
// org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot // Paper - move to API
+ if (this.isRemoved()) return false; // Lophine - shouldn't be possible to add effects to removed entity
if (!this.hasNullCallback()) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add effects to entities asynchronously"); // Folia - region threading
if (this.isTickingEffects) {
this.effectsToProcess.add(new ProcessableEffect(effectInstance, cause));
@@ -0,0 +1,49 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: mrhua269 <mrhua269@gmail.com>
Date: Sat, 3 Jan 2026 10:42:39 +0800
Subject: [PATCH] Luminol - 1.21.11: Sync dragon part when teleportation or
firstly created
A simple fix of https://github.com/PaperMC/Folia/issues/371
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
index e3a49083e75116622bd95fb17533730a6fb36752..545697d9c081a270ff47b332baee68372c0dd67d 100644
--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
@@ -96,6 +96,7 @@ public final class ServerEntityLookup extends EntityLookup {
if (entity instanceof ThrownEnderpearl enderpearl) {
this.addEnderPearl(CoordinateUtils.getChunkKey(enderpearl.chunkPosition()), enderpearl.getId()); // Folia - region threading
}
+ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon dragon) dragon.syncDragonPartsAfterTeleportTransform(); // Luminol - Sync dragon part when teleportation or firstly created
entity.registerScheduler(); // Paper - optimise Folia entity scheduler
}
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index f63252c580c313ccf89304866cfc6503f4b26052..f0ddf0bf462a316f82f4babac17af6860b6ba143 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -4325,6 +4325,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
Entity copy = this.getType().create(destination, EntitySpawnReason.DIMENSION_TRAVEL);
copy.restoreFrom(this);
copy.transform(pos, yaw, pitch, velocity);
+ if (copy instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon dragon) dragon.syncDragonPartsAfterTeleportTransform(); // Luminol - Sync dragon part when teleportation or firstly created
if (toSameWorld) copy.notifyLithiumTrackerIfNeeded();// Luminol - region threading for sleeping block entity
// vanilla code used to call remove _after_ copying, and some stuff is required to be after copy - so add hook here
// for example, clearing of inventory after switching dimensions
diff --git a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
index 84d215a498d9bf6f1232bf4af5a4a98d3ba9b131..2aa57b995d9fcfbd335655ef383314ce11d93707 100644
--- a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
+++ b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
@@ -1003,4 +1003,12 @@ public class EnderDragon extends Mob implements Enemy {
return flag ? i : 0;
}
// CraftBukkit end
+
+ // Luminol start - Sync dragon part when teleportation or firstly created
+ public void syncDragonPartsAfterTeleportTransform() {
+ for (EnderDragonPart part : this.subEntities) {
+ this.tickPart(part, 0.0, 0.0, 0.0); // offset -> 0.0
+ }
+ }
+ // Luminol end
}
@@ -79,7 +79,7 @@ index 7883b7973570b5f275a03c6aac4d3b4392e7fa6e..18d066c605f8027d9b1170b64ef1428f
@Override
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
index 679529d94c16960527e5d76adb61c7e536f55d3e..503e11640bd2b6f7c854747723d26fe9cd67f490 100644
index 7334422a6e0e7faec675cf038ccbca0e284f61b5..7fee749197b6c6a8890567a7a7819f45b290012b 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
@@ -338,7 +338,7 @@ public final class CraftMagicNumbers implements UnsafeValues {
@@ -0,0 +1,17 @@
package fun.bm.lophine.config.modules.optimizations;
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(category = EnumConfigCategory.OPTIMIZATIONS, name = "waypoint")
public class WayPointOptimizedTableConfig implements IConfigModule {
@HotReloadUnsupported
@ConfigInfo(name = "optimizedTable", comments = """
Should use optimized table instead of normal concurrent table for waypoints.
May improve performance when there are many waypoints and players.
When enabled, more memory is needed to store data.""")
public static boolean optimizedTable = false;
}
@@ -55,7 +55,7 @@ public class EntitiesCounterUtil {
synchronized (globalLoadedEntities) {
globalLoadedEntities.get(level).remove(uniqueId);
}
synchronized (mobsMap) {
synchronized (mobsAreaMap) {
mobsAreaMap.get(level).remove(uniqueId);
}
}
@@ -91,6 +91,7 @@ public class EntitiesCounterUtil {
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) {
@@ -132,6 +133,7 @@ public class EntitiesCounterUtil {
// 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 ||
@@ -160,25 +162,4 @@ public class EntitiesCounterUtil {
return new NaturalSpawner.SpawnState(getTotalChunkCount(level), map, potentialCalculator, calculator);
// Lophine end - Copy from net/minecraft/world/level/NaturalSpawner
}
public static class ChunkCounter {
private final int chunkCount;
private int timeout = 20;
public void tick() {
timeout--;
}
public ChunkCounter(int chunkCount) {
this.chunkCount = chunkCount;
}
public boolean timeout() {
return timeout <= 0;
}
public int getChunkCount() {
return chunkCount;
}
}
}
@@ -0,0 +1,38 @@
package fun.bm.lophine.utils.concurrent;
import java.util.List;
import java.util.Map;
public abstract class AbstractConcurrentTable<X, Y, Z> {
public abstract void putOrUpdate(X x, Y y, Z z);
public abstract void put(X x, Y y, Z z);
public abstract void remove(X x, Y y, Z z);
public abstract List<Z> getZ(X x, Y y);
public abstract List<Y> getY(X x, Z z);
public abstract List<X> getX(Y y, Z z);
public abstract List<Map.Entry<X, Y>> getXY(Z z);
public abstract List<Map.Entry<Y, Z>> getYZ(X x);
public abstract List<Map.Entry<X, Z>> getXZ(Y y);
public abstract List<X> getAllX();
public abstract List<Y> getAllY();
public abstract List<Z> getAllZ();
public abstract void clearXY(Z z);
public abstract void clearYZ(X x);
public abstract void clearXZ(Y y);
public abstract void clearAll();
}
@@ -0,0 +1,150 @@
package fun.bm.lophine.utils.concurrent;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.function.Predicate;
public class ConcurrentTable<X, Y, Z> extends AbstractConcurrentTable<X, Y, Z> {
protected final ConcurrentLinkedDeque<TableEntry<X, Y, Z>> data = new ConcurrentLinkedDeque<>();
@Override
public void putOrUpdate(X x, Y y, Z z) {
for (TableEntry<X, Y, Z> entry : data) {
if (entry.getX().equals(x) && entry.getY().equals(y)) {
entry.setZ(z);
return;
}
}
this.put(x, y, z);
}
@Override
public void put(X x, Y y, Z z) {
data.add(new TableEntry<>(x, y, z));
}
@Override
public void remove(X x, Y y, Z z) {
data.removeIf(entry -> entry.getX().equals(x) && entry.getY().equals(y) && entry.getZ().equals(z));
}
@Override
public List<Z> getZ(X x, Y y) {
return filterAndCollect(
entry -> entry.getX().equals(x) && entry.getY().equals(y),
TableEntry::getZ
);
}
@Override
public List<Y> getY(X x, Z z) {
return filterAndCollect(
entry -> entry.getX().equals(x) && entry.getZ().equals(z),
TableEntry::getY
);
}
@Override
public List<X> getX(Y y, Z z) {
return filterAndCollect(
entry -> entry.getY().equals(y) && entry.getZ().equals(z),
TableEntry::getX
);
}
@Override
public List<Map.Entry<X, Y>> getXY(Z z) {
return filterAndMap(
entry -> entry.getZ().equals(z),
TableEntry::getX,
TableEntry::getY
);
}
@Override
public List<Map.Entry<Y, Z>> getYZ(X x) {
return filterAndMap(
entry -> entry.getX().equals(x),
TableEntry::getY,
TableEntry::getZ
);
}
@Override
public List<Map.Entry<X, Z>> getXZ(Y y) {
return filterAndMap(
entry -> entry.getY().equals(y),
TableEntry::getX,
TableEntry::getZ
);
}
@Override
public List<X> getAllX() {
return collectAll(TableEntry::getX);
}
@Override
public List<Y> getAllY() {
return collectAll(TableEntry::getY);
}
@Override
public List<Z> getAllZ() {
return collectAll(TableEntry::getZ);
}
@Override
public void clearXY(Z z) {
data.removeIf(entry -> entry.getZ().equals(z));
}
@Override
public void clearYZ(X x) {
data.removeIf(entry -> entry.getX().equals(x));
}
@Override
public void clearXZ(Y y) {
data.removeIf(entry -> entry.getY().equals(y));
}
@Override
public void clearAll() {
data.clear();
}
private <T> List<T> filterAndCollect(Predicate<TableEntry<X, Y, Z>> filter,
java.util.function.Function<TableEntry<X, Y, Z>, T> mapper) {
List<T> result = new ArrayList<>();
for (TableEntry<X, Y, Z> entry : data) {
if (filter.test(entry)) {
result.add(mapper.apply(entry));
}
}
return result;
}
private <K, V> List<Map.Entry<K, V>> filterAndMap(Predicate<TableEntry<X, Y, Z>> filter,
java.util.function.Function<TableEntry<X, Y, Z>, K> keyMapper,
java.util.function.Function<TableEntry<X, Y, Z>, V> valueMapper) {
List<Map.Entry<K, V>> list = new ArrayList<>();
for (TableEntry<X, Y, Z> entry : data) {
if (filter.test(entry)) {
list.add(new AbstractMap.SimpleEntry<>(keyMapper.apply(entry), valueMapper.apply(entry)));
}
}
return list;
}
private <T> List<T> collectAll(java.util.function.Function<TableEntry<X, Y, Z>, T> mapper) {
List<T> result = new ArrayList<>();
for (TableEntry<X, Y, Z> entry : data) {
result.add(mapper.apply(entry));
}
return result;
}
}
@@ -0,0 +1,197 @@
package fun.bm.lophine.utils.concurrent;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import java.util.function.Predicate;
public class OptimizedConcurrentTable<X, Y, Z> extends ConcurrentTable<X, Y, Z> {
private final ConcurrentHashMap<X, ConcurrentHashMap<Y, Set<Z>>> xyIndex = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Y, ConcurrentHashMap<Z, Set<X>>> yzIndex = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Z, ConcurrentHashMap<X, Set<Y>>> zxIndex = new ConcurrentHashMap<>();
@Override
public void putOrUpdate(X x, Y y, Z z) {
boolean updated = false;
for (TableEntry<X, Y, Z> entry : data) {
if (entry.getX().equals(x) && entry.getY().equals(y)) {
removeFromIndex(xyIndex, entry.getX(), entry.getY(), entry.getZ());
removeFromIndex(yzIndex, entry.getY(), entry.getZ(), entry.getX());
removeFromIndex(zxIndex, entry.getZ(), entry.getX(), entry.getY());
entry.setZ(z);
putData(x, y, z);
updated = true;
break;
}
}
if (!updated) {
this.put(x, y, z);
}
}
private void putData(X x, Y y, Z z) {
xyIndex.computeIfAbsent(x, k -> new ConcurrentHashMap<>())
.computeIfAbsent(y, k -> ConcurrentHashMap.newKeySet()).add(z);
yzIndex.computeIfAbsent(y, k -> new ConcurrentHashMap<>())
.computeIfAbsent(z, k -> ConcurrentHashMap.newKeySet()).add(x);
zxIndex.computeIfAbsent(z, k -> new ConcurrentHashMap<>())
.computeIfAbsent(x, k -> ConcurrentHashMap.newKeySet()).add(y);
}
@Override
public void put(X x, Y y, Z z) {
super.put(x, y, z);
putData(x, y, z);
}
@Override
public void remove(X x, Y y, Z z) {
super.remove(x, y, z);
removeFromIndex(xyIndex, x, y, z);
removeFromIndex(yzIndex, y, z, x);
removeFromIndex(zxIndex, z, x, y);
}
private <K, V, T> void removeFromIndex(ConcurrentHashMap<K, ConcurrentHashMap<V, Set<T>>> index,
K key1, V key2, T value) {
index.computeIfPresent(key1, (k, map) -> {
map.computeIfPresent(key2, (k2, set) -> {
set.remove(value);
return set.isEmpty() ? null : set;
});
return map.isEmpty() ? null : map;
});
}
public void removeAll(Predicate<TableEntry<X, Y, Z>> predicate) {
data.removeIf(entry -> {
boolean shouldRemove = predicate.test(entry);
if (shouldRemove) {
removeFromIndex(xyIndex, entry.getX(), entry.getY(), entry.getZ());
removeFromIndex(yzIndex, entry.getY(), entry.getZ(), entry.getX());
removeFromIndex(zxIndex, entry.getZ(), entry.getX(), entry.getY());
}
return shouldRemove;
});
}
public boolean putIfAbsent(X x, Y y, Z z) {
if (data.stream().anyMatch(entry ->
Objects.equals(entry.getX(), x) &&
Objects.equals(entry.getY(), y) &&
Objects.equals(entry.getZ(), z))) {
return false;
}
put(x, y, z);
return true;
}
@Override
public List<Z> getZ(X x, Y y) {
Set<Z> result = xyIndex.getOrDefault(x, new ConcurrentHashMap<>()).get(y);
return result != null ? new ArrayList<>(result) : new ArrayList<>();
}
@Override
public List<Y> getY(X x, Z z) {
Set<Y> result = zxIndex.getOrDefault(z, new ConcurrentHashMap<>()).get(x);
return result != null ? new ArrayList<>(result) : new ArrayList<>();
}
public List<X> getX(Y y, Z z) {
Set<X> result = yzIndex.getOrDefault(y, new ConcurrentHashMap<>()).get(z);
return result != null ? new ArrayList<>(result) : new ArrayList<>();
}
@Override
public List<Map.Entry<X, Y>> getXY(Z z) {
return buildDataFromIndex(zxIndex.get(z));
}
@Override
public List<Map.Entry<Y, Z>> getYZ(X x) {
return buildDataFromIndex(xyIndex.get(x));
}
@Override
public List<Map.Entry<X, Z>> getXZ(Y y) {
return reverseDataFromIndex(yzIndex.get(y));
}
@Override
public List<X> getAllX() {
Set<X> resultSet = new HashSet<>(xyIndex.keySet());
return new ArrayList<>(resultSet);
}
@Override
public List<Y> getAllY() {
Set<Y> resultSet = new HashSet<>(yzIndex.keySet());
return new ArrayList<>(resultSet);
}
@Override
public List<Z> getAllZ() {
Set<Z> resultSet = new HashSet<>(zxIndex.keySet());
return new ArrayList<>(resultSet);
}
@Override
public void clearXY(Z z) {
super.clearXY(z);
zxIndex.remove(z);
}
@Override
public void clearYZ(X x) {
super.clearYZ(x);
xyIndex.remove(x);
}
@Override
public void clearXZ(Y y) {
super.clearXZ(y);
yzIndex.remove(y);
}
@Override
public void clearAll() {
super.clearAll();
xyIndex.clear();
yzIndex.clear();
zxIndex.clear();
}
private <K, V, R> List<R> buildEntriesFromIndex(ConcurrentHashMap<K, Set<V>> indexMap,
BiFunction<K, V, R> entryCreator) {
List<R> result = new ArrayList<>();
if (indexMap != null) {
for (Map.Entry<K, Set<V>> entry : indexMap.entrySet()) {
K key = entry.getKey();
Set<V> valueSet = entry.getValue();
if (valueSet != null && !valueSet.isEmpty()) {
for (V value : valueSet) {
result.add(entryCreator.apply(key, value));
}
}
}
}
return result;
}
private <K, V> List<Map.Entry<K, V>> buildDataFromIndex(ConcurrentHashMap<K, Set<V>> indexMap) {
return buildEntriesFromIndex(indexMap, AbstractMap.SimpleEntry::new);
}
private <K, V> List<Map.Entry<V, K>> reverseDataFromIndex(ConcurrentHashMap<K, Set<V>> indexMap) {
return buildEntriesFromIndex(indexMap, (key, value) -> new AbstractMap.SimpleEntry<>(value, key));
}
}
@@ -0,0 +1,29 @@
package fun.bm.lophine.utils.concurrent;
public class TableEntry<X, Y, Z> {
private final X x;
private final Y y;
private Z z;
public TableEntry(X x, Y y, Z z) {
this.x = x;
this.y = y;
this.z = z;
}
public X getX() {
return x;
}
public Y getY() {
return y;
}
public Z getZ() {
return z;
}
public void setZ(Z z) {
this.z = z;
}
}
@@ -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.3
profile:
name: qodana.recommended
include:
- name: CheckDependencyLicenses