Compare commits

...

95 Commits

Author SHA1 Message Date
TinyYana 49e92a5c41 新增繁體中文版README、CONTRIBUTING、設定語系檔;及日文版貢獻指南
Co-authored-by: corn323 <wingsky34@gmail.com>
2026-08-10 20:19:58 +08:00
Bacteriawa a9f3c2c291 Fix PCA sync protocol for async portal transfers #166
Update the PCA sync protocol patch to use getHandleRaw() instead of getHandle() when syncing entities to clients. This ensures proper ownership checking when the owner is a destination-world clone being restored during an async portal transfer.
2026-08-10 18:09:24 +08:00
Helvetica Volubi c619486da9 fix: fix bot spawn problem 2026-08-09 03:24:54 +08:00
dependabot[bot] 5cd355881a [ci skip]Bump actions/setup-java from 5.6.0 to 5.7.0 (#171)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5.6.0 to 5.7.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v5.6.0...v5.7.0)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: 5.7.0
  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>
2026-08-08 16:23:04 +08:00
Bacteriawa 5a28a619b7 Make RegionizedWorldData.connections public
Change the connections field in RegionizedWorldData from private to public (ReferenceList<Connection>) and add a comment indicating it's exposed for BotList access. The change is in the 0112 patch file; related patch index updates are included in subsequent feature patch files.
2026-08-08 02:04:59 +08:00
Bacteriawa 9b5f3e1bd9 Update Folia 2026-08-08 01:51:27 +08:00
Helvetica Volubi 132ee404d4 refactor: rename compatibility helper classes for clarity 2026-08-05 00:56:21 +08:00
Helvetica Volubi 0e10a8e8b0 merge patches 2026-08-05 00:49:01 +08:00
Helvetica Volubi 24e68fb203 fix typo 2026-08-05 00:42:33 +08:00
Helvetica Volubi 141c226294 Replace fast compressor with high compressor in buffered linear format 2026-08-05 00:26:22 +08:00
Helvetica Volubi 1049012aff chore: simplify code 2026-08-03 02:25:33 +08:00
Helvetica Volubi 302825cc7e feat: optimize RegionizedTaskQueue TTL and reduce ticket operations in nether portal searching 2026-08-01 16:38:23 +08:00
dependabot[bot] d806e4a423 [ci skip]Bump actions/setup-java from 5 to 5.6.0 (#168)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5 to 5.6.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v5...v5.6.0)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: 5.6.0
  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>
2026-08-01 16:13:12 +08:00
Helvetica Volubi 952c4321d9 fix: fix up BotList 2026-07-31 01:07:56 +08:00
Helvetica Volubi dd7ef6701a feat: update fakeplayer code from Leaves 2026-07-30 02:13:08 +08:00
Helvetica Volubi 43036f971c feat: update Entity tick and removal limiter 2026-07-30 00:58:37 +08:00
Helvetica Volubi 25ce58c40d fix: fix folia nether portal ticket placement 2026-07-28 21:23:43 +08:00
Helvetica Volubi 106f1edb09 set release to stable cannel 2026-07-28 15:38:35 +08:00
Helvetica Volubi eea830188f update some optimizations from upstream 2026-07-28 15:37:41 +08:00
Helvetica Volubi f517cc0b8a feat: update b_linear format from upstream
Because of some reasons, this patch will be anonymous and hide the original commit address.
2026-07-28 02:03:00 +08:00
Helvetica Volubi d90c8567d3 [ci skip] simplify code 2026-07-28 01:29:59 +08:00
Helvetica Volubi 78b3a8a6e7 [ci skip] Use ComfreyWeight instead of HyacinthusWeight 2026-07-27 03:43:02 +08:00
Helvetica Volubi 2f93769135 [ci skip] Use ComfreyWeight instead of HyacinthusWeight 2026-07-27 03:32:49 +08:00
Helvetica Volubi 4e277ad91e [ci skip] Update clip version 2026-07-27 01:17:13 +08:00
Helvetica Volubi 1620687dba [ci skip] fix typo 2026-07-26 19:09:05 +08:00
Bacteriawa 7b7eb911c9 Update Folia 2026-07-26 17:16:36 +08:00
Helvetica Volubi 0724ba3fa9 fix: bind packetListenerImpl to Connection when place new photographer (#160) 2026-07-26 01:38:29 +08:00
Helvetica Volubi e6d6562f4f update zh_cn.json 2026-07-26 00:45:33 +08:00
Helvetica Volubi 580201740e fix: update references and add additional launch parameters in documentation 2026-07-25 21:33:59 +08:00
Helvetica Volubi d54b248f27 fix: fix I18n in config comments 2026-07-25 19:45:01 +08:00
Helvetica Volubi 11b0b05a93 [ci skip]fix workflow & fix typo 2026-07-25 19:21:42 +08:00
Helvetica Volubi 7db058ef56 feat: add zh_cn config comment support & update en_us
Co-authored-by: flowingsun <rain_wyc@outlook.com>
2026-07-25 16:52:23 +08:00
Helvetica Volubi 019adaf46f fix: replace NullPlugin with MinecraftInternalPlugin in delayed task scheduling 2026-07-25 14:08:35 +08:00
Helvetica Volubi 6f55ed7e8e [ci skip]fix: update contribution guidelines for clarity on file submission 2026-07-25 13:17:02 +08:00
Helvetica Volubi 12c58f69b3 [ci skip]docs: add localized comment support instructions for configuration entries 2026-07-25 02:17:06 +08:00
Helvetica Volubi 0fd3b32108 fix: fix typo 2026-07-24 22:58:05 +08:00
Helvetica Volubi b8f43b47e6 resort comments in json 2026-07-24 22:16:25 +08:00
Helvetica Volubi 5f718d45a1 pre update for config comment I18n support 2026-07-24 20:34:55 +08:00
Helvetica Volubi 1cbb19d9e4 fix a losing api 2026-07-24 01:52:18 +08:00
Helvetica Volubi 4339b5e8a6 [ci skip] fix typo 2026-07-23 01:29:34 +08:00
Helvetica Volubi bcbc0685b2 [ci skip] update more information 2026-07-23 00:48:51 +08:00
Helvetica Volubi c9bfe55ce4 introduce 26.1.2 changes
Fakeplayer Action Gui - Stage 1 (#161)

Still needs some tweaks to fit Leaves' changes

* pre add gui (not implemented)

* fallback one change in Actions

* pre for new structure of gui register

* Enhance GUI node structure with item support and command handling

* feat: update GUI node handling in bot actions

* feat: enhance command building logic in GUI nodes

* fix a bug

* feat: implement GUI navigation and command execution in BotAction GUI

* feat: refactor GuiNode and related classes to use Item instead of ItemStack

* feat: update command building logic to include additional action parameters

* feat: simplify command syntax in BotAction GUI and update inventory view handling

* feat: add ActionType enum and implement action confirmation in BotAction GUI

* feat: enhance ActionType handling and selection in BotAction GUI

* feat: integrate GUI data handling in bot actions and enhance navigation

* fix up

* because of tmp move to new org, rebrand

* Implement pagination for bot action GUI and enhance action stop functionality

* ci updates

* replace boarder item

* fix https://github.com/LophineCraft/Lophine/pull/161#issuecomment-5012127501 issues - 1

* fix https://github.com/LophineCraft/Lophine/pull/161#issuecomment-5012127501 issues - 2

* fix index

* replace some items

* feat: use the same item shown for stop & start action

* remove 2 todos, we wouldn't provide them

* refactor: simplify rendering methods in BotActionGuiContainer

* feat: add empty action placeholder and simplify item rendering

* fix: remove extra suggestions

* fix: update GUI icons for jump, move, and swap actions

* fix: update comment in open action gui

* set release mode to true

---------

Co-authored-by: xiaoxijun <A3167717663@hotmail.com>
2026-07-23 00:23:03 +08:00
Helvetica Volubi 04bedd5dfc [ci skip] update some information 2026-07-22 01:31:07 +08:00
Helvetica Volubi 4cc1be5faa [ci skip] do not show luminol anymore because we are starting hard fork 2026-07-22 00:15:57 +08:00
Helvetica Volubi 9c00870696 [ci skip] fix docs 2026-07-20 03:15:35 +08:00
Helvetica Volubi e988b087a6 fix: fallback night-config version to fix config save problem 2026-07-19 17:37:19 +08:00
Helvetica Volubi 4d60979f75 merge data command related patches 2026-07-19 02:06:25 +08:00
Helvetica Volubi c8b50d84a9 2 patches from EarthMe - Camellia
Co-authored-by: MrHua269 <mrhua269@gmail.com>
2026-07-18 02:00:06 +08:00
Helvetica Volubi 6744e2c6d5 merge some patches 2026-07-18 01:46:03 +08:00
Helvetica Volubi 0678cc6e46 Start hard-fork from Luminol 2026-07-18 01:27:14 +08:00
Helvetica Volubi f4aea025c1 [ci skip] some info update 2026-07-12 21:35:51 +08:00
Helvetica Volubi 847fec5cb0 [ci skip] fix ci for update 2026-07-12 16:22:59 +08:00
Helvetica Volubi 576d2322ee [ci skip] ci updates 2026-07-11 01:21:56 +08:00
Helvetica Volubi 917b2cf3d9 fix: some fix for bot action
fix stop command could stop all the same type of action when a stop action called

fix some typo in command reply
2026-07-11 01:03:57 +08:00
Helvetica Volubi 6309c478be [ci skip] because of tmp move to new org, rebrand 2026-07-10 00:06:51 +08:00
Helvetica Volubi 012ea4f5b5 fix bot & replay api 2026-07-07 00:52:11 +08:00
Helvetica Volubi 377c81f26f fix bot command register 2026-07-06 20:24:48 +08:00
Helvetica Volubi 91e00f34ff Update Luminol to fix some bugs 2026-07-06 01:25:07 +08:00
Helvetica Volubi ac51af2fbc Update Luminol
Replace MenthaMC Maven repo with Bacteriawa

Update Maven repository URL from https://repo.menthamc.org/... to https://repo.bacteriawa.com/... across project files. Changes include README.md and README_EN.md (repository id changed to "repository" and URL updated), build.gradle.kts (rename menthaMavenPublicUrl to bacteriawaMavenPublicUrl and switch repository used for dependencies and publishing, update publishing repo name), luminol-server/build.gradle.kts.patch (use bacteriawaMavenPublicUrl), and settings.gradle.kts (add new Maven URL). This centralizes the project to use the Bacteriawa Maven host for dependency resolution and publishing.

Origin Commit: https://github.com/LuminolMC/Luminol/commit/ba28403ff6d15da3cf8b1af9828bbd1f997a7b9e

Co-authored-by: Bacteriawa <a3167717663@hotmail.com>
2026-07-05 22:07:45 +08:00
Helvetica Volubi 6df2e43b80 Update Luminol 2026-07-03 06:03:06 +08:00
Helvetica Volubi a62cc74f09 [ci skip]push repo 2026-07-02 20:01:53 +08:00
Helvetica Volubi f703bda607 Merge 26.1.2 changes to 26.2 branch 2026-07-02 19:40:42 +08:00
Helvetica Volubi 8a7b605fa6 feat: old leader zombie health logic
close https://github.com/LuminolMC/Lophine/issues/157
2026-07-02 19:40:42 +08:00
xiaoxijun 9f2f53001a #154 (#155)
* Add PCA sync protocol support

Introduce PCA sync protocol and integrate it into Carpet/Leaves.

- Added org/leavesmc/leaves/protocol/PcaSyncProtocol.java: full implementation of the PCA sync protocol (handlers for sync requests, watcher maps, update payloads, player/block watch management, scheduling, and config-driven behavior).
- Added fun/bm/lophine/config/modules/function/protocol/PcaSyncProtocolConfig.java and enum PcaPlayerEntityType.java to drive enabling and player-entity sync policy.
- Integrated protocol into Carpet: register new carpet rules (pcaSyncProtocol, pcaSyncPlayerEntity) in CarpetCompatSync and added batching to avoid excessive updates when rules change.
- Enhanced CarpetServerProtocol to track active players, batch rule updates, and send server rule data safely on the server thread.
- Added a Minecraft patch (0046-PCA-sync-protocol.patch) to trigger syncs from setChanged() in containers, chest boats, minecarts and BlockEntity updates so watched clients receive entity/block-entity updates.

This enables PCA clients to watch and receive synchronized block-entity and entity state on demand, while keeping updates gated by config and player permissions and improving rule-update performance via batching.

* Add Vanilla-like experience config and updates

Introduce a new VanillaLikeExperienceConfig module and a patch (0047) that gates multiple Paper/Luminol behavior changes behind this toggle to restore more vanilla-like technical behavior when enabled. The patch injects conditional checks across many server classes (entity merging, phantoms, pistons, hoppers, TNT handling, explosions, portal/piston protections, item merging, teleportation, spawn counting, etc.). Also adjust PCA sync protocol references in the existing 0046 patch to use the updated fun.bm.lophine package path. Includes the new fun.bm.lophine.config.modules.fixes.VanillaLikeExperienceConfig.java file.

* Restore vanilla ender pearl loading on Folia

Add patch (features/0048) that restores vanilla ender-pearl loading behavior on Folia. Reintroduces ender-pearl tracking and persistence: ServerPlayer now uses a thread-safe enderPearls set and saves pearl tags, ThrownEnderpearl maintains a lastSavedEnderPearlTag (updated on construction, owner changes, spawn and restore) and updates/clears it on tick/removal, and EnderpearlItem updates the saved tag when spawning a pearl. PlayerList and BotList now schedule pearl removal via the Bukkit taskScheduler when players/bots quit. The changes preserve Paper's legacyEnderPearlBehavior checks where applicable and add logging and safer tag handling to avoid saving invalid snapshots.

* Restore vanilla ender pearl loading

Restore vanilla ender-pearl loading behavior on Folia by preserving the last saved NBT for thrown pearls. ThrownEnderpearl.java: add a volatile CompoundTag lastSavedEnderPearlTag, provide a defensive-copying getter getLastSavedEnderPearlTag(), adjust @Nullable annotation placement, and update logger/comment. Minor index/format updates in ServerPlayer.java, PlayerList.java and EnderpearlItem.java.

* remove extra import

* Add Precise mob cap and adjust ender pearl patch

Introduce a Precise mob cap feature and integrate it into Paper/Folia world/region lifecycle. Adds new patch features/0049-Precise-mob-cap.patch which modifies RegionizedWorldData, ServerChunkCache and ServerLevel to support an incremental, event-driven mob counting system that can supersede the existing GlobalEntitiesCounter. Adds PreciseMobCapConfig (toggle and behavior flags) and PreciseMobCapCounter (unique id management, area map registration, per-level global counters, spawnable-chunk tracking, spawn state creation, and cleanup on unload). Also updates the existing 0048 ender-pearl loading patch file (minor import/index adjustments). The Precise mob cap counts only relevant mobs (configurable to match vanilla or Paper-optimized behavior), hooks into entity tracking start/end, and requires per-player-mob-spawns=false to be set in Paper world defaults when enabled.

* remove extra import

* pre merge config

* transform config

* merge config

* rebuild patches

* fix a bug in new counter

* simplify code

---------

Co-authored-by: Helvetica Volubi <suisuroru@blue-millennium.fun>
Co-authored-by: Helvetica Volubi <88063803+Suisuroru@users.noreply.github.com>
2026-07-02 19:40:42 +08:00
Helvetica Volubi f98781b7cc [ci skip]allow to push repo
we will trigger ci later
2026-07-02 17:55:45 +08:00
Helvetica Volubi 3251e0d8e0 Update Luminol 2026-07-01 23:27:02 +08:00
Helvetica Volubi 03dddb546d try update to 26.2 2026-06-29 21:38:11 +08:00
Helvetica Volubi 37c22202ab [ci skip] oops 2026-06-28 04:18:47 +08:00
Helvetica Volubi 2e67f978ec Update Luminol 2026-06-28 03:16:11 +08:00
Helvetica Volubi ed2f464914 fixup build 2026-06-25 18:12:18 +08:00
Helvetica Volubi 1cd09226f6 fixes #152 2026-06-25 17:49:55 +08:00
Helvetica Volubi 8cd8f4fbad Update Luminol 2026-06-25 17:48:52 +08:00
Bacteriawa e90c758a01 Lophine: add configs and update-suppression guards
Add Lophine-specific configuration support (global and carpet configs) and a temporary config transform step during init. Introduce config-controlled command registrations (function, scoreboard, trigger, tick) and enable tick-rate/command tweaks and an entity raytracing tracker flag. Fix datapack command save/listing behavior. Integrate Leaves update-suppression handling broadly: catch and convert update-suppression exceptions across packet handling, ticking, neighbor updates, block state updates and more; add configurable shulker CCE behavior and redstone upward-update compatibility. Provide an option to reintroduce an InstantNeighborUpdater (instant block updater) and other small compatibility/rebrand adjustments.
2026-06-23 01:35:57 +08:00
Helvetica Volubi e508462093 Update Luminol 2026-06-23 00:37:42 +08:00
dependabot[bot] b9c68a9b84 [ci skip]Bump actions/checkout from 6 to 7 (#153)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [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/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  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>
2026-06-20 16:36:49 +08:00
Helvetica Volubi 74d0b5ca5f Update Luminol & remove a unused config 2026-06-19 23:28:39 +08:00
Helvetica Volubi 4df4d07ae9 Merge the duplicate configuration items (#151)
* do not release & push repo before finished

* langs update

* updateSuppression config moved up

* redirect tick command option to carpet config system

* optimizedDragonRespawn

* diff in files

* counter rules transformed

* oops!

* some fakeplayer options moved up

* simplify

* creative no clip moved

* pre remove core module

* pre update luminol to further fix a bug that config not saved to real file

* fix up transform

* fix up transform

* fix up

* fix up some problem in PR

* redirect forgotten moved config

* fix bugs in PR

* refix

* switch for final merge
2026-06-15 20:27:41 +08:00
Helvetica Volubi cfc73efc9a Update Luminol 2026-06-14 18:43:18 +08:00
Helvetica Volubi 0989bcaec7 [ci skip]refactor: convert AbstractConcurrentTable to interface and update ConcurrentTable implementation 2026-06-13 01:19:56 +08:00
Helvetica Volubi 3763d0b885 Update clip & weight 2026-06-10 03:20:21 +08:00
Roberta001 cd3db4ae8c Fix bot connection region crash (#149)
* Fix: bot connection causing region crash, fix readme

* Fix NPE on Region Shutdown with bots

* simple fix up

* Revert "Fix NPE on Region Shutdown with bots"

This reverts commit b55c982cb9.

* Revert "fix: fix a bug in server shutdown with fakeplayer"

This reverts commit a721c27e96.

* fix: improve bot shutdown handling to prevent region crashes

---------

Co-authored-by: Helvetica Volubi <suisuroru@blue-millennium.fun>
2026-06-09 16:17:13 +08:00
Bacteriawa 11e9760eb3 Add validation and safety checks across protocols
Harden NBT/packet handling and add defensive checks across multiple protocols and utilities.

- ServerBot: Improved create-state NBT parsing, fill legacy fallbacks, validate skin list entries, and guard loading of saved actions/configs with try/catch and logged warnings.
- LeavesProtocolManager: Treat malformed payloads as rejected (INVALID_PAYLOAD), avoid throwing on decode failures, add safe bytebuf invocation with logging, and a selector description helper.
- REIServerProtocol: Enforce tag types and presence, validate indices and slot bounds, and improve error messages for malformed REI data.
- LitematicaSchematic: Validate schematic metadata (non-empty regions, palette), check region sizes and limit volume to a maximum, and avoid silent failures.
- ServuxLitematicsProtocol: Wrap Litematica paste handling in try/catch, notify player on invalid data, and log warnings.
- LitematicaBitArray: Validate array size/backing storage, prevent oversized backing arrays, and add index bounds checks.
- LitematicaBlockStatePalette: Improve error messaging for invalid palette entries.
- SchematicPlacement: Validate required tags (Schematics/Origin/SubRegion positions), use safe enum-by-ordinal lookup, and guard against null vanilla boxes when streaming chunk positions.
- CommunicationManager: Validate ordinals for rotation/mirror, limit sub-region modification counts, and catch/reject malformed Syncmatica packets with logging.

Overall this commit improves robustness by validating inputs, avoiding unhandled runtime exceptions from malformed or malicious data, and adding informative logs for rejected payloads.
2026-06-09 02:24:45 +08:00
Helvetica Volubi 13c5bd7331 Update Luminol to fix endportal teleport problem 2026-06-07 15:36:24 +08:00
Helvetica Volubi bbcc103643 Update Luminol 2026-06-06 22:10:57 +08:00
Helvetica Volubi a721c27e96 fix: fix a bug in server shutdown with fakeplayer 2026-06-06 04:48:38 +08:00
Helvetica Volubi 09011f05e6 Update Luminol 2026-06-05 16:31:04 +08:00
Helvetica Volubi b8692e23d2 [ci skip]remove some import in patches 2026-06-04 14:56:05 +08:00
Helvetica Volubi 1bb1811027 Add trigger command unsafe support 2026-06-04 01:02:14 +08:00
Helvetica Volubi a4936582bb [ci skip] switch to release
because of no code update, we will start release next commit
2026-06-04 00:53:49 +08:00
Helvetica Volubi fc760a047f port: port 1.21.11 changes, fix some bugs in lagFree (https://github.com/LuminolMC/Lophine/issues/145)
Origin Commit: 311fd32acb
2026-06-03 20:19:26 +08:00
Helvetica Volubi 5567b194d7 update file's sign & add update suppression event to Leaves Features Manager 2026-06-03 13:57:27 +08:00
Helvetica Volubi f6f2781237 enabled forgotten config sync 2026-06-03 13:25:28 +08:00
Bacteriawa f469ad848b Add MiniTweaks mob fire/explosion rules
Add a MiniTweaks patch that adjusts mob explosion and fire behavior and expose new config flags.

- Add patch file features/0043-MiniTweaks-mob-fire-and-explosion-rules.patch updating Creeper, LargeFireball and SmallFireball logic to consult config flags when deciding whether explosions break blocks or create fire.
- Add four new config options to GeneralCompatConfig: noCreeperBlockBreaking, noGhastBlockBreaking, disableBlazeFire, disableGhastFire.

These changes allow disabling creeper/ghast block breaking and suppressing blaze/ghast-caused fire while preserving existing explosion event handling.
2026-06-03 13:22:25 +08:00
Bacteriawa 924ab75f1d Remove Carpet features patches and protocols
Delete Carpet integration: remove luminol and minecraft Carpet feature patch files (1todos/server/luminol-patches/features/0006-Carpet-features.patch and 1todos/server/minecraft-patches/features/0044-Carpet-features.patch) and remove related protocol implementations (1todos/server/src/lophine/protocol/CarpetLoggerProtocol.java and 1todos/server/src/lophine/protocol/tiscm/TISCMProtocol.java). These files contained the Carpet feature backport/compat patches and protocol helpers; removing them cleans up the obsolete Carpet-specific changes.
2026-06-03 13:22:25 +08:00
Bacteriawa 1b2f577e42 Add Carpet features, update Leaves patches
Register a new lophine_carpet config and add Carpet feature patches and protocol classes. Adds luminol and minecraft Carpet feature patches, plus CarpetLoggerProtocol and TISCMProtocol Java files. Updates many Leaves-related patch files (metadata and authors/dates) and applies Leaves behaviour changes across numerous block classes (onRemove handling, affectNeighborsAfterRemoval wiring, Containers.dropContentsOnDestroy, etc.) to preserve pre-1.21.1 behaviour and compatibility. These changes enable Carpet integration and ensure Leaves patches are consistently applied across the codebase.
2026-06-03 13:22:24 +08:00
515 changed files with 36348 additions and 3819 deletions
+1 -1
View File
@@ -11,4 +11,4 @@ issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
custom: https://afdian.com/a/Luminol
custom: [https://afdian.com/a/bm0721] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+5 -5
View File
@@ -32,10 +32,10 @@ jobs:
BUILD_NUMBER: ${{ github.run_number }}
steps:
- name: Checkout Git Repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Set Up JDK
uses: actions/setup-java@v5
uses: actions/setup-java@v5.7.0
with:
distribution: zulu
java-version: 25
@@ -47,7 +47,7 @@ jobs:
run: chmod +x gradlew
- name: Configure Git User Details
run: git config --global user.email "ci@luminolmc.com" && git config --global user.name "LuminolMC CI"
run: git config --global user.email "ci@lophinelabs.com" && git config --global user.name "LophinelabsCI"
- name: Apply All Patches
run: ./gradlew --refresh-dependencies applyAllPatches
@@ -124,7 +124,7 @@ jobs:
tag: ${{ env.tag }}
name: ${{ env.project_id_b }} ${{ env.mcversion }} - ${{ env.commit_id }}${{ env.release_count }}
body: |
📦Version: `${{ env.mcversion }}` | Commit ${{ env.commit_id }} [![download](https://img.shields.io/github/downloads/LuminolMC/${{ env.project_id }}/${{ env.tag }}/total?color=red&style=flat-square)](https://github.com/LuminolMC/${{ env.project_id }}/releases/download/${{ env.tag }}/${{ env.jar }})
📦Version: `${{ env.mcversion }}` | Commit ${{ env.commit_id }} [![download](https://img.shields.io/github/downloads/LophineLabs/${{ env.project_id }}/${{ env.tag }}/total?color=red&style=flat-square)](https://github.com/LophineLabs/${{ env.project_id }}/releases/download/${{ env.tag }}/${{ env.jar }})
This release is automatically compiled by GitHub Actions
### Branch Info
> ${{ github.ref_name }}
@@ -143,7 +143,7 @@ jobs:
tag: ${{ env.tag }}
name: ${{ env.project_id_b }} ${{ env.mcversion }} - ${{ env.commit_id }}${{ env.release_count }}
body: |
📦Version: `${{ env.mcversion }}` | Commit ${{ env.commit_id }} [![download](https://img.shields.io/github/downloads/LuminolMC/${{ env.project_id }}/${{ env.tag }}/total?color=red&style=flat-square)](https://github.com/LuminolMC/${{ env.project_id }}/releases/download/${{ env.tag }}/${{ env.jar }})
📦Version: `${{ env.mcversion }}` | Commit ${{ env.commit_id }} [![download](https://img.shields.io/github/downloads/LophineLabs/${{ env.project_id }}/${{ env.tag }}/total?color=red&style=flat-square)](https://github.com/LophineLabs/${{ env.project_id }}/releases/download/${{ env.tag }}/${{ env.jar }})
This release is automatically compiled by GitHub Actions
### Comments
> ${{ inputs.comments }}
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
pull-requests: write
checks: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
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
@@ -1,24 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Bacteriawa <A3167717663@hotmail.com>
Date: Sun, 22 Mar 2026 01:39:17 +0800
Subject: [PATCH] Carpet features
diff --git a/src/main/java/me/earthme/luminol/config/ConfigManager.java b/src/main/java/me/earthme/luminol/config/ConfigManager.java
index f5fb4e210c07f32e52eb8c1ba166d1571a6778c7..e990c147e1b632ef5a33e4f3c37b5cd608040ac7 100644
--- a/src/main/java/me/earthme/luminol/config/ConfigManager.java
+++ b/src/main/java/me/earthme/luminol/config/ConfigManager.java
@@ -25,6 +25,13 @@ public class ConfigManager {
public static void initConfigs() {
configfiles.put("luminol", ConfigsInstance.of("luminol", "me.earthme.luminol.config.modules"));
configfiles.put("lophine", ConfigsInstance.of("lophine", "fun.bm.lophine.config.modules")); // add lophine global config
+ configfiles.put("lophine_carpet", ConfigsInstance.of(
+ new java.io.File("lophine_config"),
+ "lophine_carpet",
+ "lophine_carpet_config.toml",
+ "lophinecarpetconfig",
+ "fun.bm.lophine.carpet.config.modules"
+ )); // add lophine carpet config
preLoad();
}
+3 -3
View File
@@ -3,11 +3,11 @@ Lophine inherits its licensing from the included upstream projects.
Lophine从上游继承了许可证。
As such, Lophine is licensed under the
[GNU General Public License version 3](licenses/GPL.md); as it inherits it from Luminol,
who in turn inherits it from the original Folia, Paper, Spigot, Bukkit and CraftBukkit projects.
[GNU General Public License version 3](licenses/GPL.md); as it inherits it from Folia,
who in turn inherits it from the original Paper, Spigot, Bukkit and CraftBukkit projects.
因此,Lophine中应当遵守[GNU通用公共许可证-第3版](licenses/GPL.md)
因为Lophine从上游Luminol继承了它,而Luminol又从其上游的Folia、Paper、Spigot、Bukkit和Craftbukkit项目中继承了它。
因为Lophine从上游Folia继承了它,而Folia又从其上游的Paper、Spigot、Bukkit和Craftbukkit项目中继承了它。
Any author who is _not_ listed below should be presumed to have released their work
under the original [GPL](licenses/GPL.md) license.
+31 -52
View File
@@ -3,19 +3,19 @@
# Lophine
*Lophine 是一个基于Luminol的分支,具有许多有用的优化和可配置的原版特性,目标是在Folia上实现更多生电的内容(请注意,完整生电请使用Fabric)*
*Lophine 是一个基于Folia的分支,具有许多有用的优化和可配置的原版特性,目标是在Folia上实现更多生电的内容(请注意,完整生电请使用Fabric)*
![Created At](https://img.shields.io/github/created-at/LuminolMC/Lophine?style=for-the-badge&color=blue)
[![License](https://img.shields.io/github/license/LuminolMC/Lophine?style=for-the-badge&color=green)](LICENSE.md)
[![Issues](https://img.shields.io/github/issues/LuminolMC/Lophine?style=for-the-badge&color=orange)](https://github.com/LuminolMC/Lophine/issues)
![Created At](https://img.shields.io/github/created-at/LophineLabs/Lophine?style=for-the-badge&color=blue)
[![License](https://img.shields.io/github/license/LophineLabs/Lophine?style=for-the-badge&color=green)](LICENSE.md)
[![Issues](https://img.shields.io/github/issues/LophineLabs/Lophine?style=for-the-badge&color=orange)](https://github.com/LophineLabs/Lophine/issues)
![Commit Activity](https://img.shields.io/github/commit-activity/w/LuminolMC/Lophine?style=for-the-badge&color=purple)
![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/LuminolMC/Lophine?style=for-the-badge&color=yellow)
![GitHub all releases](https://img.shields.io/github/downloads/LuminolMC/Lophine/total?style=for-the-badge&color=red)
![Commit Activity](https://img.shields.io/github/commit-activity/w/LophineLabs/Lophine?style=for-the-badge&color=purple)
![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/LophineLabs/Lophine?style=for-the-badge&color=yellow)
![GitHub all releases](https://img.shields.io/github/downloads/LophineLabs/Lophine/total?style=for-the-badge&color=red)
![Repo contributors](https://img.shields.io/github/contributors/LuminolMC/Lophine?style=for-the-badge&color=brightgreen)
![Repo contributors](https://img.shields.io/github/contributors/LophineLabs/Lophine?style=for-the-badge&color=brightgreen)
[English](./README_EN.md) | **中文**
[English](./README_EN.md) | **中文(简体)** | [中文(繁體)](./README_ZH_TW.md)
</div>
---
@@ -29,10 +29,15 @@
- 🔬 **生电功能增强** - 在 Folia 上实现更多生电内容(完整生电请使用 Fabric)
- 🛠️ **更多实用功能** - 持续添加有用的服务器功能
### 额外启动参数
- morninggloryclip.useMojangSource 强制服务端使用mojang源下载文件
- morninggloryclip.enable.mixin 启用服务器插件的mixin支持
## 📥 下载
### 稳定版本
所有发布版本都可以在 [Releases](https://github.com/LuminolMC/Lophine/releases) 页面找到。
所有发布版本都可以在 [Releases](https://github.com/LophineLabs/Lophine/releases) 页面找到。
### 开发版本
如果您想体验最新功能,可以通过以下步骤自行构建。
@@ -41,11 +46,11 @@
```bash
# 克隆项目
git clone https://github.com/LuminolMC/Lophine.git
git clone https://github.com/LophineLabs/Lophine.git
cd Lophine
# 应用补丁并构建 Paperclip JAR
./gradlew applyAllPatches && ./gradlew createMojmapPaperclipJar
./gradlew applyAllPatches && ./gradlew createPaperclipJar
```
构建完成后,您可以在 `lophine-server/build/libs` 目录中找到生成的 JAR 文件。
@@ -57,12 +62,16 @@ cd Lophine
```kotlin
repositories {
maven {
url = "https://repo.menthamc.org/repository/maven-public/"
url = "https://repo.bacteriawa.com/repository/maven-public/"
}
}
dependencies {
compileOnly("fun.bm.lophine:lophine-api:$VERSION")
compileOnly("fun.bm.lophine:lophine-api:26.2.build.+")
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(25))
}
```
@@ -71,16 +80,16 @@ dependencies {
```xml
<repositories>
<repository>
<id>menthamc</id>
<url>https://repo.menthamc.org/repository/maven-public/</url>
<id>repository</id>
<url>https://repo.bacteriawa.com/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>fun.bm.lophine</groupId>
<artifactId>luminol-api</artifactId>
<version>$VERSION</version>
<artifactId>lophine-api</artifactId>
<version>[26.2.build,)</version>
</dependency>
</dependencies>
```
@@ -91,15 +100,13 @@ dependencies {
### 加入我们的社区
- **QQ群**: [1015048616](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=QML5kIVsniPi1PlZvnjHQT_02EHsZ5Jc&authKey=%2FTCJsZC7JFQ9sxAroPCKuYnlV57Z5fyqp36ewXZk3Sn4iJ9p4MB1JKdc%2FFcX3HOM&noverify=0&group_code=1015048616)
- **QQ频道**: [点击加入](https://pd.qq.com/s/eq9krf9j)
- **Telegram**: [点击加入](https://t.me/LuminolMinecraft)
- **Discord**: [点击加入](https://discord.gg/Qd7m3V6eDx)
- **QQ群**: [1020403749](https://qm.qq.com/cgi-bin/qm/qr?k=y_MA9UaN7PM9e9J1LIs9Eea3LK8C0h6J&jump_from=webapi&authKey=ap5f8MlbeezXYtnmpnT5ZOFljDuOyV6OAb2PIcViQ+Ilr60Ycq63FDDTsJOZDYtj)
- **Discord**: [点击加入](https://discord.gg/UXSgPZczcy)
### 获取帮助
- 📋 [提交 Issue](https://github.com/LuminolMC/Lophine/issues)
- 💬 [GitHub Discussions](https://github.com/LuminolMC/Lophine/discussions)
- 📋 [提交 Issue](https://github.com/LophineLabs/Lophine/issues)
- 💬 [GitHub Discussions](https://github.com/LophineLabs/Lophine/discussions)
- 📖 [项目文档](./docs/)
## 🐛 问题反馈
@@ -124,34 +131,6 @@ dependencies {
![bStats](https://bstats.org/signatures/server-implementation/Lophine.svg "bStats")
## 🎉 特别感谢
### 项目赞助商
<div align="center">
<b>感谢 <a href="https://github.com/LegacyLands">LegacyLands</a> 对本项目的赞助</b>
<br>
<i>如果你想开发跨 Folia/非 Folia 平台的插件,<a href="https://github.com/LegacyLands/legacy-lands-library/">legacy-lands-library</a> 将会是个不错的选择</i>
<br><br>
<img src="public/image/legacy-lands-logo.png" alt="LegacyLands Logo" width="200">
</div>
---
## ⭐ 请给我们一个 Star
> 你的每一个免费的 ⭐Star 就是我们每一个前进的动力。
### Star 历史
<a href="https://star-history.com/#LuminolMC/Luminol&LuminolMC/LightingLuminol&LuminolMC/Lophine&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=LuminolMC/Luminol%2CLuminolMC/LightingLuminol%2CLuminolMC/Lophine&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=LuminolMC/Luminol%2CLuminolMC/LightingLuminol%2CLuminolMC/Lophine&type=Date" />
<img alt="Star历史表" src="https://api.star-history.com/svg?repos=LuminolMC/Luminol%2CLuminolMC/LightingLuminol%2CLuminolMC/Lophine&type=Date" />
</picture>
</a>
<div align="center">
<b>如果这个项目对您有帮助,请不要忘记给我们一个 ⭐Star!</b>
</div>
+31 -52
View File
@@ -3,19 +3,19 @@
# Lophine
*Lophine is a Luminol fork with many useful optimizations and configurable vanilla features, aims to provide more function for survival-usable circuit on folia (Please note that Fabric should be used for complete survival-usable)*
*Lophine is a Folia fork with many useful optimizations and configurable vanilla features, aims to provide more function for survival-usable circuit on folia (Please note that Fabric should be used for complete survival-usable)*
![Created At](https://img.shields.io/github/created-at/LuminolMC/Lophine?style=for-the-badge&color=blue)
[![License](https://img.shields.io/github/license/LuminolMC/Lophine?style=for-the-badge&color=green)](LICENSE.md)
[![Issues](https://img.shields.io/github/issues/LuminolMC/Lophine?style=for-the-badge&color=orange)](https://github.com/LuminolMC/Lophine/issues)
![Created At](https://img.shields.io/github/created-at/LophineLabs/Lophine?style=for-the-badge&color=blue)
[![License](https://img.shields.io/github/license/LophineLabs/Lophine?style=for-the-badge&color=green)](LICENSE.md)
[![Issues](https://img.shields.io/github/issues/LophineLabs/Lophine?style=for-the-badge&color=orange)](https://github.com/LophineLabs/Lophine/issues)
![Commit Activity](https://img.shields.io/github/commit-activity/w/LuminolMC/Lophine?style=for-the-badge&color=purple)
![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/LuminolMC/Lophine?style=for-the-badge&color=yellow)
![GitHub all releases](https://img.shields.io/github/downloads/LuminolMC/Lophine/total?style=for-the-badge&color=red)
![Commit Activity](https://img.shields.io/github/commit-activity/w/LophineLabs/Lophine?style=for-the-badge&color=purple)
![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/LophineLabs/Lophine?style=for-the-badge&color=yellow)
![GitHub all releases](https://img.shields.io/github/downloads/LophineLabs/Lophine/total?style=for-the-badge&color=red)
![Repo contributors](https://img.shields.io/github/contributors/LuminolMC/Lophine?style=for-the-badge&color=brightgreen)
![Repo contributors](https://img.shields.io/github/contributors/LophineLabs/Lophine?style=for-the-badge&color=brightgreen)
**English** | [中文](./README.md)
**English** | [中文(简体)](./README.md) | [中文(繁體)](./README_ZH_TW.md)
</div>
---
@@ -29,10 +29,15 @@
- 🔬 **Redstone Enhancement** - More redstone functionality on Folia (use Fabric for complete redstone features)
- 🛠️ **More Useful Functions** - Continuously adding useful server features
### Additional Launch Parameters
- morninggloryclip.useMojangSource - Use Mojang's source for Minecraft Server
- morninggloryclip.enable.mixin - Enable mixin support for Leaves Plugin
## 📥 Download
### Stable Releases
All release versions can be found on the [Releases](https://github.com/LuminolMC/Lophine/releases) page.
All release versions can be found on the [Releases](https://github.com/LophineLabs/Lophine/releases) page.
### Development Builds
If you want to experience the latest features, you can build it yourself following the steps below.
@@ -41,11 +46,11 @@ If you want to experience the latest features, you can build it yourself followi
```bash
# Clone the project
git clone https://github.com/LuminolMC/Lophine.git
git clone https://github.com/LophineLabs/Lophine.git
cd Lophine
# Apply patches and build Paperclip JAR
./gradlew applyAllPatches && ./gradlew createMojmapPaperclipJar
./gradlew applyAllPatches && ./gradlew createPaperclipJar
```
After building, you can find the generated JAR file in the `lophine-server/build/libs` directory.
@@ -57,12 +62,16 @@ After building, you can find the generated JAR file in the `lophine-server/build
```kotlin
repositories {
maven {
url = "https://repo.menthamc.org/repository/maven-public/"
url = "https://repo.bacteriawa.com/repository/maven-public/"
}
}
dependencies {
compileOnly("fun.bm.lophine:lophine-api:$VERSION")
compileOnly("fun.bm.lophine:lophine-api:26.2.build.+")
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(25))
}
```
@@ -71,16 +80,16 @@ dependencies {
```xml
<repositories>
<repository>
<id>menthamc</id>
<url>https://repo.menthamc.org/repository/maven-public/</url>
<id>repository</id>
<url>https://repo.bacteriawa.com/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>fun.bm.lophine</groupId>
<artifactId>luminol-api</artifactId>
<version>$VERSION</version>
<artifactId>lophine-api</artifactId>
<version>[26.2.build,)</version>
<scope>provided</scope>
</dependency>
</dependencies>
@@ -92,15 +101,13 @@ dependencies {
### Join Our Community
- **QQ Group**: [1015048616](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=QML5kIVsniPi1PlZvnjHQT_02EHsZ5Jc&authKey=%2FTCJsZC7JFQ9sxAroPCKuYnlV57Z5fyqp36ewXZk3Sn4iJ9p4MB1JKdc%2FFcX3HOM&noverify=0&group_code=1015048616)
- **QQ Channel**: [Join Here](https://pd.qq.com/s/eq9krf9j)
- **Telegram**: [Join Here](https://t.me/LuminolMinecraft)
- **Discord**: [Join Here](https://discord.gg/Qd7m3V6eDx)
- **QQ Group**: [1020403749](https://qm.qq.com/cgi-bin/qm/qr?k=y_MA9UaN7PM9e9J1LIs9Eea3LK8C0h6J&jump_from=webapi&authKey=ap5f8MlbeezXYtnmpnT5ZOFljDuOyV6OAb2PIcViQ+Ilr60Ycq63FDDTsJOZDYtj)
- **Discord**: [Join Here](https://discord.gg/UXSgPZczcy)
### Get Help
- 📋 [Submit Issues](https://github.com/LuminolMC/Lophine/issues)
- 💬 [GitHub Discussions](https://github.com/LuminolMC/Lophine/discussions)
- 📋 [Submit Issues](https://github.com/LophineLabs/Lophine/issues)
- 💬 [GitHub Discussions](https://github.com/LophineLabs/Lophine/discussions)
- 📖 [Project Documentation](./docs/)
## 🐛 Bug Reports
@@ -125,34 +132,6 @@ We welcome community contributions! For detailed contribution guidelines, please
![bStats](https://bstats.org/signatures/server-implementation/Lophine.svg "bStats")
## 🎉 Special Thanks
### Project Sponsors
<div align="center">
<b>Thanks to <a href="https://github.com/LegacyLands">LegacyLands</a> for sponsoring this project</b>
<br>
<i>If you want to develop cross-Folia/non-Folia platform plugins, <a href="https://github.com/LegacyLands/legacy-lands-library/">legacy-lands-library</a> would be a great choice</i>
<br><br>
<img src="public/image/legacy-lands-logo.png" alt="LegacyLands Logo" width="200">
</div>
---
## ⭐ Give Us a Star!
> Every free ⭐Star you give is the motivation for our every step forward.
### Star History
<a href="https://star-history.com/#LuminolMC/Luminol&LuminolMC/LightingLuminol&LuminolMC/Lophine&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=LuminolMC/Luminol%2CLuminolMC/LightingLuminol%2CLuminolMC/Lophine&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=LuminolMC/Luminol%2CLuminolMC/LightingLuminol%2CLuminolMC/Lophine&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=LuminolMC/Luminol%2CLuminolMC/LightingLuminol%2CLuminolMC/Lophine&type=Date" />
</picture>
</a>
<div align="center">
<b>If this project helps you, please don't forget to give us a ⭐Star!</b>
</div>
+139
View File
@@ -0,0 +1,139 @@
<div align="center">
<img src="./public/image/lophine/lophine3.png" alt="Lophine Logo" width="300">
# Lophine
*Lophine 是一個基於 Folia 的 fork,具備許多好用的最佳化與可設定的原版特性,目標是在 Folia 上實現更多生電向的內容(完整生電支援請使用 Fabric)*
![Created At](https://img.shields.io/github/created-at/LophineLabs/Lophine?style=for-the-badge&color=blue)
[![License](https://img.shields.io/github/license/LophineLabs/Lophine?style=for-the-badge&color=green)](LICENSE.md)
[![Issues](https://img.shields.io/github/issues/LophineLabs/Lophine?style=for-the-badge&color=orange)](https://github.com/LophineLabs/Lophine/issues)
![Commit Activity](https://img.shields.io/github/commit-activity/w/LophineLabs/Lophine?style=for-the-badge&color=purple)
![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/LophineLabs/Lophine?style=for-the-badge&color=yellow)
![GitHub all releases](https://img.shields.io/github/downloads/LophineLabs/Lophine/total?style=for-the-badge&color=red)
![Repo contributors](https://img.shields.io/github/contributors/LophineLabs/Lophine?style=for-the-badge&color=brightgreen)
[English](./README_EN.md) | [中文(簡體)](./README.md) | **中文(繁體)**
</div>
---
## ✨ 核心特性
- 🔧 **可設定的原版特性** - 彈性調整遊戲機制,以符合不同伺服器的需求
- 📊 **Tpsbar 支援** - 即時顯示伺服器 TPS 狀態
- 🐛 **Folia Bug 修復** - 針對 Folia 已知問題的專項修復
- 💾 **多種存檔格式支援** - 支援 linear 與 b_linearlinear 重新實作)存檔格式
- 🔬 **生電功能強化** - 在 Folia 上實現更多生電內容(完整生電請使用 Fabric)
- 🛠️ **更多實用功能** - 持續新增有用的伺服器功能
### 額外啟動參數
- morninggloryclip.useMojangSource 強制伺服器端使用 Mojang 官方來源下載檔案
- morninggloryclip.enable.mixin 為伺服器插件啟用 mixin 支援
## 📥 下載
### 穩定版本
所有正式發布版本都可以在 [Releases](https://github.com/LophineLabs/Lophine/releases) 頁面找到。
### 開發版本
如果你想搶先體驗最新功能,可以照下面的步驟自行建置。
### 建置步驟
```bash
# Clone 這個專案
git clone https://github.com/LophineLabs/Lophine.git
cd Lophine
# 套用 patch 並建置 Paperclip JAR
./gradlew applyAllPatches && ./gradlew createPaperclipJar
```
建置完成後,你可以在 `lophine-server/build/libs` 目錄中找到產生的 JAR 檔案。
## 🔌 API 使用
### Gradle 設定
```kotlin
repositories {
maven {
url = "https://repo.bacteriawa.com/repository/maven-public/"
}
}
dependencies {
compileOnly("fun.bm.lophine:lophine-api:26.2.build.+")
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(25))
}
```
### Maven 設定
```xml
<repositories>
<repository>
<id>repository</id>
<url>https://repo.bacteriawa.com/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>fun.bm.lophine</groupId>
<artifactId>lophine-api</artifactId>
<version>[26.2.build,)</version>
<scope>provided</scope>
</dependency>
</dependencies>
```
## 💬 社群與支援
> 如果你對這個專案有興趣,或有任何問題,歡迎隨時來問我們。
### 加入我們的社群
- **QQ 群**: [1020403749](https://qm.qq.com/cgi-bin/qm/qr?k=y_MA9UaN7PM9e9J1LIs9Eea3LK8C0h6J&jump_from=webapi&authKey=ap5f8MlbeezXYtnmpnT5ZOFljDuOyV6OAb2PIcViQ+Ilr60Ycq63FDDTsJOZDYtj)
- **Discord**: [點我加入](https://discord.gg/UXSgPZczcy)
### 取得協助
- 📋 [提交 Issue](https://github.com/LophineLabs/Lophine/issues)
- 💬 [GitHub Discussions](https://github.com/LophineLabs/Lophine/discussions)
- 📖 [專案文件](./docs/)
## 🐛 問題回報
當你遇到任何問題時,歡迎來問我們,我們會盡力協助解決。請記得:
- 📝 **清楚描述問題** - 詳細說明問題的具體狀況
- 📋 **提供完整記錄檔** - 附上錯誤記錄與相關設定資訊
- 🔍 **環境資訊** - 說明伺服器版本、插件清單等環境細節
- 🔄 **重現步驟** - 如果可以,請提供重現問題的具體步驟
## 🤝 貢獻程式碼
我們歡迎社群貢獻!詳細的貢獻指南請參考:
- 📖 [貢獻指南(English](./docs/CONTRIBUTING_EN.md)
- 📖 [貢獻指南(簡體中文)](./docs/CONTRIBUTING.md)
- 📖 [貢獻指南(繁體中文)](./docs/CONTRIBUTING_ZH_TW.md)
- 📖 [貢獻指南(日本語)](./docs/CONTRIBUTING_JP.md)
## 📊 專案統計
### BStats 資料
![bStats](https://bstats.org/signatures/server-implementation/Lophine.svg "bStats")
## ⭐ 給我們一個 Star 吧!
> 你給的每一個免費 ⭐Star,都是我們前進的動力。
+108 -17
View File
@@ -1,26 +1,27 @@
import groovy.json.JsonSlurper
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent
plugins {
java // TODO java launcher tasks
id("moe.luminolmc.hyacinthusweight.patcher")
id("fun.bm.comfreyweight.patcher")
}
paperweight {
filterPatches = false
upstreams.register("luminol") {
repo = github("LuminolMC", "Luminol")
ref = providers.gradleProperty("luminolRef")
upstreams.register("folia") {
repo = github("PaperMC", "Folia")
ref = providers.gradleProperty("foliaRef")
println("Upstream commit ref: " + ref.get())
patchFile {
path = "luminol-server/build.gradle.kts"
path = "folia-server/build.gradle.kts"
outputFile = file("lophine-server/build.gradle.kts")
patchFile = file("lophine-server/build.gradle.kts.patch")
}
patchFile {
path = "luminol-api/build.gradle.kts"
path = "folia-api/build.gradle.kts"
outputFile = file("lophine-api/build.gradle.kts")
patchFile = file("lophine-api/build.gradle.kts.patch")
}
@@ -29,22 +30,17 @@ paperweight {
patchesDir = file("lophine-api/paper-patches")
outputDir = file("paper-api")
}
patchRepo("foliaApi") {
patchDir("foliaApi") {
upstreamPath = "folia-api"
excludes = listOf("build.gradle.kts", "build.gradle.kts.patch", "paper-patches")
patchesDir = file("lophine-api/folia-patches")
outputDir = file("folia-api")
}
patchDir("luminolApi") {
upstreamPath = "luminol-api"
excludes = listOf("build.gradle.kts", "build.gradle.kts.patch", "paper-patches")
patchesDir = file("lophine-api/luminol-patches")
outputDir = file("luminol-api")
}
}
}
val paperMavenPublicUrl = "https://repo.papermc.io/repository/maven-public/"
val menthaMavenPublicUrl = "https://repo.menthamc.org/repository/maven-public/";
val bacteriawaMavenPublicUrl = "https://repo.bacteriawa.com/repository/maven-public/";
subprojects {
apply(plugin = "java-library")
@@ -59,7 +55,7 @@ subprojects {
repositories {
mavenCentral()
maven(paperMavenPublicUrl)
maven(menthaMavenPublicUrl)
maven(bacteriawaMavenPublicUrl)
}
dependencies {
@@ -91,8 +87,8 @@ subprojects {
extensions.configure<PublishingExtension> {
repositories {
maven("https://repo.menthamc.org/repository/maven-snapshots/") {
name = "MenthaMC"
maven("https://repo.bacteriawa.com/repository/maven-releases/") {
name = "Bacteriawa"
credentials(PasswordCredentials::class) {
username = System.getenv("PRIVATE_MAVEN_REPO_USERNAME")
password = System.getenv("PRIVATE_MAVEN_REPO_PASSWORD")
@@ -110,3 +106,98 @@ subprojects {
}
}
}
// Sort all JSON language files under the lang directory by key in ASCII order
val langDir = layout.projectDirectory.dir("lophine-server/src/main/resources/assets/lophine/lang")
tasks.register("sortLangKeys") {
group = "lophine"
description = "Sort all JSON language files by key in ASCII (ordinal) order"
notCompatibleWithConfigurationCache("Inline task action references build script class")
inputs.dir(langDir).optional()
outputs.dir(langDir)
doLast {
val dir = langDir.asFile
if (!dir.isDirectory) {
logger.warn("Lang directory not found: $dir")
return@doLast
}
val jsonFiles = dir.listFiles { f -> f.extension == "json" }?.sortedBy { it.name } ?: emptyList()
if (jsonFiles.isEmpty()) {
logger.lifecycle("No .json files found in: $dir")
return@doLast
}
val slurper = JsonSlurper()
for (file in jsonFiles) {
@Suppress("UNCHECKED_CAST")
val data = slurper.parse(file) as Map<String, Any?>
val sorted = data.toSortedMap()
val pretty = formatJson(sorted)
file.writeText(pretty + "\n", charset = Charsets.UTF_8)
logger.lifecycle("Processed: ${file.name} (${sorted.size} keys)")
}
logger.lifecycle("Done.")
}
}
// --- Pure-Kotlin JSON serializer that preserves non-ASCII characters (e.g. CJK) ---
fun formatJson(value: Any?, indent: Int = 2): String {
val sb = StringBuilder()
appendJson(sb, value, indent, 0)
return sb.toString()
}
private fun appendJson(sb: StringBuilder, value: Any?, indent: Int, level: Int) {
when (value) {
null -> sb.append("null")
is Boolean -> sb.append(value)
is Number -> sb.append(value)
is String -> sb.append('"').append(escapeJsonString(value)).append('"')
is List<*> -> {
if (value.isEmpty()) { sb.append("[]"); return }
sb.append("[\n")
value.forEachIndexed { i, v ->
sb.append(" ".repeat(indent * (level + 1)))
appendJson(sb, v, indent, level + 1)
if (i < value.size - 1) sb.append(',')
sb.append('\n')
}
sb.append(" ".repeat(indent * level)).append(']')
}
is Map<*, *> -> {
if (value.isEmpty()) { sb.append("{}"); return }
sb.append("{\n")
val entries = value.entries.toList()
entries.forEachIndexed { i, (k, v) ->
sb.append(" ".repeat(indent * (level + 1)))
sb.append('"').append(escapeJsonString(k.toString())).append('"')
sb.append(": ")
appendJson(sb, v, indent, level + 1)
if (i < entries.size - 1) sb.append(',')
sb.append('\n')
}
sb.append(" ".repeat(indent * level)).append('}')
}
else -> sb.append('"').append(escapeJsonString(value.toString())).append('"')
}
}
private fun escapeJsonString(s: String): String {
val sb = StringBuilder(s.length)
for (c in s) {
when (c) {
'"' -> sb.append("\\\"")
'\\' -> sb.append("\\\\")
'\b' -> sb.append("\\b")
'\u000C' -> sb.append("\\f")
'\n' -> sb.append("\\n")
'\r' -> sb.append("\\r")
'\t' -> sb.append("\\t")
else -> if (c.code < 0x20) {
sb.append("\\u").append(String.format("%04x", c.code))
} else {
sb.append(c) // preserve CJK and all other printable chars as-is
}
}
}
return sb.toString()
}
+14 -6
View File
@@ -1,7 +1,7 @@
为Lophine贡献代码
===============
[English](./CONTRIBUTING_EN.md) | **中文**
[English](./CONTRIBUTING_EN.md) | **中文(简体)** | [中文(繁體)](./CONTRIBUTING_ZH_TW.md) | [日本語](./CONTRIBUTING_JP.md)
我们很开心您想为我们的项目做出贡献!一般来说,我们对PR的审核是十分宽松的;
但是如果您可以遵守下列的规则,我们可以更快地完成审核。
@@ -21,7 +21,7 @@
在开始开发之前,您首先需要拥有以下软件作为开发环境:
- `git`
- `JDK 21 或更高版本`
- `JDK 25 或更高版本`
特别提醒:在操作前,您需要启用系统和Git的长路径支持,以下为部分平台的相关描述。
@@ -41,7 +41,7 @@ Lophine 使用和 Folia 一样的补丁系统,并为了针对不同部分的
1. 将你的仓库 clone 到本地;
2. 在你的 IDE 或 终端 内执行 Gradle 的 `applyAllPatches` 任务,如果是在终端内,你可以执行 `./gradlew applyAllPatches`
3. 在执行操作后,仓库根目录下应该存在以下目录对: `lophine-api``lophine-server` `luminol-api``luminol-server` `folia-api``folia-server` 以及 `paper-api``paper-server`(下文称作 `*-api``*-server` );
3. 在执行操作后,仓库根目录下应该存在以下目录对: `lophine-api``lophine-server` , `folia-api``folia-server` 以及 `paper-api``paper-server`(下文称作 `*-api``*-server` );
4. 进入 仓库根目录下的 `*-api``*-server` 文件夹进行修改。
以下为对上述各个文件夹的简单描述,详细描述可以参考[这里](https://github.com/Toffikk/paperweight-examples/blob/18241979c88068d5b061d95ad69c98ecb201c246/README.md)
@@ -49,14 +49,12 @@ Lophine 使用和 Folia 一样的补丁系统,并为了针对不同部分的
1. API部分
- `lophine-api` :对新增API的修改
- `luminol-api` :对luminol-API的修改应当在此文件夹下进行
- `folia-api` :对folia-API的修改应当在此文件夹下进行
- `paper-api` :对paper-API/spigot-API/bukkit-API的修改应该在此文件夹下进行
2. Server部分
- `lophine-server` :对Minecraft原版服务器的修改和新增文件应当在此文件夹下进行
- `luminol-server` :对luminol-server的修改应当在此文件夹下进行
- `folia-server` :对folia-Server的修改应当在此文件夹下进行
- `paper-server` :对于paper对服务器逻辑的修改应当在此文件夹下进行
@@ -64,7 +62,7 @@ Lophine 使用和 Folia 一样的补丁系统,并为了针对不同部分的
- 在应用补丁前,基点将会指向未被更改的源码
- 在基点后的每一个提交都是一个补丁
- 只有在 Luminol 最后一个提交后的提交才会被视为 lophine 补丁
- 只有在 Folia 最后一个提交后的提交才会被视为 lophine 补丁
## 增加补丁
@@ -90,3 +88,13 @@ Lophine 使用和 Folia 一样的补丁系统,并为了针对不同部分的
4. 运行 Gradle 任务 `fixupPaperApiFilePatches` 来修改已被修改的在lophine新建文件的补丁(注意不要提交);
5. 运行 Gradle 任务 `rebuildAllServerPatches` 来修改已被修改的补丁;
6. 将修改后的补丁 PR 发回储存库。
## 为配置项提供本地化的注释支持
1.`lophine-server/src/main/resources/assets/lophine/lang` 目录下创建或修改相应的语言文件,添加本地化的注释;
- 文件的名字应当符合 `https://minecraft.wiki/w/Language` 页面下的格式,如 `en_us` `zh_cn` `zh_hk` `zh_tw`,文件以 `json` 为格式类型;
2. 运行 Gradle 任务 `sortLangKeys` 来对你的语言文件内容进行重排序;
3. 使用 `git commit -m <提交信息>` 进行提交;
4. 将你修改的文件进行推送。
这样做以后,你就可以将你的修改进行 PR 提交。
+14 -6
View File
@@ -1,7 +1,7 @@
Contributing to Lophine
=======================
**English** | [中文](./CONTRIBUTING.md)
**English** | [中文(简体)](./CONTRIBUTING.md) | [中文(繁體)](./CONTRIBUTING_ZH_TW.md) | [日本語](./CONTRIBUTING_JP.md)
We're glad that you want to contribute to our project!
In general, our review of pull requests is very lenient.
@@ -23,7 +23,7 @@ See also [This issue](https://github.com/isaacs/github/issues/1681), and then yo
Before coding, you need these pieces of software / tools as Dev Environment.
- `git`
- `JDK 21 or higher`
- `JDK 25 or higher`
PS: You need to enable long path support in your System and Git before start, some of the platform's resolution here.
@@ -44,7 +44,7 @@ If you have forked the main repository, then you should follow the steps below:
1. Clone your repository to local
2. Run Gradle's `applyAllPatches` task in your IDE or terminal (You can run `./gradlew applyAllPatches` directly in terminal.)
3. After performing the operation, the following directory pairs should exist in the root directory of the warehouse: `lophine-api` and `lophine-server` , `luminol-api` and `luminol-server` , `folia-api` and `folia-server` , `paper-api` and `paper-server` (Referred to `*-api` and `*-server` as below)
3. After performing the operation, the following directory pairs should exist in the root directory of the warehouse: `lophine-api` and `lophine-server` , `folia-api` and `folia-server` , `paper-api` and `paper-server` (Referred to `*-api` and `*-server` as below)
4. Enter `*-api` and `*-server` directory to carry out modifications.
The following is the simple description of the aforementioned folders, detailed description can be referred to [here](https://github.com/Toffikk/paperweight-examples/blob/18241979c88068d5b061d95ad69c98ecb201c246/README.md):
@@ -52,14 +52,12 @@ The following is the simple description of the aforementioned folders, detailed
1. API part
- `lophine-api` : Modifications to the new API
- `luminol-api` : Modifications to Luminol API should be carried out in this folder
- `folia-api` : Modifications to Folia API should be carried out in this folder
- `paper-api` : Modifications to Paper API/Spigot API/Bukkit API should be carried out in this folder
2. Server part
- `lophine-server` : Changes and new files to the Minecraft vanilla server should be made in this folder
- `luminol-server` : Changes to luminol-server should be made in this folder
- `folia-server` : Changes to folia-server should be made in this folder
- `paper-server` : Modifications to the server logic for paper should be made in this folder
@@ -67,7 +65,7 @@ BTW, `*-api` and `*-server` and are not normal git repositories.
- Before applying patches, the base will point to unmodified source code.
- Every commit after the base is a patch.
- Only commits after the last commit of Luminol will be considered as Lophine patches.
- Only commits after the last commit of Folia will be considered as Lophine patches.
## Adding new patches
@@ -93,3 +91,13 @@ You can modify an existing patch by following the steps below:
4. Run Gradle's task `fixupPaperApiFilePatches` to regenerate lophine-created files to patches (PS: do not commit again before you run this task)
5. Run Gradle's task `rebuildAllServerPatches` to modify existing patches
6. Push and PR again
## Providing localized comment support for configuration entries
1. Create or modify the corresponding language file under the `lophine-server/src/main/resources/assets/lophine/lang` directory to add localized comments;
- The file name should follow the format listed on the `https://minecraft.wiki/w/Language` page, such as `en_us`, `zh_cn`, `zh_hk`, `zh_tw`. The file format is `json`;
2. Run the Gradle task `sortLangKeys` to re-sort the keys in your language file;
3. Commit your changes using `git commit -m <Commit Message>`;
4. Push your modified files to your repository.
After pushing, you can open a PR to submit your changes.
+100
View File
@@ -0,0 +1,100 @@
Lophine への貢献
===============
[English](./CONTRIBUTING_EN.md) | [中文(簡體)](./CONTRIBUTING.md) | [中文(繁體)](./CONTRIBUTING_ZH_TW.md) | **日本語**
Lophine プロジェクトに貢献していただき、ありがとうございます!一般的に、PR(プルリクエスト)のレビュー基準は比較的緩やかです。
以下のルールに従っていただければ、よりスムーズにレビューを進めることができます。
## 個人アカウントでフォークしてください
定期的に既存の PR をマージしています。軽微な問題であれば、こちらで直接修正したうえでマージする場合があります。
しかし、組織(Organization)アカウントでフォークしたリポジトリから作成された PR の場合、直接変更を加えることができないため、一度クローズしてから手動で再マージする必要が生じます。
そのため、**組織アカウントを使用してこのプロジェクトをフォークしないでください**。
組織アカウントの PR に直接変更を加えられない理由については、[こちらの Issue](https://github.com/isaacs/github/issues/1681) をご参照ください。
## 開発環境
コードを書き始める前に、以下の開発ツールと環境を準備する必要があります。
- `git`
- `JDK 25` またはそれ以降のバージョン
注意: 作業を開始する前に、OS および Git で長いパス(Long Path)のサポートを有効にしてください。一部のプラットフォームの設定手順は以下をご参照ください。
[`Windows`](https://learn.microsoft.com/windows/win32/fileio/maximum-file-path-limitation)
[`Git for Windows`](https://gitforwindows.org/faq.html#i-get-errors-trying-to-check-out-files-with-long-path-names)
## パッチ(Patch)システムについて
Lophine は Folia と同じパッチシステムを採用しており、修正箇所に応じて以下の 2 つのディレクトリに分かれています。
- `lophine-api` - `Folia-API` / `Paper-API` / `Spigot-API` / `Bukkit-API` をベースにした修正。
- `lophine-server` - Minecraft のバニラサーバーの既存ロジックに対する修正。
このパッチシステムは Git をベースに動作します。Git の基本操作についてはこちらをご覧ください:<https://git-scm.com/docs/gittutorial>
メインのリポジトリをフォークしたら、以下の手順に従って操作してください:
1. フォークしたリポジトリをローカルにクローンします。
2. お使いの IDE またはターミナルで Gradle の `applyAllPatches` タスクを実行します。ターミナルで行う場合は、直接 `./gradlew applyAllPatches` を実行できます。
3. 実行が完了すると、リポジトリのルートディレクトリに `lophine-api``lophine-server``folia-api``folia-server``paper-api``paper-server` の各ディレクトリが生成されます。
4. リポジトリのルートディレクトリ下にある対応する `*-api``*-server` フォルダに移動し、そこで修正を行います。
上記各フォルダの簡単な説明は以下の通りです。より詳細な説明は[こちら](https://github.com/Toffikk/paperweight-examples/blob/18241979c88068d5b061d95ad69c98ecb201c246/README.md)をご参照ください:
1. API 部分
- `lophine-api`:新規 API の追加や変更はここで行ってください。
- `folia-api`:Folia API に対する修正はここで行ってください。
- `paper-api`Paper API / Spigot API / Bukkit API に対する修正はここで行ってください。
2. Server 部分
- `lophine-server`:Minecraft のバニラサーバーに対する修正、および新規作成したファイルはすべてこのフォルダに配置してください。
- `folia-server`Folia Server に対する修正はここで行ってください。
- `paper-server`:Paper サーバーのロジックに基づく修正はここで行ってください。
なお、リポジトリのルートディレクトリにある `*-api``*-server` は通常の Git リポジトリではありません:
- パッチを適用する前は、ベース(Base)は未修正のソースコードを指しています。
- ベース以降のすべてのコミットが、1 つのパッチとして扱われます。
- Folia の最後のコミット以降に追加されたコミットのみが、Lophine のパッチとして扱われます。
## 新規パッチの追加
新しいパッチを追加するのは非常に簡単です。以下の手順に従ってください:
1. `*-api``*-server` 内でコードを変更します。
2. Git を使用して変更をステージングエリアに追加します(例:`git add .`。新しく作成したファイルを同時に追加しないでください)。
3. `git commit -m <コミットメッセージ>` を実行してコミットします。
4. Gradle タスク `fixupPaperApiFilePatches` を実行し、新しく作成されたファイルに対応するパッチファイルを生成します(注意:実行が完了する前にコミットしないでください)。
5. Gradle タスク `rebuildAllServerPatches` を実行し、コミットをパッチに変換します。
6. 生成されたパッチファイルを Git にコミットし、プッシュします。
上記の手順が完了したら、これらのパッチファイルを元に PR を作成して提出できます。
## パッチの修正
既存のパッチを修正する場合は、以下の手順に従ってください:
1. HEAD で直接修正を行います。
2. `git commit -a --fixup <hash>` を実行して、fixup コミットを作成します(Lophine で新しく作成したファイルの変更を一緒にコミットしないでください)。
- コミットメッセージも同時に変更したい場合は、`--fixup` の代わりに `--squash` を使用してください。
3. `git rebase -i --autosquash base` を実行して、fixup コミットを自動的にまとめます。エディタが開いたら、`:q` と入力して終了します。
4. Gradle タスク `fixupPaperApiFilePatches` を実行し、Lophine で新しく作成したファイルのパッチを再生成します(注意:実行が完了する前にコミットしないでください)。
5. Gradle タスク `rebuildAllServerPatches` を実行し、修正を既存のパッチに反映させます。
6. 修正したパッチを再度プッシュし、PR を更新します。
## 設定項目に対するローカライズされたコメントサポートの提供
1. `lophine-server/src/main/resources/assets/lophine/lang` ディレクトリ下に、対応する言語のファイルを作成または修正し、各言語に対応したコメントを追加します。
- ファイル名は <https://minecraft.wiki/w/Language> ページに記載されている言語コード(例:`en_us``zh_cn``zh_hk``zh_tw``ja_jp` など)に従って命名してください。ファイル形式は `json` です。
2. Gradle タスク `sortLangKeys` を実行し、修正した言語ファイルのキーの内容を並べ替えます。
3. `git commit -m <コミットメッセージ>` を実行してコミットします。
4. 修正したファイルをプッシュします。
上記の手順が完了したら、修正した内容を元に PR を作成して提出できます。
+100
View File
@@ -0,0 +1,100 @@
為 Lophine 貢獻程式碼
===============
[English](./CONTRIBUTING_EN.md) | [中文(簡體)](./CONTRIBUTING.md) | **中文(繁體)** | [日本語](./CONTRIBUTING_JP.md)
很高興你願意為這個專案貢獻!一般來說,我們對 PR 的審核標準相當寬鬆;
如果你可以照著下面的規則來,我們就能更快完成審核。
## 請使用個人帳號進行 Fork
我們會定期嘗試合併現有的 PR,如果內容有一些小問題,我們會直接幫你修改後合併。
但如果你的 PR 是用組織(organization)帳號 fork 出來的,我們就沒辦法直接修改你的 PR,只能先關閉、再手動合併一次。
所以,請不要用組織帳號來 fork 這個專案!
你可以參考[這個 Issue](https://github.com/isaacs/github/issues/1681),了解為什麼我們沒辦法直接修改組織帳號的 PR。
## 開發環境
在開始寫程式之前,你需要先準備好以下開發工具與環境:
- `git`
- `JDK 25` 或更新版本
特別提醒:動手之前,請先在你的作業系統與 Git 裡啟用長路徑(long path)支援,以下是部分平台的設定說明。
[`Windows`](https://learn.microsoft.com/windows/win32/fileio/maximum-file-path-limitation)
[`Git for Windows`](https://gitforwindows.org/faq.html#i-get-errors-trying-to-check-out-files-with-long-path-names)
## 認識 Patch(補丁)系統
Lophine 沿用了和 Folia 相同的 patch 系統,並依照修改的部分分成了兩個目錄:
- `lophine-api` - 基於 `Folia-API` / `Paper-API` / `Spigot-API` / `Bukkit-API` 所做出的修改。
- `lophine-server` - 針對 Minecraft 原版伺服器既有邏輯所做的修改。
這套 patch 系統是以 git 為基礎運作的,你可以在這裡了解 git 的基本操作:<https://git-scm.com/docs/gittutorial>
如果你已經 fork 了主要的 repository,接下來請照以下步驟操作:
1. 把你 fork 出來的 repository clone 到本機;
2. 在你的 IDE 或終端機(terminal)裡執行 Gradle 的 `applyAllPatches` 任務;如果是在終端機裡,可以直接執行 `./gradlew applyAllPatches`
3. 執行完成後,repository 的根目錄下應該會出現以下幾組目錄: `lophine-api``lophine-server``folia-api``folia-server` 以及 `paper-api``paper-server`(以下統稱為 `*-api``*-server`);
4. 進入 repository 根目錄下對應的 `*-api``*-server` 資料夾,在裡面進行修改。
以下是對上述各個資料夾的簡單說明,更詳細的說明可以參考[這裡](https://github.com/Toffikk/paperweight-examples/blob/18241979c88068d5b061d95ad69c98ecb201c246/README.md)
1. API 部分
- `lophine-api`:新增 API 的修改請放在這裡
- `folia-api`:針對 Folia API 的修改請放在這個資料夾
- `paper-api`:對於 Paper API / Spigot API / Bukkit API 的修改請放在這個資料夾
2. Server 部分
- `lophine-server`:針對 Minecraft 原版伺服器的修改、以及新增的檔案,都請放在這個資料夾
- `folia-server`:針對 folia-server 的修改請放在這個資料夾
- `paper-server`:根據 Paper 伺服器邏輯的修改請放在這個資料夾
順帶一提,repository 根目錄下的 `*-api``*-server` 並不是一般的 git repository
- 在套用 patch 之前,base(基準點)指向的是未經修改的原始程式碼;
- base 之後的每一個 commit,都會被視為一個 patch
- 只有在 Folia 最後一個 commit 之後的 commit,才會被視為是 Lophine 的 patch。
## 新增 Patch
依照以下步驟新增一個 patch 非常簡單:
1.`*-api``*-server` 裡進行修改;
2. 用 git 把你的修改加入暫存區,例如執行 `git add .`(請不要把新建立的檔案一併加入);
3. 執行 `git commit -m <提交訊息>` 進行 commit
4. 執行 Gradle 任務 `fixupPaperApiFilePatches`,用於新建立的檔案產生對應的 patch 檔(注意:執行完之前先不要 commit);
5. 執行 Gradle 任務 `rebuildAllServerPatches`,用於把你的 commit 轉換成一個 patch
6. 把產生出來的 patch 檔 push 上去。
完成以上步驟後,你就可以拿這些 patch 檔開 PR 提交了。
## 修改 Patch
你可以用以下方法來修改一個既有 patch 的內容:
1. 直接在 HEAD 上進行修改;
2. 執行 `git commit -a --fixup <hash>` ,做一個 fixup commit(請不要把 Lophine 新建立的檔案的修改一併 commit 進去);
- 如果你想順便修改 commit 訊息,可以用 `--squash` 取代 `--fixup`
3. 執行 `git rebase -i --autosquash base` ,進行自動 rebase,接著只要輸入 `:q` 關掉確認頁面即可;
4. 執行 Gradle 任務 `fixupPaperApiFilePatches`,用於重新產生 Lophine 新建立檔案的 patch(注意:執行完之前先不要 commit);
5. 執行 Gradle 任務 `rebuildAllServerPatches`,把修改套用到既有的 patch 上;
6. 把修改後的 patch 重新 push 回去,再開一次 PR。
## 為設定項目提供本地化的注解支援
1.`lophine-server/src/main/resources/assets/lophine/lang` 目錄下新增或修改對應語言的檔案,加入本地化的注解;
- 檔名請依照 <https://minecraft.wiki/w/Language> 頁面上列出的語言代碼來命名,例如 `en_us``zh_cn``zh_hk``zh_tw`,檔案格式為 `json`
2. 執行 Gradle 任務 `sortLangKeys`,把你修改的語言檔內容重新排序;
3. 執行 `git commit -m <提交訊息>` 進行 commit
4. 把你修改的檔案 push 上去。
完成以上步驟後,你就可以拿修改後的內容開 PR 提交了。
+7 -7
View File
@@ -1,15 +1,15 @@
group=fun.bm.lophine
mcVersion=26.1.2
apiVersion=26.1.2
channel=BETA
clipVersion=3.0.17
weightVersion=2.0.12
mcVersion=26.2
apiVersion=26.2
channel=STABLE
clipVersion=1.0.1
weightVersion=1.0.0
# true for release, false for skip release, pre for pre-release
release=pre
release=true
# true for push to repo, false for skip push repo, auto for detect by release value
pushRepo=auto
luminolRef=397a754b718e76edbe1a9d6d583054f885db5fde
foliaRef=57f643f10e0a9d01024773232d38ae666067d593
org.gradle.configuration-cache=true
org.gradle.caching=true
+36 -11
View File
@@ -1,27 +1,52 @@
--- a/luminol-api/build.gradle.kts
+++ b/luminol-api/build.gradle.kts
@@ -104,20 +_,24 @@
--- a/folia-api/build.gradle.kts
+++ b/folia-api/build.gradle.kts
@@ -53,6 +_,7 @@
api("org.apache.logging.log4j:log4j-api:$log4jVersion")
api("org.slf4j:slf4j-api:$slf4jVersion")
api("com.mojang:brigadier:1.3.10")
+ api("io.sentry:sentry:8.0.0-rc.2") // Pufferfish
// Deprecate bungeecord-chat in favor of adventure
api("net.md-5:bungeecord-chat:$bungeeCordChatVersion") {
@@ -105,17 +_,21 @@
java {
srcDir(generatedDir)
srcDir(file("../paper-api/src/main/java"))
srcDir(file("../folia-api/src/main/java"))
+ srcDir(file("../luminol-api/src/main/java"))
+ srcDir(file("../folia-api/src/main/java"))
}
resources {
srcDir(file("../paper-api/src/main/resources"))
srcDir(file("../folia-api/src/main/resources"))
+ srcDir(file("../luminol-api/src/main/resources"))
+ srcDir(file("../folia-api/src/main/resources"))
}
}
test {
java {
srcDir(file("../paper-api/src/test/java"))
srcDir(file("../folia-api/src/test/java"))
+ srcDir(file("../luminol-api/src/test/java"))
+ srcDir(file("../folia-api/src/test/java"))
}
resources {
srcDir(file("../paper-api/src/test/resources"))
srcDir(file("../folia-api/src/test/resources"))
+ srcDir(file("../luminol-api/src/test/resources"))
+ srcDir(file("../folia-api/src/test/resources"))
}
}
}
@@ -259,3 +_,19 @@
tasks.check {
dependsOn(scanJarForBadCalls)
}
+
+// Pufferfish Start
+tasks.withType<JavaCompile> {
+ val compilerArgs = options.compilerArgs
+ compilerArgs.add("--add-modules=jdk.incubator.vector")
+}
+// Pufferfish End
+
+// Lophine start - Hide unnecessary warnings
+tasks.withType<JavaCompile> {
+ val compilerArgs = options.compilerArgs
+ compilerArgs.add("-Xlint:-module")
+ compilerArgs.add("-Xlint:-removal")
+ compilerArgs.add("-Xlint:-dep-ann")
+}
+// Lophine end
@@ -5,14 +5,24 @@ Subject: [PATCH] Rebrand to Lophine
diff --git a/src/main/java/io/papermc/paper/ServerBuildInfo.java b/src/main/java/io/papermc/paper/ServerBuildInfo.java
index db5e2f10278c20286779362cfdfadb2f42c724e0..c18c0cb45a193883aa12b5d1abe690e95d289f82 100644
index 56a5877181e8d37518b178c95155e892c442e96d..ebe0290bf0235af2ba353ac1f74b29080daebba4 100644
--- a/src/main/java/io/papermc/paper/ServerBuildInfo.java
+++ b/src/main/java/io/papermc/paper/ServerBuildInfo.java
@@ -29,6 +29,7 @@ public interface ServerBuildInfo {
@@ -19,6 +19,17 @@ public interface ServerBuildInfo {
*/
Key BRAND_LUMINOL_ID = Key.key("luminolmc", "luminol");
// Luminol end
+ Key BRAND_LOPHINE_ID = Key.key("luminolmc", "lophine"); // Lophine
Key BRAND_PAPER_ID = Key.key("papermc", "paper");
+ // Lophine start
+ /**
+ * The brand id for folia
+ */
+ Key BRAND_FOLIA_ID = Key.key("papermc", "folia");
+ /**
+ * The brand id for Lophine.
+ */
+ Key BRAND_LOPHINE_ID = Key.key("lophinelabs", "lophine");
+ // Lophine end
+
/**
* Gets the {@code ServerBuildInfo}.
*
@@ -0,0 +1,23 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 14 Jun 2026 18:10:20 +0800
Subject: [PATCH] Luminol Config System
diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
index 067d901c69a54c4b6ae5a00488f0dacc5fde7961..209d1968f4bff3b33785fd8bfbeea5ae231e581a 100644
--- a/src/main/java/org/bukkit/Server.java
+++ b/src/main/java/org/bukkit/Server.java
@@ -2774,6 +2774,12 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
*/
void allowPausing(@NotNull org.bukkit.plugin.Plugin plugin, boolean value);
// Paper end - API to check if the server is sleeping
+
+ // Luminol start - Luminol Config API
+ me.earthme.luminol.api.config.LuminolConfigsInstance getLuminolConfigs(String name);
+ me.earthme.luminol.api.config.LuminolConfigBuilder getLuminolConfigBuilder();
+ // Luminol end - Luminol Config API
+
// Folia start - region TPS API
/**
* Gets the TPS from the region which owns the specified location, or {@code null} if no region owns
@@ -0,0 +1,28 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Bacteriawa <A3167717663@hotmail.com>
Date: Sat, 8 Aug 2026 01:25:33 +0800
Subject: [PATCH] KioCG Chunk API
diff --git a/src/main/java/org/bukkit/Chunk.java b/src/main/java/org/bukkit/Chunk.java
index d873b78f6935a23ff8f6092b80968267b182cc0f..b4323370859dd4d9e86614484cdb9d2e35c45b40 100644
--- a/src/main/java/org/bukkit/Chunk.java
+++ b/src/main/java/org/bukkit/Chunk.java
@@ -392,4 +392,6 @@ public interface Chunk extends PersistentDataHolder {
*/
UNLOADED;
}
+
+ long getChunkHotAvg(); // KioCG
}
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
index 65e83e171ab4c514662b9344e08f7065fa9efaa1..274af6397b5f7c0b6787e2669de80e54858950a2 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
@@ -4050,4 +4050,6 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
* clearing any fixed pose in the process.
*/
void unsetFixedPose();
+
+ long getNearbyChunkHot(); // KioCG
}
@@ -0,0 +1,45 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <wangxyper@163.com>
Date: Sun, 12 Jan 2025 12:22:53 +0800
Subject: [PATCH] Disable timings warn msg and commands
diff --git a/src/main/java/co/aikar/timings/Timings.java b/src/main/java/co/aikar/timings/Timings.java
index 89017af09ce32e7a66014fc3aeb50155921252a5..b862779b48176dd7e67a2f1a3e7f24bc8c84e993 100644
--- a/src/main/java/co/aikar/timings/Timings.java
+++ b/src/main/java/co/aikar/timings/Timings.java
@@ -147,7 +147,7 @@ public final class Timings {
public static void setTimingsEnabled(boolean enabled) {
enabled = false; // Folia - region threading - disable timings
if (enabled && !warnedAboutDeprecationOnEnable) {
- Bukkit.getLogger().severe(PlainTextComponentSerializer.plainText().serialize(deprecationMessage()));
+ // Bukkit.getLogger().severe(PlainTextComponentSerializer.plainText().serialize(deprecationMessage())); // Luminol - Disable timings warn msg and commands
warnedAboutDeprecationOnEnable = true;
}
}
diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java
index ad1330d87223dfcf3c9da40aa5ece8c21141f0d6..22edf161ba1ca8c31140e25e8c4fe1503c44dba8 100644
--- a/src/main/java/org/bukkit/command/SimpleCommandMap.java
+++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java
@@ -34,7 +34,7 @@ public class SimpleCommandMap implements CommandMap {
final ReloadCommand reload = new ReloadCommand("reload");
this.knownCommands.put("bukkit:reload", reload);
this.knownCommands.put("bukkit:rl", reload);
- register("bukkit", new co.aikar.timings.TimingsCommand("timings"));
+ //register("bukkit", new co.aikar.timings.TimingsCommand("timings")); // Luminol - Disable timings warn msg and commands
}
public void setFallbackCommands() {
diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
index 457e75bbecb70ac3da4c21382f97d68de5a00547..cf22ea227f6dcc13702fb5f6f94bba5b6e326b97 100644
--- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java
+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
@@ -960,7 +960,7 @@ public final class SimplePluginManager implements PluginManager {
*/
@Deprecated(forRemoval = true)
public void useTimings(boolean use) {
- co.aikar.timings.Timings.setTimingsEnabled(use); // Paper
+ // co.aikar.timings.Timings.setTimingsEnabled(use); // Paper // Luminol - Disable timings warn msg and commands
}
// Paper start
@@ -0,0 +1,23 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <wangxyper@163.com>
Date: Thu, 23 Jan 2025 12:04:04 +0800
Subject: [PATCH] Force disable reload command
diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java
index 22edf161ba1ca8c31140e25e8c4fe1503c44dba8..50384eb221ad96e766b91335ff57b082410885e9 100644
--- a/src/main/java/org/bukkit/command/SimpleCommandMap.java
+++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java
@@ -31,9 +31,9 @@ public class SimpleCommandMap implements CommandMap {
}
private void setDefaultCommands() {
- final ReloadCommand reload = new ReloadCommand("reload");
- this.knownCommands.put("bukkit:reload", reload);
- this.knownCommands.put("bukkit:rl", reload);
+ //final ReloadCommand reload = new ReloadCommand("reload"); // Luminol - Force disable reload command
+ //this.knownCommands.put("bukkit:reload", reload); // Luminol - Force disable reload command
+ //this.knownCommands.put("bukkit:rl", reload); // Luminol - Force disable reload command
//register("bukkit", new co.aikar.timings.TimingsCommand("timings")); // Luminol - Disable timings warn msg and commands
}
@@ -0,0 +1,62 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <wangxyper@163.com>
Date: Sun, 12 Jan 2025 13:27:38 +0800
Subject: [PATCH] Pufferfish: Sentry
As part of: Pufferfish
Licensed under: GPL-3.0 (https://github.com/pufferfish-gg/Pufferfish/blob/04bc249f9eee6157338b6884113b6fa3192bf59b/PATCH-LICENSE)
diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
index cf22ea227f6dcc13702fb5f6f94bba5b6e326b97..71ef6055df30506bb0d6648a2300d1cab7b0d026 100644
--- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java
+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
@@ -596,7 +596,9 @@ public final class SimplePluginManager implements PluginManager {
// Paper start
private void handlePluginException(String msg, Throwable ex, Plugin plugin) {
+ gg.pufferfish.pufferfish.sentry.SentryContext.setPluginContext(plugin); // Pufferfish
server.getLogger().log(Level.SEVERE, msg, ex);
+ gg.pufferfish.pufferfish.sentry.SentryContext.removePluginContext(); // Pufferfish
callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerPluginEnableDisableException(msg, ex, plugin)));
}
// Paper end
@@ -666,9 +668,11 @@ public final class SimplePluginManager implements PluginManager {
));
}
} catch (Throwable ex) {
+ gg.pufferfish.pufferfish.sentry.SentryContext.setEventContext(event, registration); // Pufferfish
// Paper start - error reporting
String msg = "Could not pass event " + event.getEventName() + " to " + registration.getPlugin().getDescription().getFullName();
server.getLogger().log(Level.SEVERE, msg, ex);
+ gg.pufferfish.pufferfish.sentry.SentryContext.removeEventContext(); // Pufferfish
if (!(event instanceof com.destroystokyo.paper.event.server.ServerExceptionEvent)) { // We don't want to cause an endless event loop
callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerEventException(msg, ex, registration.getPlugin(), registration.getListener(), event)));
}
diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
index 9023a39c6481710478747482f0b16c21274afd15..0755065f86aba3ef5ecdf045620878eb045f4999 100644
--- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
@@ -329,7 +329,13 @@ public final class JavaPluginLoader implements PluginLoader {
try {
jPlugin.setEnabled(true);
} catch (Throwable ex) {
+ gg.pufferfish.pufferfish.sentry.SentryContext.setPluginContext(plugin); // Pufferfish
server.getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
+ gg.pufferfish.pufferfish.sentry.SentryContext.removePluginContext(); // Pufferfish
+ // Paper start - Disable plugins that fail to load
+ this.server.getPluginManager().disablePlugin(jPlugin);
+ return;
+ // Paper end
}
// Perhaps abort here, rather than continue going, but as it stands,
@@ -354,7 +360,9 @@ public final class JavaPluginLoader implements PluginLoader {
try {
jPlugin.setEnabled(false);
} catch (Throwable ex) {
+ gg.pufferfish.pufferfish.sentry.SentryContext.setPluginContext(plugin); // Pufferfish
server.getLogger().log(Level.SEVERE, "Error occurred while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
+ gg.pufferfish.pufferfish.sentry.SentryContext.removePluginContext(); // Pufferfish
}
if (cloader instanceof PluginClassLoader) {
@@ -0,0 +1,33 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <wangxyper@163.com>
Date: Sun, 12 Jan 2025 14:00:28 +0800
Subject: [PATCH] Pufferfish: SIMD Utilities
As part of: Pufferfish
Licensed under: GPL-3.0 (https://github.com/pufferfish-gg/Pufferfish/blob/04bc249f9eee6157338b6884113b6fa3192bf59b/PATCH-LICENSE)
diff --git a/src/main/java/org/bukkit/map/MapPalette.java b/src/main/java/org/bukkit/map/MapPalette.java
index 76ae2750ff7b04a281d4b794dc1f48dd832db2b6..fc9cd106b1ed2e92aced783e9cb0bc8867828df3 100644
--- a/src/main/java/org/bukkit/map/MapPalette.java
+++ b/src/main/java/org/bukkit/map/MapPalette.java
@@ -35,7 +35,7 @@ public final class MapPalette {
}
@NotNull
- static final Color[] colors = {
+ public static final Color[] colors = { // Luminol - package-private -> public
// Start generate - MapPalette#colors
new Color(0x00000000, true),
new Color(0x00000000, true),
@@ -394,9 +394,11 @@ public final class MapPalette {
temp.getRGB(0, 0, temp.getWidth(), temp.getHeight(), pixels, 0, temp.getWidth());
byte[] result = new byte[temp.getWidth() * temp.getHeight()];
+ if ((mapColorCache != null && mapColorCache.isCached()) || !gg.pufferfish.pufferfish.simd.SIMDDetection.isEnabled) { // Luminol - Pufferfish - vectorized map color conversion
for (int i = 0; i < pixels.length; i++) {
result[i] = matchColor(new Color(pixels[i], true));
}
+ } else gg.pufferfish.pufferfish.simd.VectorMapPalette.matchColorVectorized(pixels, result); // Luminol - Pufferfish - vectorized map color conversion
return result;
}
@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <wangxyper@163.com>
Date: Mon, 27 Jan 2025 13:01:59 +0800
Subject: [PATCH] Tick regions api
diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java
index bf2f7c4959f9f2cd43bbd4d4310e7eedf10cf892..bccb33456d84384bd90887822e16cbad65986106 100644
--- a/src/main/java/org/bukkit/World.java
+++ b/src/main/java/org/bukkit/World.java
@@ -4719,4 +4719,8 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
}
}
}
+
+ // Luminol start - Tick regions api
+ me.earthme.luminol.api.ThreadedRegionizer getThreadedRegionizer();
+ // Luminol end - Tick regions api
}
@@ -0,0 +1,39 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Wed, 22 Apr 2026 12:59:08 +0800
Subject: [PATCH] Leaves: Leaves plugin
Co-authored by: MC_XiaoHei <xiaohei.xor7@outlook.com>
As part of: Leaves (https://github.com/HyacinthHaru/Leaves/blob/90080d238e2fcfc14a9293150dc351a4f0f696e6/leaves-api/paper-patches/features/0009-Leaves-Plugin.patch)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
index 325d797766de527268eb18bcf9db594ca3a0c519..e922a0eefb03db5e33f3306aaea558b6ed61b42a 100644
--- a/src/main/java/org/bukkit/Bukkit.java
+++ b/src/main/java/org/bukkit/Bukkit.java
@@ -3045,4 +3045,10 @@ public final class Bukkit {
public static void restart() {
server.restart();
}
+
+ // Leaves start - Feature API
+ public static org.leavesmc.leaves.plugin.FeatureManager getFeatureManager() {
+ return server.getFeatureManager();
+ }
+ // Leaves end - Feature API
}
diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
index 209d1968f4bff3b33785fd8bfbeea5ae231e581a..ac19fd1b6b8e48313c79d9fb26c3df50f67b64b8 100644
--- a/src/main/java/org/bukkit/Server.java
+++ b/src/main/java/org/bukkit/Server.java
@@ -2780,6 +2780,10 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
me.earthme.luminol.api.config.LuminolConfigBuilder getLuminolConfigBuilder();
// Luminol end - Luminol Config API
+ // Leaves start - Feature API
+ org.leavesmc.leaves.plugin.FeatureManager getFeatureManager();
+ // Leaves End - Feature API
+
// Folia start - region TPS API
/**
* Gets the TPS from the region which owns the specified location, or {@code null} if no region owns
@@ -0,0 +1,27 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Thu, 12 Jun 2025 08:00:15 +0800
Subject: [PATCH] Purpur: Lobotomize stuck villagers
Co-authored by: William Blake Galbreath <Blake.Galbreath@GMail.com>
As part of: Purpur (https://github.com/PurpurMC/Purpur/blob/09f547de09fc5d886f18f6d99ff389289766ec9d/purpur-server/minecraft-patches/features/0001-Ridables.patch)
Licensed under: MIT (https://github.com/PurpurMC/Purpur/blob/09f547de09fc5d886f18f6d99ff389289766ec9d/LICENSE)
diff --git a/src/main/java/org/bukkit/entity/Villager.java b/src/main/java/org/bukkit/entity/Villager.java
index a2024b5dda030a7724c13b2446224903ae837339..71f34a40b136f7f08a4820056c00692b986f63bd 100644
--- a/src/main/java/org/bukkit/entity/Villager.java
+++ b/src/main/java/org/bukkit/entity/Villager.java
@@ -405,4 +405,13 @@ public interface Villager extends AbstractVillager {
* Demand is still updated even if all events are canceled.
*/
public void restock();
+ // Purpur start
+
+ /**
+ * Check if villager is currently lobotomized
+ *
+ * @return True if lobotomized
+ */
+ boolean isLobotomized();
+ // Purpur end
}
@@ -0,0 +1,28 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Fri, 5 Jun 2026 09:12:20 +0800
Subject: [PATCH] World load/unload APIs
diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
index ac19fd1b6b8e48313c79d9fb26c3df50f67b64b8..c037307f926d64bac3b51a91aa61f63d0357770c 100644
--- a/src/main/java/org/bukkit/Server.java
+++ b/src/main/java/org/bukkit/Server.java
@@ -775,6 +775,17 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
* @throws IllegalStateException when {@link #isTickingWorlds() isTickingWorlds} is true
*/
public boolean unloadWorld(@NotNull World world, boolean save);
+ // Luminol start - Async world unloading api
+
+ /**
+ * Unloads the given world asynchronously
+ *
+ * @param world the world to unload
+ * @param save whether to save the chunks before unloading
+ * @param callback the callback to accept the result, true if successful, false otherwise
+ */
+ public void unloadWorld(World world, boolean save, java.util.concurrent.CompletableFuture<Boolean> callback);
+ // Luminol end
/**
* Gets the world that players respawn in.
@@ -8,10 +8,10 @@ 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/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
index 8e96326c55510642df01ef15e1a3edb1e81176cb..592a3c59bf5edbc65c7c0472e2382593b7b22086 100644
index e922a0eefb03db5e33f3306aaea558b6ed61b42a..7120bbea89c1c240b09562c73833f21a9980886f 100644
--- a/src/main/java/org/bukkit/Bukkit.java
+++ b/src/main/java/org/bukkit/Bukkit.java
@@ -3048,4 +3048,15 @@ public final class Bukkit {
@@ -3051,4 +3051,15 @@ public final class Bukkit {
return server.getFeatureManager();
}
// Leaves end - Feature API
@@ -28,10 +28,10 @@ index 8e96326c55510642df01ef15e1a3edb1e81176cb..592a3c59bf5edbc65c7c0472e2382593
+ // Leaves end - Bot API
}
diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
index 473b0e02c003d5317ef798c320e5c845124f4e12..978e015e91408503b4ec84ec5b3c5193255ba7db 100644
index c037307f926d64bac3b51a91aa61f63d0357770c..6c91cabdcacf7a9bf6421fbb7d52aa17516100f8 100644
--- a/src/main/java/org/bukkit/Server.java
+++ b/src/main/java/org/bukkit/Server.java
@@ -2813,4 +2813,13 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
@@ -2825,4 +2825,13 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
*/
double @Nullable [] getRegionTPS(@NotNull World world, int chunkX, int chunkZ);
// Folia end - region TPS API
@@ -8,10 +8,10 @@ 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/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
index 592a3c59bf5edbc65c7c0472e2382593b7b22086..838ad878528267b57b69d2938aee0eb6326948eb 100644
index 7120bbea89c1c240b09562c73833f21a9980886f..b114df0dde2e5bede7bb2956359fdd4ca25ac3da 100644
--- a/src/main/java/org/bukkit/Bukkit.java
+++ b/src/main/java/org/bukkit/Bukkit.java
@@ -3059,4 +3059,10 @@ public final class Bukkit {
@@ -3062,4 +3062,10 @@ public final class Bukkit {
return server.getBotManager();
}
// Leaves end - Bot API
@@ -23,10 +23,10 @@ index 592a3c59bf5edbc65c7c0472e2382593b7b22086..838ad878528267b57b69d2938aee0eb6
+ // Leaves end - Photographer API
}
diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
index 978e015e91408503b4ec84ec5b3c5193255ba7db..d054926f6082bba7d0b59e610e5d725ff5f9ffa4 100644
index 6c91cabdcacf7a9bf6421fbb7d52aa17516100f8..e6295c4bb86286aa893b55b77ddbfb2f63fdd05c 100644
--- a/src/main/java/org/bukkit/Server.java
+++ b/src/main/java/org/bukkit/Server.java
@@ -2822,4 +2822,8 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
@@ -2834,4 +2834,8 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
*/
@NotNull org.leavesmc.leaves.entity.bot.BotManager getBotManager();
// Leaves end - Bot API
@@ -0,0 +1,181 @@
/*
* This file is part of Pufferfish (https://github.com/pufferfish-gg/Pufferfish)
*
* Pufferfish is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Pufferfish is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Pufferfish. If not, see <https://www.gnu.org/licenses/>.
*/
package gg.pufferfish.pufferfish.sentry;
import com.google.gson.Gson;
import org.apache.logging.log4j.ThreadContext;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.player.PlayerEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredListener;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.TreeMap;
public class SentryContext {
private static final Gson GSON = new Gson();
public static void setPluginContext(@Nullable Plugin plugin) {
if (plugin != null) {
ThreadContext.put("pufferfishsentry_pluginname", plugin.getName());
ThreadContext.put("pufferfishsentry_pluginversion", plugin.getPluginMeta().getVersion());
}
}
public static void removePluginContext() {
ThreadContext.remove("pufferfishsentry_pluginname");
ThreadContext.remove("pufferfishsentry_pluginversion");
}
public static void setSenderContext(@Nullable CommandSender sender) {
if (sender != null) {
ThreadContext.put("pufferfishsentry_playername", sender.getName());
if (sender instanceof Player player) {
ThreadContext.put("pufferfishsentry_playerid", player.getUniqueId().toString());
}
}
}
public static void removeSenderContext() {
ThreadContext.remove("pufferfishsentry_playername");
ThreadContext.remove("pufferfishsentry_playerid");
}
public static void setEventContext(Event event, RegisteredListener registration) {
setPluginContext(registration.getPlugin());
try {
// Find the player that was involved with this event
Player player = null;
if (event instanceof PlayerEvent) {
player = ((PlayerEvent) event).getPlayer();
} else {
Class<? extends Event> eventClass = event.getClass();
Field playerField = null;
for (Field field : eventClass.getDeclaredFields()) {
if (field.getType().equals(Player.class)) {
playerField = field;
break;
}
}
if (playerField != null) {
playerField.setAccessible(true);
player = (Player) playerField.get(event);
}
}
if (player != null) {
setSenderContext(player);
}
} catch (Exception ignored) {
} // We can't really safely log exceptions.
ThreadContext.put("pufferfishsentry_eventdata", GSON.toJson(serializeFields(event)));
}
public static void removeEventContext() {
removePluginContext();
removeSenderContext();
ThreadContext.remove("pufferfishsentry_eventdata");
}
private static Map<String, String> serializeFields(Object object) {
Map<String, String> fields = new TreeMap<>();
fields.put("_class", object.getClass().getName());
for (Field declaredField : object.getClass().getDeclaredFields()) {
try {
if (Modifier.isStatic(declaredField.getModifiers())) {
continue;
}
String fieldName = declaredField.getName();
if (fieldName.equals("handlers")) {
continue;
}
declaredField.setAccessible(true);
Object value = declaredField.get(object);
if (value != null) {
fields.put(fieldName, value.toString());
} else {
fields.put(fieldName, "<null>");
}
} catch (Exception ignored) {
} // We can't really safely log exceptions.
}
return fields;
}
public static class State {
private Plugin plugin;
private Command command;
private String commandLine;
private Event event;
private RegisteredListener registeredListener;
public Plugin getPlugin() {
return plugin;
}
public void setPlugin(Plugin plugin) {
this.plugin = plugin;
}
public Command getCommand() {
return command;
}
public void setCommand(Command command) {
this.command = command;
}
public String getCommandLine() {
return commandLine;
}
public void setCommandLine(String commandLine) {
this.commandLine = commandLine;
}
public Event getEvent() {
return event;
}
public void setEvent(Event event) {
this.event = event;
}
public RegisteredListener getRegisteredListener() {
return registeredListener;
}
public void setRegisteredListener(RegisteredListener registeredListener) {
this.registeredListener = registeredListener;
}
}
}
@@ -0,0 +1,53 @@
/*
* This file is part of Pufferfish (https://github.com/pufferfish-gg/Pufferfish)
*
* Pufferfish is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Pufferfish is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Pufferfish. If not, see <https://www.gnu.org/licenses/>.
*/
package gg.pufferfish.pufferfish.simd;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.IntVector;
import jdk.incubator.vector.VectorSpecies;
import org.slf4j.Logger;
/**
* Basically, java is annoying, and we have to push this out to its own class.
*/
@Deprecated
public class SIMDChecker {
@Deprecated
public static boolean canEnable(Logger logger) {
try {
SIMDDetection.testRun = true;
VectorSpecies<Integer> ISPEC = IntVector.SPECIES_PREFERRED;
VectorSpecies<Float> FSPEC = FloatVector.SPECIES_PREFERRED;
logger.info("Max SIMD vector size on this system is {} bits (int)", ISPEC.vectorBitSize());
logger.info("Max SIMD vector size on this system is " + FSPEC.vectorBitSize() + " bits (float)");
if (ISPEC.elementSize() < 2 || FSPEC.elementSize() < 2) {
logger.warn("SIMD is not properly supported on this system!");
return false;
}
return true;
} catch (NoClassDefFoundError | Exception ignored) {
} // Basically, we don't do anything. This lets us detect if it's not functional and disable it.
return false;
}
}
@@ -0,0 +1,53 @@
/*
* This file is part of Pufferfish (https://github.com/pufferfish-gg/Pufferfish)
*
* Pufferfish is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Pufferfish is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Pufferfish. If not, see <https://www.gnu.org/licenses/>.
*/
package gg.pufferfish.pufferfish.simd;
import org.slf4j.Logger;
@Deprecated
public class SIMDDetection {
public static boolean isEnabled = false;
public static boolean testRun = false;
@Deprecated
public static boolean canEnable(Logger logger) {
try {
return SIMDChecker.canEnable(logger);
} catch (NoClassDefFoundError | Exception ignored) {
return false;
}
}
@Deprecated
public static int getJavaVersion() {
// https://stackoverflow.com/a/2591122
String version = System.getProperty("java.version");
if (version.startsWith("1.")) {
version = version.substring(2, 3);
} else {
int dot = version.indexOf(".");
if (dot != -1) {
version = version.substring(0, dot);
}
}
version = version.split("-")[0]; // Azul is stupid
return Integer.parseInt(version);
}
}
@@ -0,0 +1,101 @@
/*
* This file is part of Pufferfish (https://github.com/pufferfish-gg/Pufferfish)
*
* Pufferfish is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Pufferfish is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Pufferfish. If not, see <https://www.gnu.org/licenses/>.
*/
package gg.pufferfish.pufferfish.simd;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.IntVector;
import jdk.incubator.vector.VectorMask;
import jdk.incubator.vector.VectorSpecies;
import org.bukkit.map.MapPalette;
import java.awt.*;
@Deprecated
public class VectorMapPalette {
private static final VectorSpecies<Integer> I_SPEC = IntVector.SPECIES_PREFERRED;
private static final VectorSpecies<Float> F_SPEC = FloatVector.SPECIES_PREFERRED;
@Deprecated
public static void matchColorVectorized(int[] in, byte[] out) {
int speciesLength = I_SPEC.length();
int i;
for (i = 0; i < in.length - speciesLength; i += speciesLength) {
float[] redsArr = new float[speciesLength];
float[] bluesArr = new float[speciesLength];
float[] greensArr = new float[speciesLength];
int[] alphasArr = new int[speciesLength];
for (int j = 0; j < speciesLength; j++) {
alphasArr[j] = (in[i + j] >> 24) & 0xFF;
redsArr[j] = (in[i + j] >> 16) & 0xFF;
greensArr[j] = (in[i + j] >> 8) & 0xFF;
bluesArr[j] = (in[i + j] >> 0) & 0xFF;
}
IntVector alphas = IntVector.fromArray(I_SPEC, alphasArr, 0);
FloatVector reds = FloatVector.fromArray(F_SPEC, redsArr, 0);
FloatVector greens = FloatVector.fromArray(F_SPEC, greensArr, 0);
FloatVector blues = FloatVector.fromArray(F_SPEC, bluesArr, 0);
IntVector resultIndex = IntVector.zero(I_SPEC);
VectorMask<Integer> modificationMask = VectorMask.fromLong(I_SPEC, 0xffffffff);
modificationMask = modificationMask.and(alphas.lt(128).not());
FloatVector bestDistances = FloatVector.broadcast(F_SPEC, Float.MAX_VALUE);
for (int c = 4; c < MapPalette.colors.length; c++) {
// We're using 32-bit floats here because it's 2x faster and nobody will know the difference.
// For correctness, the original algorithm uses 64-bit floats instead. Completely unnecessary.
FloatVector compReds = FloatVector.broadcast(F_SPEC, MapPalette.colors[c].getRed());
FloatVector compGreens = FloatVector.broadcast(F_SPEC, MapPalette.colors[c].getGreen());
FloatVector compBlues = FloatVector.broadcast(F_SPEC, MapPalette.colors[c].getBlue());
FloatVector rMean = reds.add(compReds).div(2.0f);
FloatVector rDiff = reds.sub(compReds);
FloatVector gDiff = greens.sub(compGreens);
FloatVector bDiff = blues.sub(compBlues);
FloatVector weightR = rMean.div(256.0f).add(2);
FloatVector weightG = FloatVector.broadcast(F_SPEC, 4.0f);
FloatVector weightB = FloatVector.broadcast(F_SPEC, 255.0f).sub(rMean).div(256.0f).add(2.0f);
FloatVector distance = weightR.mul(rDiff).mul(rDiff).add(weightG.mul(gDiff).mul(gDiff)).add(weightB.mul(bDiff).mul(bDiff));
// Now we compare to the best distance we've found.
// This mask contains a "1" if better, and a "0" otherwise.
VectorMask<Float> bestDistanceMask = distance.lt(bestDistances);
bestDistances = bestDistances.blend(distance, bestDistanceMask); // Update the best distances
// Update the result array
// We also AND with the modification mask because we don't want to interfere if the alpha value isn't large enough.
resultIndex = resultIndex.blend(c, bestDistanceMask.cast(I_SPEC).and(modificationMask)); // Update the results
}
for (int j = 0; j < speciesLength; j++) {
int index = resultIndex.lane(j);
out[i + j] = (byte) (index < 128 ? index : -129 + (index - 127));
}
}
// For the final ones, fall back to the regular method
for (; i < in.length; i++) {
out[i] = MapPalette.matchColor(new Color(in[i], true));
}
}
}
@@ -0,0 +1,28 @@
package me.earthme.luminol.api;
/**
* A simple package of folia's tick region state.It linked to the RegionStats of the nms part so that</br>
* You could call these methods to get the status of this tick region</br>
*/
public interface RegionStats {
/**
* Get the entity count in this tick region
*
* @return the entity count
*/
int getEntityCount();
/**
* Get the player count in this tick region
*
* @return the player count
*/
int getPlayerCount();
/**
* Get the chunk count in this tick region
*
* @return the chunk count
*/
int getChunkCount();
}
@@ -0,0 +1,61 @@
package me.earthme.luminol.api;
import org.bukkit.Location;
import org.bukkit.World;
import org.jetbrains.annotations.Nullable;
/**
* A mirror of folia's ThreadedRegion</br>
* Including some handy methods to get the information of the tick region</br>
* Note: You should call these methods inside this tick region's thread context
*/
public interface ThreadedRegion {
/**
* Get the center chunk pos of this tick region</br>
* Note:</br>
* 1.Global region will return a null value(But we don't finish the global region yet()</br>
* 2.You should call these methods inside this tick region's thread context
*
* @return The center chunk pos
*/
@Nullable
Location getCenterChunkPos();
/**
* Get the dead section percent of this tick region
* Note: </br>
* 1.Dead percent is mean the percent of the unloaded chunk count of this tick region, which is also used for determine
* that the tick region should or not check for splitting</br>
* 2.You should call these methods inside this tick region's thread context
*
* @return The dead section percent
*/
double getDeadSectionPercent();
/**
* Get the tick region data of this tick region</br>
* Note:</br>
* 1.You should call this method inside this tick region's thread context</br>
* 2.You should call these methods inside this tick region's thread context
*
* @return The tick region data
*/
TickRegionData getTickRegionData();
/**
* Get the world of this tick region</br>
* Note: Global region will return a null value too
*
* @return The world of this tick region
*/
@Nullable
World getWorld();
/**
* Get the id of the tick region</br>
*
* @return The id of the tick region
*/
long getId();
}
@@ -0,0 +1,61 @@
package me.earthme.luminol.api;
import org.bukkit.Location;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* A mirror of folia's ThreadedRegionizer
*/
public interface ThreadedRegionizer {
/**
* Get all the tick regions
*
* @return Temporary copied collection of all tick regions
*/
Collection<ThreadedRegion> getAllRegions();
/**
* Get the tick region at the given chunk coordinates
*
* @param chunkX Chunk X
* @param chunkZ Chunk Z
* @return The tick region at the given chunk coordinates
*/
@Nullable
ThreadedRegion getAtSynchronized(int chunkX, int chunkZ);
/**
* Get the tick region at the given chunk coordinates
*
* @param chunkX Chunk X
* @param chunkZ Chunk Z
* @return The tick region at the given chunk coordinates
*/
@Nullable
ThreadedRegion getAtUnSynchronized(int chunkX, int chunkZ);
/**
* Get the tick region at the given location
*
* @param pos The location
* @return The tick region at the given location
*/
@Nullable
default ThreadedRegion getAtSynchronized(@NotNull Location pos) {
return this.getAtSynchronized(pos.getBlockX() >> 4, pos.getBlockZ() >> 4);
}
/**
* Get the tick region at the given location
*
* @param pos The location
* @return The tick region at the given location
*/
@Nullable
default ThreadedRegion getAtUnSynchronized(@NotNull Location pos) {
return this.getAtUnSynchronized(pos.getBlockX() >> 4, pos.getBlockZ() >> 4);
}
}
@@ -0,0 +1,29 @@
package me.earthme.luminol.api;
import org.bukkit.World;
/**
* A mirror of folia's tick region data
*/
public interface TickRegionData {
/**
* Get the world it's currently holding
*
* @return the world
*/
World getWorld();
/**
* Get the current tick count
*
* @return the current tick count
*/
long getCurrentTickCount();
/**
* Get the region stats
*
* @return the region stats
*/
RegionStats getRegionStats();
}
@@ -0,0 +1,11 @@
package me.earthme.luminol.api.config;
import org.jspecify.annotations.Nullable;
public record ConfigDataPair(
String key,
Object value,
@Nullable String comment,
@Nullable String[] suggestions
) {
}
@@ -0,0 +1,82 @@
package me.earthme.luminol.api.config;
import org.jetbrains.annotations.NotNull;
import java.io.File;
/**
* Builder interface for creating {@link LuminolConfigsInstance} instances.
* <p>
* Provides multiple overloaded factory methods to construct configuration instances
* with varying levels of customization, including custom base directories,
* file names, and command names.
*/
public interface LuminolConfigBuilder {
/**
* Creates a configuration instance using the default base directory and file name.
*
* @param loader the class loader used to load configuration resources
* @param name the configuration name identifier
* @param pack the package path where configuration classes are located
* @return a new {@link LuminolConfigsInstance}
*/
LuminolConfigsInstance of(
@NotNull ClassLoader loader,
@NotNull String name,
@NotNull String pack
);
/**
* Creates a configuration instance with a custom base directory.
*
* @param loader the class loader used to load configuration resources
* @param base the base directory where the configuration file is stored
* @param name the configuration name identifier
* @param pack the package path where configuration classes are located
* @return a new {@link LuminolConfigsInstance}
*/
LuminolConfigsInstance of(
@NotNull ClassLoader loader,
@NotNull File base,
@NotNull String name,
@NotNull String pack
);
/**
* Creates a configuration instance with a custom base directory and file name.
*
* @param loader the class loader used to load configuration resources
* @param base the base directory where the configuration file is stored
* @param name the configuration name identifier
* @param file_name the configuration file name (without extension)
* @param pack the package path where configuration classes are located
* @return a new {@link LuminolConfigsInstance}
*/
LuminolConfigsInstance of(
@NotNull ClassLoader loader,
@NotNull File base,
@NotNull String name,
@NotNull String file_name,
@NotNull String pack
);
/**
* Creates a configuration instance with full customization options.
*
* @param loader the class loader used to load configuration resources
* @param base the base directory where the configuration file is stored
* @param name the configuration name identifier
* @param file_name the configuration file name (without extension)
* @param command_name the command name associated with this configuration
* @param pack the package path where configuration classes are located
* @return a new {@link LuminolConfigsInstance}
*/
LuminolConfigsInstance of(
@NotNull ClassLoader loader,
@NotNull File base,
@NotNull String name,
@NotNull String file_name,
@NotNull String command_name,
@NotNull String pack
);
}
@@ -0,0 +1,203 @@
package me.earthme.luminol.api.config;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
/**
* Interface for managing configuration instances and operations.
* Provides methods for loading, saving, modifying, and querying configuration values.
*/
public interface LuminolConfigsInstance {
/**
* Initialize the configuration subsystem for this instance.
* Implementations should load configuration files, apply defaults and
* prepare any internal caches or state needed for subsequent operations.
* This method may perform I/O and therefore can throw an {@link IOException}
* if initialization fails.
*
* @throws IOException if an I/O error occurs while initializing
*/
void initialize() throws IOException;
/**
* Reloads all configurations asynchronously
*
* @param keepComments whether to preserve existing comments in the config file
* @return CompletableFuture that completes when reload is finished
*/
@NotNull CompletableFuture<Void> reloadAsync(boolean keepComments);
/**
* Sets a configuration value by key
*
* @param key the configuration key (dot-separated path)
* @param value the value to set
* @return true if the key exists and value was set, false otherwise
*/
boolean setConfig(String key, Object value);
/**
* Saves all pending configuration changes to disk
*/
void saveConfigs();
/**
* Resets a configuration value to its default
*
* @param key the configuration key to reset
*/
void resetConfig(String key);
/**
* Gets the default value of a configuration as string
*
* @param key the configuration key
* @return the default value as string
*/
String getDefaultConfig(String key);
/**
* Gets the current value of a configuration as string
*
* @param key the configuration key
* @return the current value as string
*/
String getConfig(String key);
/**
* Gets the original (untransformed) value of a configuration
*
* @param key the configuration key
* @param <T> the expected type of the value
* @return the original configuration value
*/
<T> T getConfigOrigin(String key);
/**
* Gets available suggestions for a configuration key
*
* @param key the configuration key
* @return array of suggestion strings, or null if no suggestions available
*/
String[] getConfigSuggestions(String key);
/**
* Completes a partial configuration path by finding matching keys
*
* @param partialPath the partial path to complete
* @return list of possible completions
*/
List<String> completeConfigPath(String partialPath);
/**
* Completes a partial configuration path with specific depth
*
* @param partialPath the partial path to complete
* @param dotIndex the maximum number of dots (depth) in the result
* @return list of possible completions
*/
List<String> completeConfigPath(String partialPath, int dotIndex);
/**
* Gets all configuration paths that start with the given prefix
*
* @param currentPath the prefix to search for
* @return list of matching configuration paths
*/
List<String> getAllConfigPaths(String currentPath);
/**
* Gets all configuration data pairs without prefix filter
*
* @return set of all configuration data
*/
Set<ConfigDataPair> getAllData();
/**
* Gets configuration data pairs filtered by prefix
*
* @param prefix the prefix to filter by
* @return set of matching configuration data
*/
Set<ConfigDataPair> getData(String prefix);
/**
* Gets all configuration data pairs with full information (comments and suggestions)
*
* @return set of all configuration data with full details
*/
Set<ConfigDataPair> getAllDataFull();
/**
* Gets configuration data pairs with full information filtered by prefix
*
* @param prefix the prefix to filter by
* @return set of matching configuration data with full details
*/
Set<ConfigDataPair> getDataFull(String prefix);
/**
* Gets configuration data pairs with optional comments and suggestions
*
* @param prefix the prefix to filter by
* @param _comment whether to include comments
* @param _withSuggestions whether to include suggestions
* @return set of matching configuration data
*/
Set<ConfigDataPair> getData(String prefix, boolean _comment, boolean _withSuggestions);
/**
* Gets configuration data pairs for specified keys
*
* @param list list of configuration keys
* @return set of configuration data for the specified keys
*/
Set<ConfigDataPair> getData(List<String> list);
/**
* Gets configuration data pairs with comments for specified keys
*
* @param list list of configuration keys
* @return set of configuration data with comments
*/
Set<ConfigDataPair> getDataWithComment(List<String> list);
/**
* Gets configuration data pairs with full information for specified keys
*
* @param list list of configuration keys
* @return set of configuration data with full details
*/
Set<ConfigDataPair> getDataFull(List<String> list);
/**
* Gets configuration data pairs for specified keys with optional features
*
* @param list list of configuration keys
* @param _comment whether to include comments
* @param _withSuggestions whether to include suggestions
* @return set of configuration data
*/
Set<ConfigDataPair> getData(List<String> list, boolean _comment, boolean _withSuggestions);
/**
* Remove a configuration entry by its key.
* If the key does not exist this should be a no-op.
*
* @param key the configuration key to remove
*/
void removeConfig(String key);
/**
* Remove multiple configuration entries by their keys.
* Implementations should attempt to remove each key provided. If some
* keys do not exist they may be ignored.
*
* @param keys an array of configuration keys to remove
*/
void removeConfig(String[] keys);
}
@@ -0,0 +1,71 @@
package me.earthme.luminol.api.entity;
import org.apache.commons.lang3.Validate;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.jetbrains.annotations.NotNull;
/**
* A simple event fired when a teleportAsync was called
*
* @see org.bukkit.entity.Entity#teleportAsync(org.bukkit.Location, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause)
* @see org.bukkit.entity.Entity#teleportAsync(org.bukkit.Location)
* (Also fired when teleportAsync called from nms)
*/
public class EntityTeleportAsyncEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList();
private final Entity entity;
private final PlayerTeleportEvent.TeleportCause teleportCause;
private final Location destination;
public EntityTeleportAsyncEvent(Entity entity, PlayerTeleportEvent.TeleportCause teleportCause, Location destination) {
Validate.notNull(entity, "entity cannot be a null value!");
Validate.notNull(teleportCause, "teleportCause cannot be a null value!");
Validate.notNull(destination, "destination cannot be a null value!");
this.entity = entity;
this.teleportCause = teleportCause;
this.destination = destination;
}
/**
* Get the entity is about to be teleported
*
* @return that entity
*/
public @NotNull Entity getEntity() {
return this.entity;
}
/**
* Get the cause of the teleport
*
* @return the cause
*/
public @NotNull PlayerTeleportEvent.TeleportCause getTeleportCause() {
return this.teleportCause;
}
/**
* Get the destination of the teleport
*
* @return the destination
*/
public @NotNull Location getDestination() {
return this.destination;
}
@Override
public @NotNull HandlerList getHandlers() {
return HANDLERS;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLERS;
}
}
@@ -0,0 +1,42 @@
package me.earthme.luminol.api.entity;
import org.apache.commons.lang3.Validate;
import org.bukkit.entity.Entity;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* A simple event created for missing teleport events api of folia
* This event is fired when the entity portal process has been done
*/
public class PostEntityPortalEvent extends Event {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final Entity teleportedEntity;
public PostEntityPortalEvent(Entity teleportedEntity) {
Validate.notNull(teleportedEntity, "teleportedEntity cannot be null!");
this.teleportedEntity = teleportedEntity;
}
/**
* Get the entity which was teleported
*
* @return the entity which was teleported
*/
public Entity getTeleportedEntity() {
return this.teleportedEntity;
}
@Override
public @NotNull HandlerList getHandlers() {
return HANDLER_LIST;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
}
@@ -0,0 +1,81 @@
package me.earthme.luminol.api.entity;
import org.apache.commons.lang3.Validate;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* A simple event created for missing teleport events api of folia
* This event will be fired when a portal teleportation is about to happen
*/
public class PreEntityPortalEvent extends Event implements Cancellable {
private static final HandlerList HANDLERS = new HandlerList();
private final Entity entity;
private final Location portalPos;
private final World destination;
private boolean cancelled = false;
public PreEntityPortalEvent(Entity entity, Location portalPos, World destination) {
Validate.notNull(entity, "entity cannot be null!");
Validate.notNull(portalPos, "portalPos cannot be null!");
Validate.notNull(destination, "destination cannot be null!");
this.entity = entity;
this.portalPos = portalPos;
this.destination = destination;
}
/**
* Get the entity that is about to teleport
*
* @return the entity
*/
public @NotNull Entity getEntity() {
return this.entity;
}
/**
* Get the location of the portal
*
* @return the portal location
*/
public @NotNull Location getPortalPos() {
return this.portalPos;
}
/**
* Get the destination world
*
* @return the destination world
*/
public @NotNull World getDestination() {
return this.destination;
}
@Override
public boolean isCancelled() {
return this.cancelled;
}
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
@Override
public @NotNull HandlerList getHandlers() {
return HANDLERS;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLERS;
}
}
@@ -0,0 +1,41 @@
package me.earthme.luminol.api.entity.player;
import org.apache.commons.lang3.Validate;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* A simple event fired when the respawn process of player is done
*/
public class PostPlayerRespawnEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList();
private final Player player;
public PostPlayerRespawnEvent(Player player) {
Validate.notNull(player, "Player cannot be a null value!");
this.player = player;
}
/**
* Get the respawned player
*
* @return the player
*/
public @NotNull Player getPlayer() {
return this.player;
}
@Override
public @NotNull HandlerList getHandlers() {
return HANDLERS;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLERS;
}
}
@@ -0,0 +1,35 @@
package me.earthme.luminol.api.portal;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* A event fired when an end platform is created.
*/
public class EndPlatformCreateEvent extends Event implements Cancellable {
private static final HandlerList HANDLERS = new HandlerList();
private boolean cancelled = false;
@Override
public boolean isCancelled() {
return this.cancelled;
}
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
@Override
public @NotNull HandlerList getHandlers() {
return HANDLERS;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLERS;
}
}
@@ -0,0 +1,55 @@
package me.earthme.luminol.api.portal;
import org.apache.commons.lang3.Validate;
import org.bukkit.Location;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* A event fired when the portal process started locating the destination position
* Notice: If you changed the destination to an another position in end teleportation.The end platform won't create under the entity and won't create
* if the position is out of current tick region
*/
public class PortalLocateEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList();
private final Location original;
private final Location destination;
public PortalLocateEvent(Location original, Location destination) {
Validate.notNull(original, "original couldn't be null!");
Validate.notNull(destination, "destination couldn't be null!");
this.original = original;
this.destination = destination;
}
/**
* Get the destination position of this teleportation
*
* @return the destination position
*/
public Location getDestination() {
return this.destination;
}
/**
* Get the original portal position of this teleportation
*
* @return the original portal position
*/
public Location getOriginal() {
return this.original;
}
@Override
public @NotNull HandlerList getHandlers() {
return HANDLERS;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLERS;
}
}
@@ -0,0 +1,46 @@
package org.leavesmc.leaves.event.bot;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.entity.bot.Bot;
public class BotActionGuiOpenEvent extends BotEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private final Player player;
private boolean cancel = false;
public BotActionGuiOpenEvent(@NotNull Bot who, @Nullable Player player) {
super(who);
this.player = player;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
@Override
public boolean isCancelled() {
return cancel;
}
@Override
public void setCancelled(boolean cancel) {
this.cancel = cancel;
}
@Nullable
public Player getOpenPlayer() {
return player;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlers;
}
}
@@ -0,0 +1,14 @@
/*
* This file is licensed under the MIT license.
* Origin : Leaves (https://github.com/LeavesMC/Leaves)
*/
package org.leavesmc.leaves.plugin;
import java.util.Set;
public interface FeatureManager {
Set<String> getAvailableFeatures();
boolean isFeatureAvailable(String feature);
}
@@ -0,0 +1,15 @@
/*
* This file is licensed under the MIT license.
* Origin : Leaves (https://github.com/LeavesMC/Leaves)
*/
package org.leavesmc.leaves.plugin;
public class Features {
public static final String MIXIN = "mixin";
public static final String FAKEPLAYER = "fakeplayer";
public static final String PHOTOGRAPHER = "photographer";
public static final String RECORDER = "recorder";
public static final String BYTEBUF = "bytebuf";
public static final String UPDATE_SUPPRESSION_EVENT = "update_suppression_event";
}
+114 -35
View File
@@ -1,72 +1,151 @@
--- a/luminol-server/build.gradle.kts
+++ b/luminol-server/build.gradle.kts
@@ -50,7 +_,23 @@
--- a/folia-server/build.gradle.kts
+++ b/folia-server/build.gradle.kts
@@ -7,15 +_,16 @@
`java-library`
`maven-publish`
idea
- id("io.papermc.paperweight.core")
+ id("fun.bm.comfreyweight.core")
id("io.papermc.fill.gradle") version "1.0.12"
}
val paperMavenPublicUrl = "https://repo.papermc.io/repository/maven-public/"
+val bacteriawaMavenPublicUrl = "https://repo.bacteriawa.com/repository/maven-public/"
dependencies {
mache("io.papermc:mache:26.2+build.1")
- paperclip("io.papermc:paperclip:3.0.4")
+ morninggloryclip("fun.bm:morninggloryclip:${providers.gradleProperty("clipVersion").get()}")
}
paperweight {
@@ -23,7 +_,8 @@
filterPatches = false
gitFilePatches = false
- val fork = forks.register("folia") {
+ val folia = forks.register("folia") {
+ rootDirectory = upstreamsDirectory().map { it.dir("folia") }
upstream.patchDir("paperServer") {
upstreamPath = "paper-server"
excludes = setOf("src/minecraft", "patches", "build.gradle.kts")
@@ -32,7 +_,23 @@
}
}
- activeFork = luminol
- activeFork = fork
+ val lophine = forks.register("lophine") {
+ forks = luminol
+ forks = folia
+ upstream.patchRepo("paperServer") {
+ upstreamRepo = luminol.patchedRepo("paperServer")
+ upstreamRepo = folia.patchedRepo("paperServer")
+ patchesDir = rootDirectory.dir("lophine-server/paper-patches")
+ outputDir = rootDirectory.dir("paper-server")
+ }
+
+ upstream.patchDir("luminolServer") {
+ upstreamPath = "luminol-server"
+ upstream.patchDir("foliaServer") {
+ upstreamPath = "folia-server"
+ excludes = setOf("src/minecraft", "paper-patches", "minecraft-patches", "build.gradle.kts", "build.gradle.kts.patch")
+ patchesDir = rootDirectory.dir("lophine-server/luminol-patches")
+ outputDir = rootDirectory.dir("luminol-server")
+ patchesDir = rootDirectory.dir("lophine-server/folia-patches")
+ outputDir = rootDirectory.dir("folia-server")
+ }
+ }
+
+ activeFork = lophine
updatingMinecraft {
@@ -44,6 +_,7 @@
libraryRepositories.addAll(
"https://repo.maven.apache.org/maven2/",
paperMavenPublicUrl,
+ bacteriawaMavenPublicUrl
)
}
@@ -125,12 +_,16 @@
@@ -103,10 +_,14 @@
main {
java { srcDir("../paper-server/src/main/java"); srcDir("../paper-server/src/generated/java") }
resources { srcDir("../paper-server/src/main/resources") }
java { srcDir("../folia-server/src/main/java") }
resources { srcDir("../folia-server/src/main/resources") }
+ java { srcDir("../luminol-server/src/main/java") }
+ resources { srcDir("../luminol-server/src/main/resources") }
+ java { srcDir("../folia-server/src/main/java") }
+ resources { srcDir("../folia-server/src/main/resources") }
}
test {
java { srcDir("../paper-server/src/test/java") }
resources { srcDir("../paper-server/src/test/resources") }
java { srcDir("../folia-server/src/test/java") }
resources { srcDir("../folia-server/src/test/resources") }
+ java { srcDir("../luminol-server/src/main/java") }
+ resources { srcDir("../luminol-server/src/main/resources") }
+ java { srcDir("../folia-server/src/test/java") }
+ resources { srcDir("../folia-server/src/test/resources") }
}
}
val log4jPlugins = sourceSets.create("log4jPlugins") {
@@ -158,7 +_,7 @@
@@ -134,7 +_,21 @@
}
dependencies {
- implementation(project(":luminol-api"))
+ implementation(project(":lophine-api"))
// Luminol start - Dependenices insert
implementation("net.objecthunter:exp4j:0.4.8")
implementation("com.electronwill.night-config:toml:3.8.3") // Night config
@@ -234,14 +_,14 @@
- implementation(project(":folia-api"))
+ implementation(project(":lophine-api")) // Lophine - api update
+ // Lophine start - Dependenices insert
+ implementation("net.objecthunter:exp4j:0.4.8")
+ implementation("com.electronwill.night-config:toml:3.8.4") // Night config
+ implementation("com.github.luben:zstd-jni:1.5.4-1")
+ implementation("net.openhft:zero-allocation-hashing:0.16")
+ implementation("io.github.classgraph:classgraph:4.8.158")
+ implementation("net.openhft:affinity:3.23.3")
+ // Leaves start - leaves plugin
+ implementation("org.spongepowered:configurate-gson:4.2.0-SNAPSHOT") {
+ exclude(group = "com.google.code.gson", module = "gson")
+ exclude(group = "com.google.guava", module = "guava")
+ }
+ // Leaves end - leaves plugin
+ // Lophine end
implementation("ca.spottedleaf:leafpile:1.0.0")
implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+
implementation("org.jline:jline-terminal-jni:3.27.1") // fall back to jni on java 21
@@ -189,21 +_,21 @@
val git = Git(rootProject.layout.projectDirectory.path)
val mcVersion = rootProject.providers.gradleProperty("mcVersion").get()
val build = System.getenv("BUILD_NUMBER") ?: null
- val buildTime = providers.environmentVariable("BUILD_STARTED_AT").map(Instant::parse).orElse(Instant.EPOCH).get()
+ val buildTime = Instant.now() // Always use current as build time
val gitHash = git.exec(providers, "rev-parse", "--short=7", "HEAD").get().trim()
val implementationVersion = "$mcVersion-${build ?: "DEV"}-$gitHash"
val date = git.exec(providers, "show", "-s", "--format=%ci", gitHash).get().trim()
val gitBranch = git.exec(providers, "rev-parse", "--abbrev-ref", "HEAD").get().trim()
attributes(
"Main-Class" to "org.bukkit.craftbukkit.Main",
- "Implementation-Title" to "Luminol", // Luminol - Rebrand
+ "Implementation-Title" to "Lophine", // Luminol - Rebrand
- "Implementation-Title" to "Folia",
+ "Implementation-Title" to "Lophine", // Lophine - Rebrand
"Implementation-Version" to implementationVersion,
"Implementation-Vendor" to date,
- "Specification-Title" to "Luminol", // Luminol - Rebrand
+ "Specification-Title" to "Lophine", // Luminol - Rebrand
- "Specification-Title" to "Folia",
+ "Specification-Title" to "Lophine", // Lophine - Rebrand
"Specification-Version" to project.version,
"Specification-Vendor" to "LuminoLMC", // Luminol - Rebrand
- "Brand-Id" to "luminolmc:luminol", // Luminol - Rebrand
- "Brand-Name" to "Luminol", // Luminol - Rebrand
+ "Brand-Id" to "luminolmc:lophine", // Luminol - Rebrand
+ "Brand-Name" to "Lophine", // Luminol - Rebrand
- "Specification-Vendor" to "Paper Team",
- "Brand-Id" to "papermc:folia",
- "Brand-Name" to "Folia",
+ "Specification-Vendor" to "LophineLabs", // Lophine - Rebrand
+ "Brand-Id" to "lophinelabs:lophine", // Lophine - Rebrand
+ "Brand-Name" to "Lophine", // Lophine - Rebrand
"Build-Number" to (build ?: ""),
"Build-Time" to buildTime.toString(),
"Git-Branch" to gitBranch,
@@ -214,6 +_,20 @@
}
}
}
+// Pufferfish Start
+tasks.withType<JavaCompile> {
+ val compilerArgs = options.compilerArgs
+ compilerArgs.add("--add-modules=jdk.incubator.vector")
+}
+// Pufferfish End
+// Lophine start - Hide unnecessary warnings
+tasks.withType<JavaCompile> {
+ val compilerArgs = options.compilerArgs
+ compilerArgs.add("-Xlint:-module")
+ compilerArgs.add("-Xlint:-removal")
+ compilerArgs.add("-Xlint:-dep-ann")
+}
+// Lophine end
// Compile tests with -parameters for better junit parameterized test names
tasks.compileTestJava {
@@ -1,31 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 8 Jun 2025 00:58:13 +0800
Subject: [PATCH] Rebrand to Lophine
diff --git a/src/main/java/me/earthme/luminol/config/ConfigManager.java b/src/main/java/me/earthme/luminol/config/ConfigManager.java
index 490316c7d5cbb77e988039cfa7a7cc0aa3849730..f5fb4e210c07f32e52eb8c1ba166d1571a6778c7 100644
--- a/src/main/java/me/earthme/luminol/config/ConfigManager.java
+++ b/src/main/java/me/earthme/luminol/config/ConfigManager.java
@@ -24,6 +24,7 @@ public class ConfigManager {
public static void initConfigs() {
configfiles.put("luminol", ConfigsInstance.of("luminol", "me.earthme.luminol.config.modules"));
+ configfiles.put("lophine", ConfigsInstance.of("lophine", "fun.bm.lophine.config.modules")); // add lophine global config
preLoad();
}
diff --git a/src/main/java/me/earthme/luminol/config/modules/misc/ServerModNameConfig.java b/src/main/java/me/earthme/luminol/config/modules/misc/ServerModNameConfig.java
index e4022494b050b68d54bac3cde87bdfc9e908859c..a4188ef831825764c0756b826651e2e7a9f99c5b 100644
--- a/src/main/java/me/earthme/luminol/config/modules/misc/ServerModNameConfig.java
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/ServerModNameConfig.java
@@ -8,7 +8,7 @@ import me.earthme.luminol.enums.EnumConfigCategory;
@ConfigClassInfo(category = EnumConfigCategory.MISC, name = "server_mod_name")
public class ServerModNameConfig implements IConfigModule {
@ConfigInfo(name = "name", comments = "Decides the server mod name shown in your F3 debug screen.")
- public static String serverModName = "Luminol";
+ public static String serverModName = "Lophine";
@ConfigInfo(name = "vanilla_spoof", comments = "Ignore any plugin's modification and server mod name set in this config block, only force sending brand name of vanilla")
public static boolean fakeVanilla = false;
@@ -1,141 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Mon, 16 Mar 2026 15:51:40 +0800
Subject: [PATCH] Diff in auto-update patch
diff --git a/src/main/java/me/earthme/luminol/config/modules/misc/AutoUpdateConfig.java b/src/main/java/me/earthme/luminol/config/modules/misc/AutoUpdateConfig.java
index ce01352b637547c1cf8308d56d4b3f9c48fcca70..9b5623ce1de3d68c949c3c00b966cedab3a4c986 100644
--- a/src/main/java/me/earthme/luminol/config/modules/misc/AutoUpdateConfig.java
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/AutoUpdateConfig.java
@@ -16,13 +16,13 @@ import java.util.Set;
category = EnumConfigCategory.MISC,
name = "auto_update",
comments = """
- Checks GitHub Releases for newer Luminol jars on a schedule.
- Downloads are staged under auto_update/luminol and written to auto_update/core.path,
+ Checks GitHub Releases for newer version's jars on a schedule.
+ Downloads are staged under auto_update/lophine and written to auto_update/core.path,
which Hyacinthusclip can consume on the next restart.
- If target_jar_path is set, Luminol will also try to replace that launcher jar directly."""
+ If target_jar_path is set, server will also try to replace that launcher jar directly."""
)
public class AutoUpdateConfig implements IConfigModule {
- @ConfigInfo(name = "enabled", comments = "Whether Luminol should check for updates automatically.")
+ @ConfigInfo(name = "enabled", comments = "Whether the server should check for updates automatically.")
public static boolean enabled = false;
@ConfigInfo(name = "check_times", comments = "List of daily check times in HH:mm, based on the server's local time zone.")
@@ -33,7 +33,7 @@ public class AutoUpdateConfig implements IConfigModule {
@ConfigInfo(name = "target_jar_path", comments = """
Optional launcher jar path to replace after a successful download.
- Leave this blank to keep the downloaded jar staged in auto_update/luminol
+ Leave this blank to keep the downloaded jar staged in auto_update/lophine
and let Hyacinthusclip switch to it through auto_update/core.path on restart.""")
public static String targetJarPath = "";
diff --git a/src/main/java/me/earthme/luminol/utils/AutoUpdateHelper.java b/src/main/java/me/earthme/luminol/utils/AutoUpdateHelper.java
index 94a25cd681c9352d6931edafd9ec67fd7ba8a777..d0d0cf5e5ff8d4b8efa4d9778c27267b41cf8cf8 100644
--- a/src/main/java/me/earthme/luminol/utils/AutoUpdateHelper.java
+++ b/src/main/java/me/earthme/luminol/utils/AutoUpdateHelper.java
@@ -35,10 +35,10 @@ public final class AutoUpdateHelper {
private final Logger LOGGER = LogUtils.getClassLogger();
private final Gson GSON = new Gson();
- private final String GITHUB_API_BASE = "https://api.github.com/repos/LuminolMC/Luminol";
+ private final String GITHUB_API_BASE = "https://api.github.com/repos/lophineMC/lophine";
private final Path AUTO_UPDATE_DIR = Path.of("auto_update");
private final Path CORE_PATH_FILE = AUTO_UPDATE_DIR.resolve("core.path");
- private final Path LUMINOL_UPDATE_DIR = AUTO_UPDATE_DIR.resolve("luminol");
+ private final Path LUMINOL_UPDATE_DIR = AUTO_UPDATE_DIR.resolve("lophine");
private final Path LATEST_PATH_FILE = LUMINOL_UPDATE_DIR.resolve("latest.path");
private final long DAILY_TASK_PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1);
@@ -51,13 +51,13 @@ public final class AutoUpdateHelper {
ensureWorkingDirectories();
scheduler = Executors.newSingleThreadScheduledExecutor(task -> {
- final Thread thread = new Thread(task, "Luminol Auto Update Scheduler");
+ final Thread thread = new Thread(task, "Lophine Auto Update Scheduler");
thread.setDaemon(true);
return thread;
});
if (AutoUpdateConfig.checkTimes.isEmpty()) {
- LOGGER.warn("Luminol auto-update is enabled, but misc.auto_update.check_times is empty.");
+ LOGGER.warn("Lophine auto-update is enabled, but misc.auto_update.check_times is empty.");
return;
}
@@ -95,14 +95,14 @@ public final class AutoUpdateHelper {
private void checkForUpdatesSafely() {
if (!UPDATE_RUNNING.compareAndSet(false, true)) {
- LOGGER.info("A Luminol auto-update check is already running, skipping this schedule.");
+ LOGGER.info("A Lophine auto-update check is already running, skipping this schedule.");
return;
}
try {
checkForUpdates();
} catch (Exception e) {
- LOGGER.warn("Luminol auto-update failed.", e);
+ LOGGER.warn("Lophine auto-update failed.", e);
} finally {
UPDATE_RUNNING.set(false);
}
@@ -123,7 +123,7 @@ public final class AutoUpdateHelper {
final ReleaseAssetInfo latestRelease = getLatestCompatibleRelease(mcVersion, gitBranch);
if (latestRelease == null) {
- LOGGER.warn("No compatible Luminol release was found for Minecraft {}{}.",
+ LOGGER.warn("No compatible Lophine release was found for Minecraft {}{}.",
mcVersion,
gitBranch == null ? "" : " on branch " + gitBranch
);
@@ -132,7 +132,7 @@ public final class AutoUpdateHelper {
final UpdateStatus updateStatus = compareReleaseWithCurrentBuild(latestRelease.releaseCommitHash(), currentGitHash);
if (updateStatus == UpdateStatus.UP_TO_DATE) {
- LOGGER.info("You are already running the latest compatible Luminol release: {}", latestRelease.tagName());
+ LOGGER.info("You are already running the latest compatible Lophine release: {}", latestRelease.tagName());
return;
}
@@ -154,7 +154,7 @@ public final class AutoUpdateHelper {
return;
}
- LOGGER.info("Found a newer Luminol release: {}", latestRelease.tagName());
+ LOGGER.info("Found a newer Lophine release: {}", latestRelease.tagName());
final Path stagedJar = downloadAndStageRelease(latestRelease);
final Path finalJarPath = tryApplyTargetJar(stagedJar);
@@ -163,7 +163,7 @@ public final class AutoUpdateHelper {
if (finalJarPath.equals(stagedJar)) {
LOGGER.info(
- "Downloaded the latest Luminol jar to {} and refreshed auto_update/core.path for Hyacinthusclip. Please restart your server.",
+ "Downloaded the latest Lophine jar to {} and refreshed auto_update/core.path for Hyacinthusclip. Please restart your server.",
finalJarPath.toAbsolutePath()
);
} else {
@@ -179,7 +179,7 @@ public final class AutoUpdateHelper {
try {
Files.createDirectories(LUMINOL_UPDATE_DIR);
} catch (IOException e) {
- throw new RuntimeException("Failed to create Luminol auto update directory", e);
+ throw new RuntimeException("Failed to create Lophine auto update directory", e);
}
}
@@ -440,7 +440,7 @@ public final class AutoUpdateHelper {
private @NotNull HttpURLConnection openConnection(String url) throws IOException, URISyntaxException {
final HttpURLConnection connection = (HttpURLConnection) new URI(url).toURL().openConnection();
connection.setRequestProperty("Accept", "application/vnd.github+json");
- connection.setRequestProperty("User-Agent", "Luminol Auto Update");
+ connection.setRequestProperty("User-Agent", "Lophine Auto Update");
connection.setConnectTimeout(10000);
connection.setReadTimeout(10000);
return connection;
@@ -1,190 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Wed, 13 Aug 2025 22:23:46 +0800
Subject: [PATCH] Leaves: Item overstack util
diff --git a/src/main/java/org/leavesmc/leaves/util/ItemOverstackUtils.java b/src/main/java/org/leavesmc/leaves/util/ItemOverstackUtils.java
index a5df7e40369cf2c309f4d37fede6615f34a467e2..84d0e1a7cd687ac4ed9f1bf824ca28940c2fab1e 100644
--- a/src/main/java/org/leavesmc/leaves/util/ItemOverstackUtils.java
+++ b/src/main/java/org/leavesmc/leaves/util/ItemOverstackUtils.java
@@ -17,10 +17,98 @@
package org.leavesmc.leaves.util;
+import fun.bm.lophine.config.modules.function.ContainerExpansionConfig;
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.item.ItemEntity;
+import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.component.CustomData;
+import net.minecraft.world.item.component.ItemContainerContents;
+import net.minecraft.world.level.block.ShulkerBoxBlock;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
// Only used part of the original code was copied
public class ItemOverstackUtils {
+
+ private static final List<ItemUtil> overstackUtils = List.of(
+ new ShulkerBox()
+ );
+
+ public static int getItemStackMaxCount(ItemStack stack) {
+ int size;
+ for (ItemUtil util : overstackUtils) {
+ if (util.isEnabled() && (size = util.getMaxServerStackCount(stack)) != -1) {
+ return size;
+ }
+ }
+ return stack.getMaxStackSize();
+ }
+
+ public static int getNetworkMaxCount(ItemStack stack) {
+ int size;
+ for (ItemUtil util : overstackUtils) {
+ if (util.isEnabled() && (size = util.getMaxClientStackCount(stack)) != -1) {
+ return size;
+ }
+ }
+ return stack.getMaxStackSize();
+ }
+
+ public static boolean tryStackItems(ItemEntity self, ItemEntity other) {
+ for (ItemUtil util : overstackUtils) {
+ if (util.isEnabled() && util.tryStackItems(self, other)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean hasOverstackingItem() {
+ for (ItemUtil util : overstackUtils) {
+ if (util.isEnabled()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static int getItemStackMaxCountReal(ItemStack stack) {
+ CompoundTag nbt = Optional.ofNullable(stack.get(DataComponents.CUSTOM_DATA)).orElse(CustomData.EMPTY).copyTag();
+ return nbt.getInt("Leaves.RealStackSize").orElse(stack.getMaxStackSize());
+ }
+
+ public static ItemStack encodeMaxStackSize(ItemStack itemStack) {
+ int realMaxStackSize = getItemStackMaxCountReal(itemStack);
+ int modifiedMaxStackSize = getNetworkMaxCount(itemStack);
+ if (itemStack.getMaxStackSize() != modifiedMaxStackSize) {
+ itemStack.set(DataComponents.MAX_STACK_SIZE, modifiedMaxStackSize);
+ CompoundTag nbt = itemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
+ nbt.putInt("Leaves.RealStackSize", realMaxStackSize);
+ itemStack.set(DataComponents.CUSTOM_DATA, CustomData.of(nbt));
+ }
+ return itemStack;
+ }
+
+ public static ItemStack decodeMaxStackSize(ItemStack itemStack) {
+ int realMaxStackSize = getItemStackMaxCountReal(itemStack);
+ if (itemStack.getMaxStackSize() != realMaxStackSize) {
+ itemStack.set(DataComponents.MAX_STACK_SIZE, realMaxStackSize);
+ CompoundTag nbt = itemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
+ nbt.remove("Leaves.RealStackSize");
+ if (nbt.isEmpty()) {
+ itemStack.remove(DataComponents.CUSTOM_DATA);
+ } else {
+ itemStack.set(DataComponents.CUSTOM_DATA, CustomData.of(nbt));
+ }
+ }
+ return itemStack;
+ }
+
public static float getItemStackSignalStrength(int maxStackSize, ItemStack itemStack) {
float result = (float) itemStack.getCount() / Math.min(maxStackSize, itemStack.getMaxStackSize());
/*if (LeavesConfig.modify.oldMC.allowGrindstoneOverstacking && CurseEnchantedBook.isCursedEnchantedBook(itemStack)) { // Luminol
@@ -28,4 +116,80 @@ public class ItemOverstackUtils {
}*/ // Luminol
return Math.clamp(result, 0f, 1f);
}
+
+ public static boolean isStackable(ItemStack itemStack) {
+ return getItemStackMaxCount(itemStack) > 1 && (!itemStack.isDamageableItem() || !itemStack.isDamaged());
+ }
+
+ private interface ItemUtil {
+ boolean isEnabled();
+
+ boolean tryStackItems(ItemEntity self, ItemEntity other);
+
+ // number -> modified count, -1 -> I don't care
+ int getMaxServerStackCount(ItemStack stack);
+
+ // number -> modified count, -1 -> I don't care
+ default int getMaxClientStackCount(ItemStack stack) {
+ return getMaxServerStackCount(stack);
+ }
+ }
+
+ private static class ShulkerBox implements ItemUtil {
+ public static boolean shulkerBoxCheck(@NotNull ItemStack stack1, @NotNull ItemStack stack2) {
+ if (ContainerExpansionConfig.nbtShulkerStackable) {
+ return Objects.equals(stack1.getComponents(), stack2.getComponents());
+ }
+ return shulkerBoxNoItem(stack1) && shulkerBoxNoItem(stack2) && Objects.equals(stack1.getComponents(), stack2.getComponents());
+ }
+
+ public static boolean shulkerBoxNoItem(@NotNull ItemStack stack) {
+ // Leaves - Paper 26.1: ItemContainerContents#stream() removed, use nonEmptyItemCopyStream()
+ return stack.getComponents().getOrDefault(DataComponents.CONTAINER, ItemContainerContents.EMPTY).nonEmptyItemCopyStream().findAny().isEmpty();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return ContainerExpansionConfig.shulkerCount > 1;
+ }
+
+ @Override
+ public boolean tryStackItems(ItemEntity self, ItemEntity other) {
+ ItemStack selfStack = self.getItem();
+ if (!(selfStack.getItem() instanceof BlockItem blockItem) || !(blockItem.getBlock() instanceof ShulkerBoxBlock)) {
+ return false;
+ }
+
+ ItemStack otherStack = other.getItem();
+ if (selfStack.getItem() == otherStack.getItem()
+ && shulkerBoxCheck(selfStack, otherStack)
+ && selfStack.getCount() != ContainerExpansionConfig.shulkerCount) {
+ int amount = Math.min(otherStack.getCount(), ContainerExpansionConfig.shulkerCount - selfStack.getCount());
+
+ selfStack.grow(amount);
+ self.setItem(selfStack);
+
+ self.pickupDelay = Math.max(other.pickupDelay, self.pickupDelay);
+ self.age = Math.min(other.getAge(), self.age);
+
+ otherStack.shrink(amount);
+ if (otherStack.isEmpty()) {
+ other.discard();
+ } else {
+ other.setItem(otherStack);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int getMaxServerStackCount(ItemStack stack) {
+ if (stack.getItem() instanceof BlockItem bi &&
+ bi.getBlock() instanceof ShulkerBoxBlock && (ContainerExpansionConfig.nbtShulkerStackable || shulkerBoxNoItem(stack))) {
+ return ContainerExpansionConfig.shulkerCount;
+ }
+ return -1;
+ }
+ }
}
@@ -1,58 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Wed, 18 Feb 2026 00:15:08 +0800
Subject: [PATCH] Leaves: Fakeplayer
diff --git a/src/main/java/me/earthme/luminol/utils/FoliaServerWaypointManager.java b/src/main/java/me/earthme/luminol/utils/FoliaServerWaypointManager.java
index 0a20c6f03625391201e5ced167707e54ed27daa1..2872b22e44cba48442c526f0a53e1fc6c5ce760f 100644
--- a/src/main/java/me/earthme/luminol/utils/FoliaServerWaypointManager.java
+++ b/src/main/java/me/earthme/luminol/utils/FoliaServerWaypointManager.java
@@ -46,6 +46,12 @@ public class FoliaServerWaypointManager implements WaypointManager<@NotNull Wayp
return;
}
+ // Leaves start - fakeplayer
+ if (waypoint instanceof org.leavesmc.leaves.bot.ServerBot bot && !bot.getConfigValue(org.leavesmc.leaves.bot.agent.Configs.ENABLE_LOCATOR_BAR)) {
+ return;
+ }
+ // Leaves end - fakeplayer
+
this.waypoints.add(waypoint);
for (ServerPlayer toCreateFor : this.trackingPlayers) {
@@ -101,7 +107,13 @@ public class FoliaServerWaypointManager implements WaypointManager<@NotNull Wayp
scheduleIfOffTarget(player, () -> {
this.trackingPlayers.add(player);
- for (WaypointTransmitter transmitter : this.waypoints) {
+ // Leaves start - fakeplayer
+ if (player instanceof org.leavesmc.leaves.bot.ServerBot) {
+ return;
+ }
+ // Leaves end - fakeplayer
+
+ for (WaypointTransmitter transmitter : this.waypoints) {
this.createConnection(player, transmitter);
}
diff --git a/src/main/java/org/leavesmc/leaves/plugin/ServerFeatureManager.java b/src/main/java/org/leavesmc/leaves/plugin/ServerFeatureManager.java
index 8f0548908a9e43ce29c4476f2707f6898e540aec..8b7c66785a06eb4782de0b0a9b542221d6258220 100644
--- a/src/main/java/org/leavesmc/leaves/plugin/ServerFeatureManager.java
+++ b/src/main/java/org/leavesmc/leaves/plugin/ServerFeatureManager.java
@@ -11,14 +11,7 @@ public class ServerFeatureManager implements FeatureManager {
private final Set<String> availableFeatures = new HashSet<>();
private ServerFeatureManager() {
- if (false) { // Luminol not provide this
- availableFeatures.addAll(Set.of(
- FAKEPLAYER,
- PHOTOGRAPHER,
- BYTEBUF,
- UPDATE_SUPPRESSION_EVENT
- ));
- }
+ availableFeatures.add(FAKEPLAYER); // Lophine - fakeplayer support
if (Boolean.getBoolean("leavesclip.enable.mixin")) {
availableFeatures.add(MIXIN);
}
@@ -1,18 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 17 May 2026 05:33:21 +0800
Subject: [PATCH] Leaves: Replay Mod API
diff --git a/src/main/java/org/leavesmc/leaves/plugin/ServerFeatureManager.java b/src/main/java/org/leavesmc/leaves/plugin/ServerFeatureManager.java
index 8b7c66785a06eb4782de0b0a9b542221d6258220..1f3fe7f786bb8f7c59e3a0235dcc572f3171ee6b 100644
--- a/src/main/java/org/leavesmc/leaves/plugin/ServerFeatureManager.java
+++ b/src/main/java/org/leavesmc/leaves/plugin/ServerFeatureManager.java
@@ -12,6 +12,7 @@ public class ServerFeatureManager implements FeatureManager {
private ServerFeatureManager() {
availableFeatures.add(FAKEPLAYER); // Lophine - fakeplayer support
+ availableFeatures.add(PHOTOGRAPHER); // Lophine - Replay Mod API support
if (Boolean.getBoolean("leavesclip.enable.mixin")) {
availableFeatures.add(MIXIN);
}
@@ -0,0 +1,44 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 14 Jun 2026 18:10:20 +0800
Subject: [PATCH] Luminol Config System
diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
index 693ce1c23c7be6406eda22da57aa4a1b86a08f5f..4385488983a7544eb6d9193d913dc55294f4620d 100644
--- a/net/minecraft/server/Main.java
+++ b/net/minecraft/server/Main.java
@@ -110,6 +110,7 @@ public class Main {
JvmProfiler.INSTANCE.start(Environment.SERVER);
}
+ me.earthme.luminol.config.ConfigManager.initConfigs(); // Luminol - Luminol config
io.papermc.paper.plugin.PluginInitializerManager.load(options); // Paper
Bootstrap.bootStrap();
Bootstrap.validate();
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index be15a671e0b39b455cd5983f4b90f8f3022a2ebc..56d26866cb8f413684e62558e5609b273efb8552 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1168,6 +1168,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
ca.spottedleaf.moonrise.common.util.MoonriseCommon.haltExecutors();
}
// Paper end - rewrite chunk system
+ // Luminol start - Config Systems
+ me.earthme.luminol.config.ConfigManager.saveConfigs(false);
+ // Luminol end
// Paper start - Improved watchdog support - move final shutdown items here
Util.shutdownExecutors();
this.onServerExit();
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
index cd5551c0221ad2a7b5c7d452d8d91ffc9711c3ee..1ad4704523a3d90414ac514488207b1b5def9afd 100644
--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -238,6 +238,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess());
this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
// Paper end - initialize global and world-defaults configuration
+ me.earthme.luminol.config.ConfigManager.loadConfigFiles(); // Luminol - load config file
this.server.spark.enableEarlyIfRequested(); // Paper - spark
// Paper start - fix converting txt to json file; convert old users earlier after PlayerList creation but before file load/save
if (this.convertOldUsers()) {
@@ -0,0 +1,45 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 28 Jun 2026 17:56:30 +0800
Subject: [PATCH] Correct player respawn place
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 16f49ae6736c475207f31392307e280ec06edbc3..a2e40bc7a15ba0663314a288f4a9b3ec3a39dc4d 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -511,8 +511,10 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
double amountX = selectMaxX - selectMinX;
double amountZ = selectMaxZ - selectMinZ;
- int selectX = amountX < 1.0 ? Mth.floor(worldBorder.getCenterX()) : (int)Mth.floor((amountX + 1.0) * random.nextDouble() + selectMinX);
- int selectZ = amountZ < 1.0 ? Mth.floor(worldBorder.getCenterZ()) : (int)Mth.floor((amountZ + 1.0) * random.nextDouble() + selectMinZ);
+ // Luminol start - Correct player respawn place
+ int selectX = amountX < 0.0 ? Mth.floor(worldBorder.getCenterX()) : (int)Mth.floor(amountX * random.nextDouble() + selectMinX);
+ int selectZ = amountZ < 0.0 ? Mth.floor(worldBorder.getCenterZ()) : (int)Mth.floor(amountZ * random.nextDouble() + selectMinZ);
+ // Luminol end - Correct player respawn place
return new BlockPos(selectX, 0, selectZ);
}
@@ -523,10 +525,20 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
}
private static BlockPos findSpawnAround(ServerLevel world, BlockPos selected) {
+ // Luminol start - Correct player respawn place
+ BlockPos inChunk = PlayerSpawnFinder.getLevelRespawnPos(world, selected.getX(), selected.getZ());
+ if (inChunk != null) {
+ AABB checkVolume = PlayerSpawnFinder.PLAYER_DIMENSIONS.makeBoundingBox((double)inChunk.getX() + 0.5, (double)inChunk.getY(), (double)inChunk.getZ() + 0.5);
+
+ if (world.noCollision(null, checkVolume, true)) {
+ return inChunk;
+ }
+ }
+ // Luminol end - Correct player respawn place
// try hard to find, so that we don't attempt another chunk load
for (int dz = -SPAWN_RADIUS_SELECTION_SEARCH; dz <= SPAWN_RADIUS_SELECTION_SEARCH; ++dz) {
for (int dx = -SPAWN_RADIUS_SELECTION_SEARCH; dx <= SPAWN_RADIUS_SELECTION_SEARCH; ++dx) {
- BlockPos inChunk = PlayerSpawnFinder.getLevelRespawnPos(world, selected.getX() + dx, selected.getZ() + dz);
+ inChunk = PlayerSpawnFinder.getLevelRespawnPos(world, selected.getX() + dx, selected.getZ() + dz); // Luminol - Correct player respawn place
if (inChunk == null) {
continue;
}
@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Fri, 17 Apr 2026 23:01:59 +0800
Subject: [PATCH] Correct volatile reference get in RegionizedTaskQueue
diff --git a/io/papermc/paper/threadedregions/RegionizedTaskQueue.java b/io/papermc/paper/threadedregions/RegionizedTaskQueue.java
index d1a045d47dcd12f246d95ecdfc202da357897cbf..b264dbd13a392cde3f24ff75c0034985fda68ccc 100644
--- a/io/papermc/paper/threadedregions/RegionizedTaskQueue.java
+++ b/io/papermc/paper/threadedregions/RegionizedTaskQueue.java
@@ -533,7 +533,7 @@ public final class RegionizedTaskQueue {
}
private ReferenceCountData getReferenceCounterVolatile() {
- return (ReferenceCountData)REFERENCE_COUNTER_HANDLE.get(this);
+ return (ReferenceCountData)REFERENCE_COUNTER_HANDLE.getVolatile(this); // Luminol - Correct volatile reference get in RegionizedTaskQueue
}
private ReferenceCountData compareAndExchangeReferenceCounter(final ReferenceCountData expect, final ReferenceCountData update) {
@@ -0,0 +1,32 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Tue, 30 Jun 2026 22:34:00 +0800
Subject: [PATCH] Correct thread unsafe random sources
diff --git a/net/minecraft/world/entity/npc/wanderingtrader/WanderingTraderSpawner.java b/net/minecraft/world/entity/npc/wanderingtrader/WanderingTraderSpawner.java
index 01b3cda3da79abd30860eb33491ac00f9e958767..dd35e72757b1be1a73e67b272c138bb4e3b6336a 100644
--- a/net/minecraft/world/entity/npc/wanderingtrader/WanderingTraderSpawner.java
+++ b/net/minecraft/world/entity/npc/wanderingtrader/WanderingTraderSpawner.java
@@ -30,7 +30,7 @@ public class WanderingTraderSpawner implements CustomSpawner {
private static final int SPAWN_CHANCE_INCREASE = 25;
private static final int SPAWN_ONE_IN_X_CHANCE = 10;
private static final int NUMBER_OF_SPAWN_ATTEMPTS = 10;
- private final RandomSource random = RandomSource.create();
+ private final RandomSource random = io.papermc.paper.threadedregions.util.ThreadLocalRandomSource.INSTANCE; // Luminol - Correct thread unsafe random sources
private final SavedDataStorage savedDataStorage;
// Folia - moved to global data
diff --git a/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java b/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java
index 22b9f70f76ff8f592359e958fa439e9806a29fcc..818598ed4e498bf3f177faac03336097b41915ee 100644
--- a/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java
@@ -28,7 +28,7 @@ public class EnchantingTableBlockEntity extends BlockEntity implements Nameable
public float rot;
public float oRot;
public float tRot;
- private static final RandomSource RANDOM = RandomSource.create();
+ private static final RandomSource RANDOM = io.papermc.paper.threadedregions.util.ThreadLocalRandomSource.INSTANCE; // Luminol - Correct thread unsafe random sources
private @Nullable Component name;
public EnchantingTableBlockEntity(final BlockPos worldPosition, final BlockState blockState) {
@@ -0,0 +1,58 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Thu, 2 Jul 2026 22:31:15 +0800
Subject: [PATCH] Correct portal logic of some projectile entities
diff --git a/net/minecraft/world/entity/projectile/ShulkerBullet.java b/net/minecraft/world/entity/projectile/ShulkerBullet.java
index d5506bfc84912ec50cd5391225b9b5291435d3aa..6339a770526324bdc1bcc0c0e3bacc0a10f359ab 100644
--- a/net/minecraft/world/entity/projectile/ShulkerBullet.java
+++ b/net/minecraft/world/entity/projectile/ShulkerBullet.java
@@ -229,7 +229,7 @@ public class ShulkerBullet extends Projectile {
this.setPos(this.position().add(movement));
this.applyEffectsFromBlocks();
if (this.portalProcess != null && this.portalProcess.isInsidePortalThisTick()) {
- this.handlePortal();
+ if (this.handlePortal()) return; // Luminol - Correct portal logic of some projectile entities (Should not be ticking anymore after portal logics)
}
if (hitResult != null && this.isAlive() && hitResult.getType() != HitResult.Type.MISS) {
diff --git a/net/minecraft/world/entity/projectile/arrow/AbstractArrow.java b/net/minecraft/world/entity/projectile/arrow/AbstractArrow.java
index 88c2f0f927bd8376522eb9aed184abd6f09230f6..d66a4826400eca835645e1115c4fefc61715f336 100644
--- a/net/minecraft/world/entity/projectile/arrow/AbstractArrow.java
+++ b/net/minecraft/world/entity/projectile/arrow/AbstractArrow.java
@@ -276,7 +276,7 @@ public abstract class AbstractArrow extends Projectile {
.clipIncludingBorder(
new ClipContext(originalPosition, originalPosition.add(movement), ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)
);
- this.stepMoveAndHit(blockHitResult);
+ if (this.stepMoveAndHit(blockHitResult)) return; // Luminol - Correct portal logic of some projectile entities (Should not be ticking anymore after portal logics)
} else {
this.setPos(originalPosition.add(movement));
this.applyEffectsFromBlocks();
@@ -299,7 +299,7 @@ public abstract class AbstractArrow extends Projectile {
return 0.99F;
}
- private void stepMoveAndHit(final BlockHitResult blockHitResult) {
+ private boolean stepMoveAndHit(final BlockHitResult blockHitResult) { // Luminol - Correct portal logic of some projectile entities
while (this.isAlive()) {
Vec3 initialPosition = this.position();
ArrayList<EntityHitResult> entitiesHit = new ArrayList<>(this.findHitEntities(initialPosition, blockHitResult.getLocation()));
@@ -309,7 +309,7 @@ public abstract class AbstractArrow extends Projectile {
this.setPos(nextLocation);
this.applyEffectsFromBlocks(initialPosition, nextLocation);
if (this.portalProcess != null && this.portalProcess.isInsidePortalThisTick()) {
- this.handlePortal();
+ if (this.handlePortal()) return true; // Luminol - Correct portal logic of some projectile entities (Should not be ticking anymore after portal logics)
}
if (entitiesHit.isEmpty()) {
@@ -327,6 +327,7 @@ public abstract class AbstractArrow extends Projectile {
break;
}
}
+ return false; // Luminol - Correct portal logic of some projectile entities
}
private ProjectileDeflection hitTargetsOrDeflectSelf(final Collection<EntityHitResult> entityHitResults) {
@@ -0,0 +1,391 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 28 Jun 2026 18:54:50 +0800
Subject: [PATCH] Anonymous Object: Configurable region format framework
As requested by the original author, this patch will be anonymous and hide the original commit address.
This patch is used with permission from the original author.
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java b/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java
index a814512fcfb85312474ae2c2c21443843bf57831..9d69a022872e06b320bee46329cd8889f74fbbaa 100644
--- a/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java
@@ -8,9 +8,9 @@ public interface ChunkSystemRegionFileStorage {
public boolean moonrise$doesRegionFileNotExistNoIO(final int chunkX, final int chunkZ);
- public RegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ);
+ public io.anonymous.anonymous.data.RegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ); // Anonymous - Configurable region file format
- public RegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException;
+ public io.anonymous.anonymous.data.RegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException; // Anonymous - Configurable region file format
public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(
final int chunkX, final int chunkZ, final CompoundTag compound
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java b/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
index 3bb9b58ee97464687e348e23b99159226415c267..4c3ad63f17bb4a04f982b36ad3f53d3c0f58bdb5 100644
--- a/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
@@ -1273,7 +1273,7 @@ public final class MoonriseRegionFileIO {
this.regionDataController.finishWrite(this.chunkX, this.chunkZ, writeData);
// Paper start - flush regionfiles on save
if (this.world.paperConfig().chunks.flushRegionsOnSave) {
- final RegionFile regionFile = this.regionDataController.getCache().moonrise$getRegionFileIfLoaded(this.chunkX, this.chunkZ);
+ final io.anonymous.anonymous.data.RegionFile regionFile = this.regionDataController.getCache().moonrise$getRegionFileIfLoaded(this.chunkX, this.chunkZ); // Anonymous - Configurable region file format
if (regionFile != null) {
regionFile.flush();
} // else: evicted from cache, which should have called flush
@@ -1489,7 +1489,7 @@ public final class MoonriseRegionFileIO {
public static interface IORunnable {
- public void run(final RegionFile regionFile) throws IOException;
+ public void run(final io.anonymous.anonymous.data.RegionFile regionFile) throws IOException; // Anonymous - Configurable region file format
}
}
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java b/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java
index 51c126735ace8fdde89ad97b5cab62f244212db0..ea2f45964fb8c617f90938597c230b50d83744aa 100644
--- a/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java
@@ -8,5 +8,5 @@ public interface ChunkSystemChunkBuffer {
public void moonrise$setWriteOnClose(final boolean value);
- public void moonrise$write(final RegionFile regionFile) throws IOException;
+ public void moonrise$write(final io.anonymous.anonymous.data.RegionFile regionFile) throws IOException; // Anonymous - Configurable region file format
}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 56d26866cb8f413684e62558e5609b273efb8552..dd63f14cf6b857202146348b2242673af1883cb7 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -986,10 +986,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
if (flush) {
for (ServerLevel level : this.getAllLevels()) {
String storageName = level.getChunkSource().chunkMap.getStorageName();
- LOGGER.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", LEGACY_WORLD_NAMES_FOR_REALMS_LOG.getOrDefault(storageName, storageName));
+ LOGGER.info("ThreadedChunkStorage ({}): All chunks are saved", LEGACY_WORLD_NAMES_FOR_REALMS_LOG.getOrDefault(storageName, storageName)); // Anonymous - Configurable region format
}
- LOGGER.info("ThreadedAnvilChunkStorage: All dimensions are saved");
+ LOGGER.info("ThreadedChunkStorage: All dimensions are saved"); // Anonymous - configurable region format
}
return result;
diff --git a/net/minecraft/util/worldupdate/FileToUpgrade.java b/net/minecraft/util/worldupdate/FileToUpgrade.java
index a7f2cfa9277c898038f6b9e0a3401db51012b64c..7b96f3ec2738875a3ed7aa34347c0f174b6579c5 100644
--- a/net/minecraft/util/worldupdate/FileToUpgrade.java
+++ b/net/minecraft/util/worldupdate/FileToUpgrade.java
@@ -4,5 +4,5 @@ import java.util.List;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.storage.RegionFile;
-public record FileToUpgrade(RegionFile file, List<ChunkPos> chunksToUpgrade) {
+public record FileToUpgrade(io.anonymous.anonymous.data.RegionFile file, List<ChunkPos> chunksToUpgrade) { // Anonymous - Configurable region file format
}
diff --git a/net/minecraft/util/worldupdate/RegionStorageUpgrader.java b/net/minecraft/util/worldupdate/RegionStorageUpgrader.java
index b228cd6c54e8c8998923ab332882d99092769c67..2dd3d0afd8f58e5e68a361dd38c95da06b4d133b 100644
--- a/net/minecraft/util/worldupdate/RegionStorageUpgrader.java
+++ b/net/minecraft/util/worldupdate/RegionStorageUpgrader.java
@@ -36,7 +36,7 @@ import org.slf4j.Logger;
public class RegionStorageUpgrader {
private static final Logger LOGGER = LogUtils.getLogger();
private static final String NEW_DIRECTORY_PREFIX = "new_";
- private static final Pattern REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$");
+ private static final Pattern REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\." + me.earthme.luminol.config.modules.function.RegionFormatConfig.regionFormat.getArgument() + "$"); // Anonymous - Configurable region file format
private final DataFixer dataFixer;
private final UpgradeProgress upgradeProgress;
private final String type;
@@ -176,7 +176,8 @@ public class RegionStorageUpgrader {
int zOffset = Integer.parseInt(regex.group(2)) << 5;
List<ChunkPos> chunkPositions = Lists.newArrayList();
- try (RegionFile regionSource = new RegionFile(info, regionFile.toPath(), regionFolder, true)) {
+ var regionFileInfo = new io.anonymous.anonymous.utils.RegionCreatorInfo(info, regionFile.toPath(), regionFolder, true); // Anonymous - Configurable region file format
+ try (io.anonymous.anonymous.data.RegionFile regionSource = me.earthme.luminol.config.modules.function.RegionFormatConfig.regionFormat.getCreator().newFile(regionFileInfo)) { // Anonymous - Configurable region file format
for (int x = 0; x < 32; x++) {
for (int z = 0; z < 32; z++) {
ChunkPos pos = new ChunkPos(x + xOffset, z + zOffset);
@@ -253,7 +254,7 @@ public class RegionStorageUpgrader {
return storage.upgradeChunkTag(chunkTag, this.defaultVersion, this.dataFixContextTag, targetVersion);
}
- private void onFileFinished(final RegionFile regionFile) {
+ private void onFileFinished(final io.anonymous.anonymous.data.RegionFile regionFile) { // Anonymous - Configurable region file format
if (this.recreateRegionFiles) {
if (this.previousWriteFuture != null) {
this.previousWriteFuture.join();
diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
index 3de7fd2b084c38e72d7a6bc416880a881f514ad3..821924f85a507589a039832be8657e1dc3c0ce0c 100644
--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
@@ -22,7 +22,7 @@ import net.minecraft.world.level.ChunkPos;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
-public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile { // Paper - rewrite chunk system
+public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile , io.anonymous.anonymous.data.RegionFile{ // Paper - rewrite chunk system // Anonymous - Configurable region file format
private static final Logger LOGGER = LogUtils.getLogger();
public static final int MAX_CHUNK_SIZE = 500 * 1024 * 1024; // Paper - don't write garbage data to disk if writing serialization fails
private static final int SECTOR_BYTES = 4096;
@@ -130,7 +130,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
return this.recalculateCount.get();
}
- boolean recalculateHeader() throws IOException {
+ public boolean recalculateHeader() throws IOException { // Anonymous - Configurable region file format
if (!this.canRecalcHeader) {
return false;
}
@@ -789,7 +789,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
}
}
- protected synchronized void write(final ChunkPos pos, final ByteBuffer data) throws IOException {
+ public synchronized void write(final ChunkPos pos, final ByteBuffer data) throws IOException { // Anonymous - Configurable region file format
int offsetIndex = getOffsetIndex(pos);
int offset = this.offsets.get(offsetIndex);
int sectorNumber = getSectorNumber(offset);
@@ -907,7 +907,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
}
@Override
- public final void moonrise$write(final RegionFile regionFile) throws IOException {
+ public final void moonrise$write(final io.anonymous.anonymous.data.RegionFile regionFile) throws IOException { // Anonymous - Configurable region file format
regionFile.write(this.pos, ByteBuffer.wrap(this.buf, 0, this.count));
}
// Paper end - rewrite chunk system
@@ -973,11 +973,11 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
return (x & 31) + (z & 31) * 32;
}
- synchronized boolean isOversized(int x, int z) {
+ public synchronized boolean isOversized(int x, int z) { // Anonymous - Configurable region file format
return this.oversized[getChunkIndex(x, z)] == 1;
}
- synchronized void setOversized(int x, int z, boolean oversized) throws IOException {
+ public synchronized void setOversized(int x, int z, boolean oversized) throws IOException { // Anonymous - Configurable region file format
final int offset = getChunkIndex(x, z);
boolean previous = this.oversized[offset] == 1;
this.oversized[offset] = (byte) (oversized ? 1 : 0);
@@ -1016,7 +1016,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
return this.path.getParent().resolve(this.path.getFileName().toString().replaceAll("\\.mca$", "") + "_oversized_" + x + "_" + z + ".nbt");
}
- synchronized net.minecraft.nbt.CompoundTag getOversizedData(int x, int z) throws IOException {
+ public synchronized net.minecraft.nbt.CompoundTag getOversizedData(int x, int z) throws IOException { // Anonymous - Configurable region file format
Path file = getOversizedFile(x, z);
try (DataInputStream out = new DataInputStream(new java.io.BufferedInputStream(new java.util.zip.InflaterInputStream(Files.newInputStream(file))))) {
return net.minecraft.nbt.NbtIo.read((java.io.DataInput) out);
diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
index 63b40c420030d935bb81a219fb33defe0946e21f..e0e19f091f6b85d7788259cfcc53cc3db848b68e 100644
--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
@@ -19,7 +19,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper
public static final String ANVIL_EXTENSION = ".mca";
private static final int MAX_CACHE_SIZE = 256;
- private final Long2ObjectLinkedOpenHashMap<RegionFile> regionCache = new Long2ObjectLinkedOpenHashMap<>();
+ private final Long2ObjectLinkedOpenHashMap<io.anonymous.anonymous.data.RegionFile> regionCache = new Long2ObjectLinkedOpenHashMap<>(); // Anonymous - Configurable region file format
private final RegionStorageInfo info;
private final Path folder;
private final boolean sync;
@@ -30,7 +30,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
@Nullable
public static ChunkPos getRegionFileCoordinates(Path file) {
String fileName = file.getFileName().toString();
- if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) {
+ if (!fileName.startsWith("r.") || !fileName.endsWith(getExtensionName())) { // Anonymous - Configurable region file format
return null;
}
@@ -55,8 +55,32 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
private static final int MAX_NON_EXISTING_CACHE = 1024 * 4;
private final it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet nonExistingRegionFiles = new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet();
private static String getRegionFileName(final int chunkX, final int chunkZ) {
- return "r." + (chunkX >> REGION_SHIFT) + "." + (chunkZ >> REGION_SHIFT) + ".mca";
+ return "r." + (chunkX >> REGION_SHIFT) + "." + (chunkZ >> REGION_SHIFT) + getExtensionName(); // Anonymous - Configurable region file format
}
+ // Anonymous start - Configurable region file format
+ public static io.anonymous.anonymous.data.RegionFile createNew(RegionStorageInfo info, Path filePath, Path folder, boolean sync) throws IOException{
+ final io.anonymous.anonymous.enums.EnumRegionFormat regionFormat = me.earthme.luminol.config.modules.function.RegionFormatConfig.regionFormat;
+ final String fullFileName = filePath.getFileName().toString();
+ final String[] fullNameSplit = fullFileName.split("\\.");
+ final String extensionName = fullNameSplit[fullNameSplit.length - 1];
+
+ if (!regionFormat.getArgument().equalsIgnoreCase(extensionName)) {
+ // raise a delayed crash
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> {
+ throw new RuntimeException("Invalid region file format: " + extensionName + " expected " + regionFormat.getArgument());
+ });
+
+
+ throw new IOException("Invalid region file format: " + extensionName + " expected " + regionFormat.getArgument());
+ }
+
+ return regionFormat.getCreator().newFile(new io.anonymous.anonymous.utils.RegionCreatorInfo(info, filePath, folder, sync));
+ }
+
+ public static String getExtensionName() {
+ return "." + me.earthme.luminol.config.modules.function.RegionFormatConfig.regionFormat.getArgument();
+ }
+ // Anonymous end
private boolean doesRegionFilePossiblyExist(final long position) {
synchronized (this.nonExistingRegionFiles) {
@@ -90,15 +114,15 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
}
@Override
- public synchronized final RegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ) {
+ public synchronized final io.anonymous.anonymous.data.RegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ) { // Anonymous - Configurable region file format
return this.regionCache.getAndMoveToFirst(ChunkPos.pack(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT));
}
@Override
- public synchronized final RegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException {
+ public synchronized final io.anonymous.anonymous.data.RegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException { // Anonymous - Configurable region file format
final long key = ChunkPos.pack(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT);
- RegionFile ret = this.regionCache.getAndMoveToFirst(key);
+ io.anonymous.anonymous.data.RegionFile ret = this.regionCache.getAndMoveToFirst(key); // Anonymous - Configurable region file format
if (ret != null) {
return ret;
}
@@ -124,7 +148,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
FileUtil.createDirectoriesSafe(this.folder);
- ret = new RegionFile(this.info, regionPath, this.folder, this.sync);
+ ret = this.createNew(this.info, regionPath, this.folder, this.sync); // Anonymous - Configurable region file format
this.regionCache.putAndMoveToFirst(key, ret);
@@ -143,7 +167,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
}
final ChunkPos pos = new ChunkPos(chunkX, chunkZ);
- final RegionFile regionFile = this.getRegionFile(pos);
+ final io.anonymous.anonymous.data.RegionFile regionFile = this.getRegionFile(pos); // Anonymous - Configurable region file format
// note: not required to keep regionfile loaded after this call, as the write param takes a regionfile as input
// (and, the regionfile parameter is unused for writing until the write call)
@@ -177,7 +201,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
) throws IOException {
final ChunkPos pos = new ChunkPos(chunkX, chunkZ);
if (writeData.result() == ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.DELETE) {
- final RegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ);
+ final io.anonymous.anonymous.data.RegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); // Anonymous - Configurable region file format
if (regionFile != null) {
regionFile.clear(pos);
} // else: didn't exist
@@ -192,7 +216,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
public final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData moonrise$readData(
final int chunkX, final int chunkZ
) throws IOException {
- final RegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ);
+ final io.anonymous.anonymous.data.RegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); // Anonymous - Configurable region file format
final DataInputStream input = regionFile == null ? null : regionFile.getChunkDataInputStream(new ChunkPos(chunkX, chunkZ));
@@ -237,7 +261,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
final ChunkPos pos = new ChunkPos(chunkX, chunkZ);
final ChunkPos headerChunkPos = SerializableChunkData.getChunkCoordinate(ret);
- final RegionFile regionFile = this.getRegionFile(pos);
+ final io.anonymous.anonymous.data.RegionFile regionFile = this.getRegionFile(pos); // Anonymous - Configurable region file format
if (regionFile.getRecalculateCount() != readData.recalculateCount()) {
return null;
@@ -261,7 +285,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
}
// Paper end - rewrite chunk system
// Paper start - rewrite chunk system
- public RegionFile getRegionFile(ChunkPos pos) throws IOException {
+ public io.anonymous.anonymous.data.RegionFile getRegionFile(ChunkPos pos) throws IOException { // Anonymous - Configurable region file format
return this.getRegionFile(pos, false);
}
// Paper end - rewrite chunk system
@@ -273,7 +297,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
this.isChunkData = info.dfuType()[0] == net.minecraft.util.datafix.DataFixTypes.CHUNK; // Paper - recalculate region file headers
}
- @org.jetbrains.annotations.Contract("_, false -> !null") private @Nullable RegionFile getRegionFile(final ChunkPos pos, boolean existingOnly) throws IOException { // CraftBukkit
+ @org.jetbrains.annotations.Contract("_, false -> !null") private io.anonymous.anonymous.data.RegionFile getRegionFile(final ChunkPos pos, boolean existingOnly) throws IOException { // CraftBukkit // Anonymous - Configurable region file format
// Paper start - rewrite chunk system
if (existingOnly) {
return this.moonrise$getRegionFileIfExists(pos.x(), pos.z());
@@ -281,7 +305,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
synchronized (this) {
final long key = ChunkPos.pack(pos.x() >> REGION_SHIFT, pos.z() >> REGION_SHIFT);
- RegionFile ret = this.regionCache.getAndMoveToFirst(key);
+ io.anonymous.anonymous.data.RegionFile ret = this.regionCache.getAndMoveToFirst(key); // Anonymous - Configurable region file format
if (ret != null) {
return ret;
}
@@ -298,7 +322,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
FileUtil.createDirectoriesSafe(this.folder);
- ret = new RegionFile(this.info, regionPath, this.folder, this.sync);
+ ret = this.createNew(this.info, regionPath, this.folder, this.sync); // Anonymous - Configurable region file format
this.regionCache.putAndMoveToFirst(key, ret);
@@ -312,7 +336,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
LOGGER.error("{} ({} - {},{}) Go clean it up to remove this message. /minecraft:tp {} 128 {} - DO NOT REPORT THIS TO PAPER - You may ask for help on Discord, but do not file an issue. These error messages can not be removed.", msg, file.toString().replaceAll(".+[\\\\/]", ""), x, z, x << 4, z << 4);
}
- private static CompoundTag readOversizedChunk(RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException {
+ private static CompoundTag readOversizedChunk(io.anonymous.anonymous.data.RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException { // Anonymous - Configurable region file format
synchronized (regionfile) {
try (DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkCoordinate)) {
CompoundTag oversizedData = regionfile.getOversizedData(chunkCoordinate.x(), chunkCoordinate.z());
@@ -346,7 +370,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
public @Nullable CompoundTag read(final ChunkPos pos) throws IOException {
// CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
- RegionFile region = this.getRegionFile(pos, true);
+ io.anonymous.anonymous.data.RegionFile region = this.getRegionFile(pos, true); // Anonymous - Configurable region file format
if (region == null) {
return null;
}
@@ -383,7 +407,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
public void scanChunk(final ChunkPos pos, final StreamTagVisitor scanner) throws IOException {
// CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
- RegionFile region = this.getRegionFile(pos, true);
+ io.anonymous.anonymous.data.RegionFile region = this.getRegionFile(pos, true); // Anonymous - Configurable region file format
if (region == null) {
return;
}
@@ -398,7 +422,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
public void write(final ChunkPos pos, final @Nullable CompoundTag value) throws IOException {
if (!SharedConstants.DEBUG_DONT_SAVE_WORLD) {
- RegionFile region = this.getRegionFile(pos, value == null); // CraftBukkit // Paper - rewrite chunk system
+ io.anonymous.anonymous.data.RegionFile region = this.getRegionFile(pos, value == null); // CraftBukkit // Paper - rewrite chunk system // Anonymous - Configurable region file format
// Paper start - rewrite chunk system
if (region == null) {
// if the RegionFile doesn't exist, no point in deleting from it
@@ -430,7 +454,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
// Paper start - rewrite chunk system
synchronized (this) {
final ExceptionCollector<IOException> exceptionCollector = new ExceptionCollector<>();
- for (final RegionFile regionFile : this.regionCache.values()) {
+ for (final io.anonymous.anonymous.data.RegionFile regionFile : this.regionCache.values()) { // Anonymous - Configurable region file format
try {
regionFile.close();
} catch (final IOException ex) {
@@ -446,7 +470,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
// Paper start - rewrite chunk system
synchronized (this) {
final ExceptionCollector<IOException> exceptionCollector = new ExceptionCollector<>();
- for (final RegionFile regionFile : this.regionCache.values()) {
+ for (final io.anonymous.anonymous.data.RegionFile regionFile : this.regionCache.values()) { // Anonymous - Configurable region file format
try {
regionFile.flush();
} catch (final IOException ex) {
@@ -0,0 +1,21 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Fri, 17 Apr 2026 23:00:48 +0800
Subject: [PATCH] Do not enable any debug subscriptions
Really this would really crash the server by accident when the operators used F3 + J or toggled the debug synchronizer
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index a2e40bc7a15ba0663314a288f4a9b3ec3a39dc4d..216c9263999f05c36fb4c5598b41ff70e5759f5e 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -3483,7 +3483,8 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
}
public Set<DebugSubscription<?>> debugSubscriptions() {
- return !this.server.debugSubscribers().hasRequiredPermissions(this) ? Set.of() : this.requestedDebugSubscriptions;
+ // return !this.server.debugSubscribers().hasRequiredPermissions(this) ? Set.of() : this.requestedDebugSubscriptions; // Luminol - Do not enable any debug subscriptions
+ return Set.of(); // Luminol - Do not enable any debug subscriptions
}
public record RespawnConfig(LevelData.RespawnData respawnData, boolean forced) {
@@ -0,0 +1,32 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Fri, 17 Apr 2026 23:25:45 +0800
Subject: [PATCH] Do not load too far poi chunk in poi compete scan
Beeeeeeeeeeeeeeee
diff --git a/net/minecraft/world/entity/ai/behavior/PoiCompetitorScan.java b/net/minecraft/world/entity/ai/behavior/PoiCompetitorScan.java
index 53131c1b8d5562f9a1b45dfab4fdbdd37b655139..0e9c19a820aefa802d6df134b21f5128a5e8d656 100644
--- a/net/minecraft/world/entity/ai/behavior/PoiCompetitorScan.java
+++ b/net/minecraft/world/entity/ai/behavior/PoiCompetitorScan.java
@@ -24,8 +24,18 @@ public class PoiCompetitorScan {
return true;
}
// Folia end - region threading
- level.getPoiManager()
- .getType(pos.pos())
+ // Luminol start - POI fixes
+ var blockPosOfJobSite = pos.pos();
+ var sectionPosOfJobSite = net.minecraft.core.SectionPos.asLong(blockPosOfJobSite);
+ var poiManager = level.getPoiManager();
+ // we don't care if we should clear the memory of JOB_SITE
+ // as it will be automatically removed in SetWalkTargetFromBlockMemory
+ // so simply break down if it's not loaded
+ var poiChunk = me.earthme.luminol.config.modules.fixes.POIRangeFixes.doNotCompetePOIIfUnloaded ? poiManager.get(sectionPosOfJobSite) : poiManager.getOrLoad(sectionPosOfJobSite);
+ poiChunk.flatMap(poiSection -> poiSection.getType(blockPosOfJobSite))
+ // Luminol end - POI fixes
+ /*level.getPoiManager() // Luminol - POI fixes
+ .getType(pos.pos())*/ // Luminol - POI fixes
.ifPresent(
// Paper start - Improve performance of PoiCompetitorScan by unrolling stream
// The previous logic used Stream#reduce to simulate a form of single-iteration bubble sort
@@ -0,0 +1,287 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 28 Jun 2026 19:02:01 +0800
Subject: [PATCH] Fixes around entity brain and memories
diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java
index 3b1c3e84ab24b6e3a0e4d129b3617b393b6d6651..9c8e8de53cbbd87ac7cb8483bd3c1af3fcf66b6e 100644
--- a/net/minecraft/world/entity/Mob.java
+++ b/net/minecraft/world/entity/Mob.java
@@ -299,6 +299,11 @@ public abstract class Mob extends LivingEntity implements Targeting, EquipmentUs
if (Objects.equals(currentTarget, target)) {
return false;
}
+ // Luminol start - Fix off-region targeting
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(target)) {
+ return false;
+ }
+ // Luminol end
LivingEntity originalTarget = target;
target = asValidTarget(target);
if (reason != null) {
diff --git a/net/minecraft/world/entity/ai/Brain.java b/net/minecraft/world/entity/ai/Brain.java
index e097bc1268b4050e3948413c52dea69950858458..27894e2935e2afee8190708def4be3b00f922f64 100644
--- a/net/minecraft/world/entity/ai/Brain.java
+++ b/net/minecraft/world/entity/ai/Brain.java
@@ -383,7 +383,7 @@ public class Brain<E extends LivingEntity> {
}
public void tick(final ServerLevel level, final E body) {
- this.forgetOutdatedMemories();
+ this.forgetOutdatedMemories(body); // Luminol - Add config to force clean entity memory that don't belong to current tick region
this.tickSensors(level, body);
this.startEachNonRunningBehavior(level, body);
this.tickEachRunningBehavior(level, body);
@@ -395,8 +395,8 @@ public class Brain<E extends LivingEntity> {
}
}
- private void forgetOutdatedMemories() {
- this.memories.values().forEach(MemorySlot::tick);
+ private void forgetOutdatedMemories(E body) { // Luminol - Add config to force clean entity memory that don't belong to current tick region
+ this.memories.values().forEach(slot -> slot.tick(body)); // Luminol - Add config to force clean entity memory that don't belong to current tick region
}
public void stopAll(final ServerLevel level, final E body) {
diff --git a/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java b/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java
index f744ecd20dc70786311a7162b52aa4ceca0717db..aa60fbfbe7283f1ed9cf5343d59d1222380857dd 100644
--- a/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java
+++ b/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java
@@ -81,6 +81,11 @@ public class BehaviorUtils {
public static void setWalkAndLookTargetMemories(
final LivingEntity walker, final PositionTracker target, final float speedModifier, final int closeEnoughDistance
) {
+ // Luminol - Do not set walk target if target position is out of current tick region
+ if (!target.checkThread(walker.level())) {
+ return;
+ }
+ // Luminol end
WalkTarget walkTarget = new WalkTarget(target, speedModifier, closeEnoughDistance);
walker.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, target);
walker.getBrain().setMemory(MemoryModuleType.WALK_TARGET, walkTarget);
diff --git a/net/minecraft/world/entity/ai/behavior/BlockPosTracker.java b/net/minecraft/world/entity/ai/behavior/BlockPosTracker.java
index 68251875edfa47ac34c64f99510998ad4bbb14b4..d83b353395bad7c6c41dfaa24018fd207904d995 100644
--- a/net/minecraft/world/entity/ai/behavior/BlockPosTracker.java
+++ b/net/minecraft/world/entity/ai/behavior/BlockPosTracker.java
@@ -37,4 +37,11 @@ public class BlockPosTracker implements PositionTracker {
public String toString() {
return "BlockPosTracker{blockPos=" + this.blockPos + ", centerPosition=" + this.centerPosition + "}";
}
+
+ // Luminol start - Fix a series issue around entity memory typed GlobalPos and WalkTarget
+ @Override
+ public boolean checkThread(net.minecraft.world.level.Level currOwnedByLevel) {
+ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(currOwnedByLevel, this.blockPos);
+ }
+ // Luminol end
}
diff --git a/net/minecraft/world/entity/ai/behavior/EntityTracker.java b/net/minecraft/world/entity/ai/behavior/EntityTracker.java
index 3ac52b025ac3e1a3f9135b8e593a385a847afe0f..29c13f18cff52e9950e8932c75cb12fb02f952cb 100644
--- a/net/minecraft/world/entity/ai/behavior/EntityTracker.java
+++ b/net/minecraft/world/entity/ai/behavior/EntityTracker.java
@@ -55,4 +55,11 @@ public class EntityTracker implements PositionTracker {
public String toString() {
return "EntityTracker for " + this.entity;
}
+
+ // Luminol start - Fix a series issue around entity memory typed GlobalPos and WalkTarget
+ @Override
+ public boolean checkThread(net.minecraft.world.level.Level currOwnedByLevel) {
+ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.entity);
+ }
+ // Luminol end
}
diff --git a/net/minecraft/world/entity/ai/behavior/PositionTracker.java b/net/minecraft/world/entity/ai/behavior/PositionTracker.java
index ce6cf5ecfb190428e3ef9b7dd39c98e3d27a7b9d..3334da2236fbe90cbdc3e67884f9c6ed80ac0f56 100644
--- a/net/minecraft/world/entity/ai/behavior/PositionTracker.java
+++ b/net/minecraft/world/entity/ai/behavior/PositionTracker.java
@@ -10,4 +10,7 @@ public interface PositionTracker {
BlockPos currentBlockPosition();
boolean isVisibleBy(final LivingEntity body);
+
+
+ boolean checkThread(net.minecraft.world.level.Level currOwnedByLevel); // Luminol - Fix a series issue around entity memory typed GlobalPos and WalkTarget
}
diff --git a/net/minecraft/world/entity/ai/behavior/SleepInBed.java b/net/minecraft/world/entity/ai/behavior/SleepInBed.java
index 16017d819c28b077f732e2ef571eac179d24e323..7a598aa1a0963fda3302946a30d0330c341da74f 100644
--- a/net/minecraft/world/entity/ai/behavior/SleepInBed.java
+++ b/net/minecraft/world/entity/ai/behavior/SleepInBed.java
@@ -57,6 +57,11 @@ public class SleepInBed extends Behavior<LivingEntity> {
}
}
+ // Luminol Start - Prevent off-tick-region chunk operations
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(level, target.pos())) {
+ return false;
+ }
+ // Luminol End
BlockState blockState = level.getBlockStateIfLoaded(target.pos()); // Paper - Prevent sync chunk loads when villagers try to find beds
if (blockState == null) return false; // Paper - Prevent sync chunk loads when villagers try to find beds
return target.pos().closerToCenterThan(body.position(), 2.0) && blockState.is(BlockTags.BEDS) && !blockState.getValue(BedBlock.OCCUPIED);
diff --git a/net/minecraft/world/entity/ai/memory/MemorySlot.java b/net/minecraft/world/entity/ai/memory/MemorySlot.java
index 88a89b4c72cc99dc89d3f3cc928b2dfda4125759..3b51a44ab700740047c9e725547ceec65c0c80b1 100644
--- a/net/minecraft/world/entity/ai/memory/MemorySlot.java
+++ b/net/minecraft/world/entity/ai/memory/MemorySlot.java
@@ -13,7 +13,7 @@ public class MemorySlot<T> {
this.timeToLive = timeToLive;
}
- public void tick() {
+ public void tick(net.minecraft.world.entity.Entity owner) { // Luminol - Add config to force clean entity memory that don't belong to current tick region
if (this.hasValue() && this.canExpire()) {
if (this.hasExpired()) {
this.clear();
@@ -21,6 +21,41 @@ public class MemorySlot<T> {
this.timeToLive--;
}
}
+ // Luminol start - Add config to force clean entity memory that don't belong to current tick region
+ final net.minecraft.world.level.Level ownerLevel = owner.level();
+
+ // type: entity
+ if (me.earthme.luminol.config.modules.fixes.ForceCleanupEntityBrainMemoryConfig.enabledForEntity && this.value instanceof net.minecraft.world.entity.Entity entity) {
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(entity)) {
+ this.clear();
+ }
+ }
+
+ // type: block_pos
+ if (me.earthme.luminol.config.modules.fixes.ForceCleanupEntityBrainMemoryConfig.enabledForBlockPos && this.value instanceof net.minecraft.core.BlockPos blockPos) {
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(ownerLevel, blockPos)) {
+ this.clear();
+ }
+ }
+
+
+ //type: position_tracker and walk_target
+ if (me.earthme.luminol.config.modules.fixes.ForceCleanupEntityBrainMemoryConfig.enabledForPositionTracker) {
+ net.minecraft.world.entity.ai.behavior.PositionTracker tracker = null;
+
+ if (value instanceof net.minecraft.world.entity.ai.behavior.PositionTracker positionTracker) {
+ tracker = positionTracker;
+ }
+
+ if (value instanceof net.minecraft.world.entity.ai.memory.WalkTarget walkTarget) {
+ tracker = walkTarget.getTarget();
+ }
+
+ if (tracker != null && !tracker.checkThread(owner.level())) {
+ this.clear();
+ }
+ }
+ // Luminol end
}
public static <T> MemorySlot<T> create() {
diff --git a/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java b/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java
index e44814cfb6afb594456b8215bd13a92e93de2c85..c18e2071c699d9c5f4d46b7a58191df4f4d8ab5d 100644
--- a/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java
+++ b/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java
@@ -60,6 +60,17 @@ public class FlyingPathNavigation extends PathNavigation {
if (!this.isDone()) {
Vec3 target = this.path.getNextEntityPos(this.mob);
+ // Luminol - Recompute path when path finding out of current tick region
+ if (me.earthme.luminol.config.modules.fixes.PathfindingFixesConfig.breakDownPathfindingWhenOutOfRegion) {
+ // we assume that:
+ // 1. The code above doesn't touch the 'main thread context' with the position from 'this.path'
+ // 2. The pathfinder could correctly recompute or discard the incorrect target position and this situation is happening rarely
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.mob.level(), target)) {
+ this.hasDelayedRecomputation = true;
+ return;
+ }
+ }
+ // Luminol end
this.mob.getMoveControl().setWantedPosition(target.x, target.y, target.z, this.speedModifier);
}
}
diff --git a/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/net/minecraft/world/entity/ai/navigation/PathNavigation.java
index 2ea1ec39a37899ae1510d0036aa670e758077537..4c727e4f224f6a31a4cd9d024bdcfaf0fa1bc0e5 100644
--- a/net/minecraft/world/entity/ai/navigation/PathNavigation.java
+++ b/net/minecraft/world/entity/ai/navigation/PathNavigation.java
@@ -188,6 +188,18 @@ public abstract class PathNavigation {
}
}
// Paper end - EntityPathfindEvent
+ // Luminol start - Do not path find for targets out of current region
+ if (me.earthme.luminol.config.modules.fixes.PathfindingFixesConfig.doNotPathfindToNotOwnedTargets) {
+ // filter the targets not owned by current region
+ targets = new java.util.HashSet<>(targets); // well no idea about how to determine if this should be copied to a modifiable one
+ targets.removeIf(pos -> !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.mob.level(), pos));
+
+ // return if no available (observe the logic in the first if block)
+ if (targets.isEmpty()) {
+ return null;
+ }
+ }
+ // Luminol end
ProfilerFiller profiler = Profiler.get();
profiler.push("pathfind");
BlockPos fromPos = above ? this.mob.blockPosition().above() : this.mob.blockPosition();
@@ -286,6 +298,17 @@ public abstract class PathNavigation {
if (!this.isDone()) {
Vec3 target = this.path.getNextEntityPos(this.mob);
+ // Luminol - Recompute path when path finding out of current tick region
+ if (me.earthme.luminol.config.modules.fixes.PathfindingFixesConfig.breakDownPathfindingWhenOutOfRegion) {
+ // we assume that:
+ // 1. The code above doesn't touch the 'main thread context' with the position from 'this.path'
+ // 2. The pathfinder could correctly recompute or discard the incorrect target position and this situation is happening rarely
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.mob.level(), target)) {
+ this.hasDelayedRecomputation = true;
+ return;
+ }
+ }
+ // Luminol end
this.mob.getMoveControl().setWantedPosition(target.x, this.getGroundY(target), target.z, this.speedModifier);
}
}
diff --git a/net/minecraft/world/entity/animal/allay/AllayAi.java b/net/minecraft/world/entity/animal/allay/AllayAi.java
index c3667e7997551e0f5ce63bc6ee890082d1431400..2e2458535a8ec542d6371ceba2fff1260773c7f5 100644
--- a/net/minecraft/world/entity/animal/allay/AllayAi.java
+++ b/net/minecraft/world/entity/animal/allay/AllayAi.java
@@ -112,6 +112,17 @@ public class AllayAi {
Optional<GlobalPos> likedNoteblockPos = brain.getMemory(MemoryModuleType.LIKED_NOTEBLOCK_POSITION);
if (likedNoteblockPos.isPresent()) {
GlobalPos position = likedNoteblockPos.get();
+ // Luminol start - Do not like item if they were out of current tickregion
+ final Level targetLevel = allay.level().getServer().getLevel(position.dimension());
+ final BlockPos targetPos = position.pos();
+
+ // thread checks
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(targetLevel, targetPos)) {
+ brain.eraseMemory(MemoryModuleType.LIKED_NOTEBLOCK_POSITION); // The memory value is not being belong to current tick region anymore
+ return Optional.empty();
+ }
+ // Luminol end
+
if (shouldDepositItemsAtLikedNoteblock(allay, brain, position)) {
return Optional.of(new BlockPosTracker(position.pos().above()));
}
diff --git a/net/minecraft/world/entity/animal/sniffer/Sniffer.java b/net/minecraft/world/entity/animal/sniffer/Sniffer.java
index d5394ae7ce56555ad21aeafb2d78c291c62e2988..a2873d8bbfa3e04678bc31637222b3a10cd6fedd 100644
--- a/net/minecraft/world/entity/animal/sniffer/Sniffer.java
+++ b/net/minecraft/world/entity/animal/sniffer/Sniffer.java
@@ -279,8 +279,18 @@ public class Sniffer extends Animal {
private boolean canDig(final BlockPos position) {
return this.level().getBlockState(position).is(BlockTags.SNIFFER_DIGGABLE_BLOCK)
- && this.getExploredPositions().noneMatch(explored -> GlobalPos.of(this.level().dimension(), position).equals(explored))
- && Optional.ofNullable(this.getNavigation().createPath(position, 1)).map(Path::canReach).orElse(false);
+ && this.getExploredPositions().noneMatch(explored -> { // Luminol start - Do not pathfind out of tickregion
+ // thread checks
+ final Level targetLevel = net.minecraft.server.MinecraftServer.getServer().getLevel(explored.dimension());
+ final BlockPos targetPos = explored.pos();
+
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(targetLevel, targetPos)) {
+ return false;
+ }
+
+ return GlobalPos.of(this.level().dimension(), position).equals(explored); // Original logic
+ }) // Luminol end
+ && Optional.ofNullable(this.getNavigation().createPath(position, 1)).map(Path::canReach).orElse(false); // Luminol - Do not pathfind out of tickregion - diff on change
}
private void dropSeed() {
@@ -0,0 +1,38 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Fri, 17 Apr 2026 23:16:45 +0800
Subject: [PATCH] Fix off region leashing
PS: My friend reported me this bug and I still need to check the details of how the bug was caused
XD
diff --git a/net/minecraft/world/entity/Leashable.java b/net/minecraft/world/entity/Leashable.java
index 98034a4c96bf2972316078d3b3a49140921aab50..284ba0c3742d5d5ab0b344a84a9c8e2c2a98a74a 100644
--- a/net/minecraft/world/entity/Leashable.java
+++ b/net/minecraft/world/entity/Leashable.java
@@ -95,10 +95,24 @@ public interface Leashable {
if (leashUuid.isPresent()) {
Entity leasher = serverLevel.getEntity(leashUuid.get());
if (leasher != null) {
+ // Luminol start - Fix off region leashing
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(leasher)) {
+ entity.spawnAtLocation(serverLevel, Items.LEAD);
+ entity.setLeashData(null);
+ return;
+ }
+ // Luminol end
setLeashedTo(entity, leasher, true);
return;
}
} else if (pos.isPresent()) {
+ // Luminol start - Fix off region leashing
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(serverLevel, pos.get())) {
+ entity.spawnAtLocation(serverLevel, Items.LEAD);
+ entity.setLeashData(null);
+ return;
+ }
+ // Luminol end
setLeashedTo(entity, LeashFenceKnotEntity.getOrCreateKnot(serverLevel, pos.get()), true);
return;
}
@@ -0,0 +1,30 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Fri, 17 Apr 2026 23:21:30 +0800
Subject: [PATCH] Fix unpatched task returning of
ServerConfigurationPacketListenerImpl#disconnectAsync
Inspired by https://github.com/CraftCanvasMC/Canvas/blob/ver/1.21.8/canvas-server/minecraft-patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch
but Canvas loses its main thread checks so fixed that btw
diff --git a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
index 77c4670868a95dc7b13461dfa67af34b0a3c329a..111789b4aca281dd1899dfbcc4e7e0ca36d74b51 100644
--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
@@ -299,13 +299,14 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis
// Paper start
@Override
public void disconnectAsync(final net.minecraft.network.DisconnectionDetails disconnectionInfo) {
- if (this.cserver.isPrimaryThread()) {
+ if (io.papermc.paper.threadedregions.RegionizedServer.isGlobalTickThread()) { // Luminol - Fix unpatched task returning of ServerConfigurationPacketListenerImpl#disconnectAsync
this.disconnect(disconnectionInfo);
return;
}
this.connection.setReadOnly();
- this.server.scheduleOnMain(() -> {
+ // this.server.scheduleOnMain(() -> { // Luminol - Fix unpatched task returning of ServerConfigurationPacketListenerImpl#disconnectAsync
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Luminol - Fix unpatched task returning of ServerConfigurationPacketListenerImpl#disconnectAsync
this.disconnect(disconnectionInfo); // Currently you cannot cancel disconnect during the config stage
});
}
@@ -1,19 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Mon, 19 May 2025 00:42:17 +0800
Subject: [PATCH] Old zombie reinforcement
diff --git a/net/minecraft/world/entity/monster/zombie/Zombie.java b/net/minecraft/world/entity/monster/zombie/Zombie.java
index 0008aa0ca02fc4ea67b795b3f0a0e9656233ce89..2734ffc103ee4760d3521609fdd090ab99df675b 100644
--- a/net/minecraft/world/entity/monster/zombie/Zombie.java
+++ b/net/minecraft/world/entity/monster/zombie/Zombie.java
@@ -331,7 +331,7 @@ public class Zombie extends Monster {
int x = Mth.floor(this.getX());
int y = Mth.floor(this.getY());
int z = Mth.floor(this.getZ());
- EntityType<? extends Zombie> type = this.getType();
+ EntityType<? extends Zombie> type = fun.bm.lophine.config.modules.function.OldFeatureConfig.oldZombieReinforcement ? EntityType.ZOMBIE : this.getType(); // Lophine - old zombie reinforcement
Zombie reinforcement = type.create(level, EntitySpawnReason.REINFORCEMENT);
if (reinforcement == null) {
return true;
@@ -0,0 +1,64 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Fri, 17 Apr 2026 23:24:07 +0800
Subject: [PATCH] Fix riding statistics desync
Referred to: https://github.com/CraftCanvasMC/Canvas/commit/057175b0c10d5a4d1d9059fd9d077750c32633b2
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 216c9263999f05c36fb4c5598b41ff70e5759f5e..6c2196726a0669bddda456a089aeec0d9d9bdfa3 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -2516,7 +2516,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
}
}
- private void checkRidingStatistics(final double dx, final double dy, final double dz) {
+ public void checkRidingStatistics(final double dx, final double dy, final double dz) { // Luminol - Fix riding statics desync (make public)
if (this.isPassenger() && !didNotMove(dx, dy, dz)) {
int distance = Math.round((float)Math.sqrt(dx * dx + dy * dy + dz * dz) * 100.0F);
Entity vehicle = this.getVehicle();
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 18b9e630442fd31a14e131bb9043be46e10881e3..b475a8fe259848749ee05ff6cf10e46f52f955d3 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -4194,7 +4194,13 @@ public abstract class Entity
}
}
+ // Luminol start - Fix riding statics desync
public void adjustRiders(boolean teleport) {
+ this.adjustRiders(teleport, false);
+ }
+
+ public void adjustRiders(boolean teleport, boolean syncStatics) {
+ // Luminol end - Fix riding statics desync
java.util.ArrayDeque<EntityTreeNode> queue = new java.util.ArrayDeque<>();
queue.add(this);
@@ -4207,14 +4213,24 @@ public abstract class Entity
for (EntityTreeNode passenger : passengers) {
queue.add(passenger);
+ // Luminol start - Fix riding statics desync
+ final double oldX = passenger.root.getX();
+ final double oldY = passenger.root.getY();
+ final double oldZ = passenger.root.getZ();
+ // Luminol end - Fix riding statics desync
curr.root.positionRider(passenger.root, teleport ? Entity::snapTo : Entity::setPos);
+ // Luminol start - Fix riding statics desync
+ if (syncStatics && passenger.root instanceof net.minecraft.server.level.ServerPlayer serverPlayer) {
+ serverPlayer.checkRidingStatistics(serverPlayer.getX() - oldX, serverPlayer.getY() - oldY, serverPlayer.getZ() - oldZ);
+ }
+ // Luminol end - Fix riding statics desync
}
}
}
}
public void repositionAllPassengers(boolean teleport) {
- this.makePassengerTree().adjustRiders(teleport);
+ this.makePassengerTree().adjustRiders(teleport, true); // Luminol - Fix riding statics desync
}
protected EntityTreeNode makePassengerTree() {
@@ -0,0 +1,24 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Sat, 18 Apr 2026 10:45:19 +0800
Subject: [PATCH] Fix misbehaved ender pearls when player switched dimension
A fix of https://github.com/PaperMC/Folia/issues/421
Almost the same bug as:
https://github.com/PaperMC/Folia/pull/418 and https://github.com/PaperMC/Folia/issues/393
diff --git a/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java b/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java
index d0dba5cc351903fb762e231a95f302479794185f..fb00f70505a6c548cd1311d7ea55c6a501e814e3 100644
--- a/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java
+++ b/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java
@@ -264,7 +264,8 @@ public class ThrownEnderpearl extends ThrowableItemProjectile {
int previousChunkZ = SectionPos.blockToSectionCoord(this.position().z());
Entity owner = this.owner != null ? findOwnerIncludingDeadPlayer(serverLevel, this.owner.getUUID()) : null;
if (owner instanceof ServerPlayer serverPlayer
- && !owner.isAlive()
+ // && !owner.isAlive() // Luminol - Fix misbehaved ender pearls when player switched dimension
+ && (owner.getBukkitEntity().taskScheduler.isRetired() || serverPlayer.getHealth() <= 0.0D) // Luminol - Fix misbehaved ender pearls when player switched dimension
&& !serverPlayer.wonGame
&& serverPlayer.level().getGameRules().get(GameRules.ENDER_PEARLS_VANISH_ON_DEATH)) {
this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
@@ -0,0 +1,21 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Thu, 2 Apr 2026 12:26:24 +0800
Subject: [PATCH] Fix long commands support
Some long commands can be run through the dialog command, but paper has prohibited it.
Revert to vanilla to fix it.
diff --git a/net/minecraft/network/protocol/game/ServerboundChatCommandPacket.java b/net/minecraft/network/protocol/game/ServerboundChatCommandPacket.java
index 491af2413a4e793a121fec368259ff8211ed031e..042cb4792b0084f143608f05dd08da133a31e435 100644
--- a/net/minecraft/network/protocol/game/ServerboundChatCommandPacket.java
+++ b/net/minecraft/network/protocol/game/ServerboundChatCommandPacket.java
@@ -12,7 +12,7 @@ public record ServerboundChatCommandPacket(String command) implements Packet<Ser
);
private ServerboundChatCommandPacket(final FriendlyByteBuf input) {
- this(input.readUtf(MAX_CHAT_PACKET_INPUT_SIZE)); // Paper - limit chat command inputs
+ this(me.earthme.luminol.config.modules.fixes.LongCommandSupportConfig.enabled ? input.readUtf() : input.readUtf(MAX_CHAT_PACKET_INPUT_SIZE)); // Paper - limit chat command inputs // Luminol - add support for long command inputs
}
private void write(final FriendlyByteBuf output) {
@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Mon, 20 Apr 2026 01:02:43 +0800
Subject: [PATCH] Fix creative player item pick
diff --git a/net/minecraft/world/entity/item/ItemEntity.java b/net/minecraft/world/entity/item/ItemEntity.java
index 223793bea54a483fefc66caa7e07b29ab351c339..303b9befb30d7156a9a71900ac1a6ea8f7e8d876 100644
--- a/net/minecraft/world/entity/item/ItemEntity.java
+++ b/net/minecraft/world/entity/item/ItemEntity.java
@@ -420,7 +420,7 @@ public class ItemEntity extends Entity implements TraceableEntity {
Item item = itemStack.getItem();
int orgCount = itemStack.getCount();
// CraftBukkit start - fire PlayerPickupItemEvent
- int canHold = player.getInventory().canHold(itemStack);
+ int canHold = player.hasInfiniteMaterials() ? orgCount : player.getInventory().canHold(itemStack); // Luminol - Fix creative item picking
int remaining = orgCount - canHold;
boolean flyAtPlayer = false; // Paper
@@ -0,0 +1,67 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Mon, 20 Apr 2026 01:10:30 +0800
Subject: [PATCH] Fix entity portal-teleport speed
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 35ab432bbd59aba80a6eaca1769e6aa82c8a7e46..6950fa2462353f30ee02e1d0091cfcf289f63939 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -1492,7 +1492,27 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
foliaProfiler.startTimer(timerId);
try {
// Folia end - profiler
+ // Luminol start - Entity portal-teleport speed fix
if (isActive) { // Paper - EAR 2
+ if (!(entity instanceof Player) && entity.teleportTickType == 2) { // Luminol - after portal compensate tick
+ entity.tick();
+ entity.tick();
+ entity.teleportTickType = 0;
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(entity)) {
+ return;
+ }
+ if (entity.handlePortal()) {
+ return;
+ }
+ } else if (!(entity instanceof Player) && entity.teleportTickType == 1) { // Luminol - portal teleport only
+ entity.teleportTickType++;
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(entity)) {
+ return;
+ }
+ if (entity.handlePortal()) {
+ return;
+ }
+ } else {
entity.tick();
// Folia start - region threading
if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(entity)) {
@@ -1503,6 +1523,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// portalled
return;
}
+ }
+ // Luminol end - Entity portal-teleport speed fix
// Folia end - region threading
} else {entity.inactiveTick();} // Paper - EAR 2
profiler.pop();
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index b475a8fe259848749ee05ff6cf10e46f52f955d3..5e7cf38d241591715fec9ec008d3ac87bbaef662 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -387,6 +387,7 @@ public abstract class Entity
public long activatedTick = Integer.MIN_VALUE;
public boolean isTemporarilyActive;
public long activatedImmunityTick = Integer.MIN_VALUE;
+ public int teleportTickType = 0;// Luminol - Entity portal-teleport speed fix
public void inactiveTick() {
}
@@ -3591,6 +3592,7 @@ public abstract class Entity
} else {
if (this.portalProcess == null || !this.portalProcess.isSamePortal(portal)) {
this.portalProcess = new PortalProcessor(portal, pos.immutable());
+ this.teleportTickType = 1; // Luminol - Entity portal-teleport speed fix
} else if (!this.portalProcess.isInsidePortalThisTick()) {
this.portalProcess.updateEntryPosition(pos.immutable());
this.portalProcess.setAsInsidePortalThisTick(true);
@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Wed, 24 Jun 2026 22:52:13 +0800
Subject: [PATCH] Fix player auto saving ignores interval
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 6c2196726a0669bddda456a089aeec0d9d9bdfa3..0802483484015fd92dec211aae9fd33e45621f74 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -208,7 +208,7 @@ import org.slf4j.Logger;
public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer { // Paper - rewrite chunk system
private static final Logger LOGGER = LogUtils.getLogger();
- public static final long LAST_SAVE_ABSENT = Long.MIN_VALUE; public long lastSave = LAST_SAVE_ABSENT; // Paper // Folia - threaded regions - changed to nanoTime
+ public static final long LAST_SAVE_ABSENT = Long.MIN_VALUE; public long lastSave = System.nanoTime(); // Paper // Folia - threaded regions - changed to nanoTime // Luminol - Fix player auto saving ignores interval
private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32;
private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10;
private static final int FLY_STAT_RECORDING_SPEED = 25;
@@ -0,0 +1,98 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Bacteriawa <A3167717663@hotmail.com>
Date: Tue, 21 Apr 2026 01:15:09 +0800
Subject: [PATCH] Force disable builtin spark plugin
The spark passed down from paper has some memory leaking issue, so we fully removed it from the code to prevent that memory leaking issue.
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index dd63f14cf6b857202146348b2242673af1883cb7..88ccf32b3c1b3506fb76be5ae520168b6b167aa3 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -672,8 +672,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
// Paper end - Configurable player collision; Handle collideRule team for player collision toggle
this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD);
- this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark
- this.server.spark.enableAfterPlugins(this.server); // Paper - spark
+ if (false) this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark // Luminol - Force disable builtin spark
+ if (false) this.server.spark.enableAfterPlugins(this.server); // Paper - spark // Luminol - Force disable builtin spark
io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setValid(); // Paper - reset invalid state for event fire below
io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, io.papermc.paper.command.brigadier.PaperCommands.INSTANCE, org.bukkit.plugin.Plugin.class, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL); // Paper - call commands event for regular plugins
this.server.getCommandMap().registerServerAliases(); // Paper - relocate initial CommandMap#registerServerAliases() call
@@ -1087,7 +1087,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
Commands.COMMAND_SENDING_POOL.shutdownNow(); // Paper - Perf: Async command map building; Shutdown and don't bother finishing
// CraftBukkit start
if (this.server != null) {
- this.server.spark.disable(); // Paper - spark
+ if (false) this.server.spark.disable(); // Paper - spark // Luminol - Force disable builtin spark
this.server.disablePlugins();
this.server.waitForAsyncTasksShutdown(); // Paper - Wait for Async Tasks during shutdown
}
@@ -1351,7 +1351,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.statusIcon = this.loadStatusIcon().orElse(null);
this.status = this.buildServerStatus();
- this.server.spark.enableBeforePlugins(); // Paper - spark
+ if (false) this.server.spark.enableBeforePlugins(); // Paper - spark // Luminol - Force disable builtin spark
// Folia start - region threading
if (true) {
io.papermc.paper.threadedregions.RegionizedServer.getInstance().init(); // Folia - region threading - only after loading worlds
@@ -1665,7 +1665,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
if (this.emptyTicks >= emptyTickThreshold) {
- this.server.spark.tickStart(); // Paper - spark
+ if (false) this.server.spark.tickStart(); // Paper - spark // Luminol - Force disable builtin spark
if (this.emptyTicks == emptyTickThreshold) {
LOGGER.info("Server empty for {} seconds, pausing", this.pauseWhenEmptySeconds());
this.autoSave();
@@ -1684,7 +1684,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// Paper end - avoid issues with certain tasks not processing during sleep
//this.server.spark.executeMainThreadTasks(); // Paper - spark // Folia - region threading
this.tickConnection();
- this.server.spark.tickEnd(((double)(System.nanoTime() - this.currentTickStart) / 1000000D)); // Paper - spark
+ if (false) this.server.spark.tickEnd(((double)(System.nanoTime() - this.currentTickStart) / 1000000D)); // Paper - spark // Luminol - Force disable builtin spark
return;
}
}
@@ -1697,7 +1697,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
};
// Folia end - region threading
- this.server.spark.tickStart(); // Paper - spark
+ if (false) this.server.spark.tickStart(); // Paper - spark // Luminol - Force disable builtin spark
new com.destroystokyo.paper.event.server.ServerTickStartEvent((int)region.getCurrentTick()).callEvent(); // Paper - Server Tick Events // Folia - region threading
// Folia start - region threading
if (region != null) {
@@ -1784,7 +1784,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
long remaining = scheduledEnd - endTime; // Folia - region ticking
new com.destroystokyo.paper.event.server.ServerTickEndEvent((int)io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick(), ((double)(endTime - startTime) / 1000000D), remaining).callEvent(); // Folia - region ticking
// Paper end - Server Tick Events
- this.server.spark.tickEnd(((double)(endTime - startTime) / 1000000D)); // Paper - spark // Folia - region threading
+ if (false) this.server.spark.tickEnd(((double)(endTime - startTime) / 1000000D)); // Paper - spark // Folia - region threading // Luminol - Force disable builtin spark
// Folia - region threading
}
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
index 1ad4704523a3d90414ac514488207b1b5def9afd..1145d5839e621a02b7972c2ff1142f51db2237de 100644
--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -239,7 +239,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
// Paper end - initialize global and world-defaults configuration
me.earthme.luminol.config.ConfigManager.loadConfigFiles(); // Luminol - load config file
- this.server.spark.enableEarlyIfRequested(); // Paper - spark
+ if (false) this.server.spark.enableEarlyIfRequested(); // Paper - spark // Luminol - Force disable builtin spark
// Paper start - fix converting txt to json file; convert old users earlier after PlayerList creation but before file load/save
if (this.convertOldUsers()) {
this.services().nameToIdCache().save(false); // Paper
@@ -249,7 +249,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); // Paper - start watchdog thread
consoleThread.start(); // Paper - Enhance console tab completions for brigadier commands; start console thread after MinecraftServer.console & PaperConfig are initialized
io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command
- this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark
+ if (false) this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark // Luminol - Force disable builtin spark
com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics
com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now
@@ -0,0 +1,24 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 28 Jun 2026 19:10:30 +0800
Subject: [PATCH] Prevent tamable animals check can teleport in an unloaded
chunk
Based on the implementation of Canvas(https://github.com/CraftCanvasMC/Canvas/commit/af2aacb12cbccfd328dda4961167786f5d9dad53)
We need to check canTeleport for TamableAnimal but this logic could do the check in another tick region itself, so we need to prevent that. But it needs to push off this check to schedule to the correct tickregion, which needs costly rewriting of this logic, so we could simply allow the read when the owner's position is loaded
diff --git a/net/minecraft/world/entity/TamableAnimal.java b/net/minecraft/world/entity/TamableAnimal.java
index 95ba821a895c7ea8fffcbf1df2e7b3c174463d9e..b0a01673224522552672b7c8c9aa219108e8010f 100644
--- a/net/minecraft/world/entity/TamableAnimal.java
+++ b/net/minecraft/world/entity/TamableAnimal.java
@@ -318,7 +318,8 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity {
return false;
}
- BlockState blockStateBelow = this.level().getBlockState(pos.below());
+ BlockState blockStateBelow = this.level().getBlockStateIfLoaded(pos.below()); // Luminol - Prevent tamable animals check can teleport in an unloaded chunk
+ if (blockStateBelow == null) return false; // Luminol - Prevent tamable animals check can teleport in an unloaded chunk
if (!this.canFlyToOwner() && blockStateBelow.getBlock() instanceof LeavesBlock) {
return false;
}
@@ -0,0 +1,88 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Fri, 1 May 2026 21:52:07 +0800
Subject: [PATCH] Prevent teleprotAsync calls in move events
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index c86b1fd5e200c478d178be32791b6e8c960d47e2..9be6d486082d344fb91fd74207eee83ca2f8e869 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -730,7 +730,9 @@ public class ServerGamePacketListenerImpl
Location oldTo = to.clone();
PlayerMoveEvent event = new PlayerMoveEvent(player, from, to);
+ this.player.blockTeleportAsync = true; // Luminol - Prevent teleprotAsync calls during move events
this.cserver.getPluginManager().callEvent(event);
+ this.player.blockTeleportAsync = false; // Luminol - Prevent teleprotAsync calls during move events
// If the event is cancelled we move the player back to their old location.
if (event.isCancelled()) {
@@ -1741,7 +1743,9 @@ public class ServerGamePacketListenerImpl
Location oldTo = to.clone();
PlayerMoveEvent event = new PlayerMoveEvent(player, from, to);
+ this.player.blockTeleportAsync = true; // Luminol - Prevent teleprotAsync calls during move events
this.cserver.getPluginManager().callEvent(event);
+ this.player.blockTeleportAsync = false; // Luminol - Prevent teleprotAsync calls during move events
// If the event is cancelled we move the player back to their old location.
if (event.isCancelled()) {
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 5e7cf38d241591715fec9ec008d3ac87bbaef662..51dacbe395c85aba9b81831e511a413b303ca675 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -4472,12 +4472,25 @@ public abstract class Entity
teleportTarget.cause(), teleportFlags, teleportComplete
);
}
+ // Luminol start - Prevent teleprotAsync calls in move events
+ public boolean blockTeleportAsync = false;
+ // Luminol end
public final boolean teleportAsync(ServerLevel destination, Vec3 pos, Float yaw, Float pitch, Vec3 velocity,
org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause, long teleportFlags,
java.util.function.Consumer<Entity> teleportComplete) {
ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot teleport entity async");
+ // Luminol start - Prevent teleprotAsync calls in move events
+ if (this.blockTeleportAsync && me.earthme.luminol.config.modules.fixes.PreventIncorrectTeleportAsyncConfig.enabled) {
+ if (me.earthme.luminol.config.modules.fixes.PreventIncorrectTeleportAsyncConfig.throwWhenCaught) {
+ throw new IllegalStateException("Call teleportAsync during move events!");
+ }
+
+ LOGGER.error("Calling teleportAsync during move events!", new Throwable());
+ return false;
+ }
+ // Luminol end
if (!ServerLevel.isInSpawnableBounds(new BlockPos(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getBlockX(pos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getBlockY(pos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getBlockZ(pos)))) {
return false;
}
diff --git a/net/minecraft/world/entity/vehicle/boat/AbstractBoat.java b/net/minecraft/world/entity/vehicle/boat/AbstractBoat.java
index 3fcd534cee1f605a710ffc43eff4b5106c7dd40c..9b887b40236a212de513e3ceece1abfec5372ae2 100644
--- a/net/minecraft/world/entity/vehicle/boat/AbstractBoat.java
+++ b/net/minecraft/world/entity/vehicle/boat/AbstractBoat.java
@@ -282,7 +282,9 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable {
if (this.lastLocation != null && !this.lastLocation.equals(to)) {
org.bukkit.event.vehicle.VehicleMoveEvent event = new org.bukkit.event.vehicle.VehicleMoveEvent(vehicle, this.lastLocation, to);
+ this.blockTeleportAsync = true; // Luminol - Prevent teleprotAsync calls during move events
event.callEvent();
+ this.blockTeleportAsync = false; // Luminol - Prevent teleprotAsync calls during move events
}
this.lastLocation = vehicle.getLocation();
// CraftBukkit end
diff --git a/net/minecraft/world/entity/vehicle/minecart/AbstractMinecart.java b/net/minecraft/world/entity/vehicle/minecart/AbstractMinecart.java
index 5ad77636d0681d1f0341cff4482ba6ad52f3583b..0ae8b5222c5f2f608af352cbaa866c493dd065aa 100644
--- a/net/minecraft/world/entity/vehicle/minecart/AbstractMinecart.java
+++ b/net/minecraft/world/entity/vehicle/minecart/AbstractMinecart.java
@@ -318,7 +318,9 @@ public abstract class AbstractMinecart extends VehicleEntity {
new org.bukkit.event.vehicle.VehicleUpdateEvent(vehicle).callEvent();
if (!from.equals(to)) {
+ this.blockTeleportAsync = true; // Luminol - Prevent teleprotAsync calls during move events
new org.bukkit.event.vehicle.VehicleMoveEvent(vehicle, from, to).callEvent();
+ this.blockTeleportAsync = false; // Luminol - Prevent teleprotAsync calls during move events
}
// CraftBukkit end
this.updateFluidInteraction();
@@ -0,0 +1,47 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 28 Jun 2026 19:19:31 +0800
Subject: [PATCH] Sync dragon part when teleportation or firstly created
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 aa53138b3670a8337b639472a6ab5ce46702ff76..ed27b73e787782f328906b2a061feeca25d42a24 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
@@ -93,6 +93,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 51dacbe395c85aba9b81831e511a413b303ca675..e2e1b101de8afbec7d853593c527e35111c94319 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -4456,6 +4456,7 @@ public abstract class Entity
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
// 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
this.postRemoveAfterChangingDimensions();
diff --git a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
index ab921ab4b4cc860abd77744f2bd201013e136e51..fd85816ec78c99f45c740ebf8052ff41f4859ed6 100644
--- a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
+++ b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
@@ -1011,4 +1011,12 @@ public class EnderDragon extends Mob implements Enemy {
return 500;
}
// Paper end - init expToDrop for already dying spawned dragon
+
+ // 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
}
@@ -0,0 +1,63 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 28 Jun 2026 19:32:34 +0800
Subject: [PATCH] Teleport async if entity was moving to another region at once
On folia, entity usually cannot move out of the tickregion, but sometimes it actually does(like some end pearl gun that can shoot an end pearl to the block faraway than 10000 blocks even more). To fix this, we added a temporary fix which teleport these entities to the destination instead running its move logics so that we could ensure anything is under control.But one thing need to consider is that teleportAsync is actually calling halfway of the entity tick and there is still something running when teleportAsync called, which is actually modified the entity in another thread, so there is still need an improvement
Reference from : https://github.com/KaiijuMC/Kaiiju/blob/ver/1.20.1/patches/server/0040-Teleport-async-if-we-cannot-move-entity-off-main.patch
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index e2e1b101de8afbec7d853593c527e35111c94319..33c9692a59c58c44a77b1077553e2bda88cb8305 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -1163,6 +1163,19 @@ public abstract class Entity
this.moveStartZ = this.getZ();
this.moveVector = delta;
}
+ //Luminol start - Fix high position moving
+ // Filter the threads as it may be called by the chunk system worker thread
+ if (me.earthme.luminol.config.modules.fixes.FoliaEntityMovingFixConfig.enabled && ca.spottedleaf.moonrise.common.util.TickThread.isTickThread()){
+ var finalPosition = delta.add(this.position);
+ // not NaN (Prevent incorrect checks under NaN minecarts)
+ if (!Double.isNaN(finalPosition.x) && !Double.isNaN(finalPosition.y) && !Double.isNaN(finalPosition.z)) {
+ // kill tick passively if it's moving out of region and we'll catch this exception
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.level,finalPosition)) {
+ throw new me.earthme.luminol.utils.EntityMoveOutOfRegionException(this, delta, moverType);
+ }
+ }
+ }
+ //Luminol end
try {
// Paper end - detailed watchdog information
if (this.noPhysics) {
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
index a0eb1f608a99301e0197ab1d1a6fbbb0a0be4ce0..dc28223684091cd0d8dbf7ff01626b20e4f1b662 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -1571,6 +1571,25 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public <T extends Entity> void guardEntityTick(final Consumer<T> tick, final T entity) {
try {
tick.accept(entity);
+ } catch (me.earthme.luminol.utils.EntityMoveOutOfRegionException moveOutOfRegionException) { // Luminol - Teleport async if entity was moving to another region at once
+ // Luminol start - Teleport async if entity was moving to another region at once
+ final Entity ent = moveOutOfRegionException.getEntity();
+ var currPosition = ent.position();
+ var toPosition = moveOutOfRegionException.getMovement().add(currPosition);
+
+ if (me.earthme.luminol.config.modules.fixes.FoliaEntityMovingFixConfig.warnOnDetected) {
+ MinecraftServer.LOGGER.warn("Entity {} with entityId {} has tried moving to another region!",ent, ent.getId());
+ }
+
+ ent.getBukkitEntity().taskScheduler.schedule(entityFresh -> entityFresh.teleportAsync(
+ (ServerLevel) entityFresh.level(),
+ toPosition,
+ entityFresh.getYRot(), entityFresh.getXRot(),
+ entityFresh.getDeltaMovement(), org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.UNKNOWN,
+ Entity.TELEPORT_FLAG_LOAD_CHUNK | Entity.TELEPORT_FLAG_TELEPORT_PASSENGERS,
+ null
+ ), null, 1L);
+ // Luminol end
} catch (Throwable t) {
// Paper start - Prevent block entity and entity crashes
final String msg = String.format("Entity threw exception at %s:%s,%s,%s", io.papermc.paper.util.MCUtil.getLevelName(entity.level()), entity.getX(), entity.getY(), entity.getZ());
@@ -0,0 +1,47 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Wed, 8 Jul 2026 21:37:19 +0800
Subject: [PATCH] Fix teleport missing original rotation and velocity
diff --git a/io/papermc/paper/threadedregions/TeleportUtils.java b/io/papermc/paper/threadedregions/TeleportUtils.java
index 2a64a5b2cf049661fe3f5a22ddfa39979624f5ec..335e9775e0d9a61f0e9641aba691a3bdf3ac7f2b 100644
--- a/io/papermc/paper/threadedregions/TeleportUtils.java
+++ b/io/papermc/paper/threadedregions/TeleportUtils.java
@@ -41,7 +41,7 @@ public final class TeleportUtils {
return;
}
(useFromRootVehicle ? realFrom.getRootVehicle() : realFrom).teleportAsync(
- ((CraftWorld)loc.getWorld()).getHandle(), pos, null, null, null,
+ ((CraftWorld)loc.getWorld()).getHandle(), pos, yaw, pitch, null, // Camellia - Fix teleport missing original rotation and velocity
cause, teleportFlags, onComplete
);
},
diff --git a/net/minecraft/server/commands/TeleportCommand.java b/net/minecraft/server/commands/TeleportCommand.java
index 13d7965fd4f99f0848b080349df41f8b1c31a19b..5b83f2edd6afe94c28bf491afab737e92fb89dc0 100644
--- a/net/minecraft/server/commands/TeleportCommand.java
+++ b/net/minecraft/server/commands/TeleportCommand.java
@@ -248,8 +248,8 @@ public class TeleportCommand {
// Folia start - region threading
if (true) {
Vec3 posFinal = new Vec3(x, y, z);
- Float yawFinal = Float.valueOf(newYRot);
- Float pitchFinal = Float.valueOf(newXRot);
+ Float yawFinal = Float.valueOf(newYRot + victim.getYRot()); // Camellia - Fix teleport missing original rotation and velocity
+ Float pitchFinal = Float.valueOf(newXRot + victim.getXRot()); // Camellia - Fix teleport missing original rotation and velocity
victim.getBukkitEntity().taskScheduler.schedule((Entity nmsEntity) -> {
nmsEntity.stopRiding();
nmsEntity.teleportAsync(
diff --git a/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java b/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java
index fb00f70505a6c548cd1311d7ea55c6a501e814e3..4df9a3356cfdee52761db47694e59779976840db 100644
--- a/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java
+++ b/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java
@@ -99,7 +99,7 @@ public class ThrownEnderpearl extends ThrowableItemProjectile {
}
entity.teleportAsync(
- checkWorld, to, null, null, null,
+ checkWorld, to, null, null, entity.getDeltaMovement(), // Camellia - Fix teleport missing original rotation and velocity
org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.ENDER_PEARL,
// chunk could have been unloaded
Entity.TELEPORT_FLAG_TELEPORT_PASSENGERS | Entity.TELEPORT_FLAG_LOAD_CHUNK,
@@ -0,0 +1,288 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 28 Jun 2026 19:47:24 +0800
Subject: [PATCH] Improved Server Health Report
diff --git a/io/papermc/paper/threadedregions/commands/CommandServerHealth.java b/io/papermc/paper/threadedregions/commands/CommandServerHealth.java
index aa860cfe9de616f185163fe74e0f4f49bd13a805..05d4f73340412f737224cb1443ecf1e33586ed38 100644
--- a/io/papermc/paper/threadedregions/commands/CommandServerHealth.java
+++ b/io/papermc/paper/threadedregions/commands/CommandServerHealth.java
@@ -29,6 +29,14 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
+// Luminol start - Improved Server Health Report
+import java.util.concurrent.TimeUnit;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.lang.management.MemoryUsage;
+import java.lang.management.RuntimeMXBean;
+import org.jetbrains.annotations.NotNull;
+// Luminol end
public final class CommandServerHealth extends Command {
@@ -45,7 +53,7 @@ public final class CommandServerHealth extends Command {
private static final TextColor HEADER = TextColor.color(79, 164, 240);
private static final TextColor PRIMARY = TextColor.color(48, 145, 237);
private static final TextColor SECONDARY = TextColor.color(104, 177, 240);
- private static final TextColor INFORMATION = TextColor.color(145, 198, 243);
+ private static final TextColor INFORMATION = TextColor.color(180, 220, 255); // Luminol start - Improved Server Health Report
private static final TextColor LIST = TextColor.color(33, 97, 188);
public CommandServerHealth() {
@@ -60,9 +68,11 @@ public final class CommandServerHealth extends Command {
return Component.text()
.append(Component.text(prefix, PRIMARY, TextDecoration.BOLD))
.append(Component.text(ONE_DECIMAL_PLACES.get().format(util * 100.0), CommandUtil.getUtilisationColourRegion(util)))
- .append(Component.text("% util at ", PRIMARY))
+ .append(Component.text("% util", PRIMARY))
+ .append(Component.text(" | ", SECONDARY))
.append(Component.text(TWO_DECIMAL_PLACES.get().format(mspt), CommandUtil.getColourForMSPT(mspt)))
- .append(Component.text(" MSPT at ", PRIMARY))
+ .append(Component.text(" mspt", PRIMARY))
+ .append(Component.text(" | ", SECONDARY))
.append(Component.text(TWO_DECIMAL_PLACES.get().format(tps), CommandUtil.getColourForTPS(tps)))
.append(Component.text(" TPS" + (newline ? "\n" : ""), PRIMARY))
.build();
@@ -81,7 +91,7 @@ public final class CommandServerHealth extends Command {
private static boolean executeRegion(final CommandSender sender, final String commandLabel, final String[] args) {
final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region =
- TickRegionScheduler.getCurrentRegion();
+ TickRegionScheduler.getCurrentRegion();
if (region == null) {
sender.sendMessage(Component.text("You are not in a region currently", NamedTextColor.RED));
return true;
@@ -105,8 +115,7 @@ public final class CommandServerHealth extends Command {
final double tps1m = report1m.tpsData().segmentAll().average();
final double mspt1m = report1m.timePerTickData().segmentAll().average() / 1.0E6;
- final int yLoc = 80;
- final String location = "[w:'" + world.getWorld().getName() + "'," + centerBlockX + "," + yLoc + "," + centerBlockZ + "]";
+ final String location = world.getWorld().getName() + " (" + centerBlockX + ", " + centerBlockZ + ")";
final Component line = Component.text()
.append(Component.text("Region around block ", PRIMARY))
@@ -144,7 +153,7 @@ public final class CommandServerHealth extends Command {
}
final List<ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData>> regions =
- new ArrayList<>();
+ new ArrayList<>();
for (final World bukkitWorld : Bukkit.getWorlds()) {
final ServerLevel world = ((CraftWorld)bukkitWorld).getHandle();
@@ -163,6 +172,18 @@ public final class CommandServerHealth extends Command {
final long currTime = System.nanoTime();
final TickData.TickReportData globalTickReport = RegionizedServer.getGlobalTickData().getTickReport15s(currTime);
+ final MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
+ final MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
+ final long usedMemory = heapUsage.getUsed() / (1024 * 1024);
+ final long maxMemory = heapUsage.getMax() / (1024 * 1024);
+
+ final double memPercent = (double) usedMemory / maxMemory * 100.0;
+ final TextColor memColor = memPercent < 60 ? CommandUtil.getUtilisationColourRegion(0.0) : (memPercent < 85 ? NamedTextColor.YELLOW : NamedTextColor.RED);
+
+ final RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
+ final long uptime = runtimeBean.getUptime();
+ final String uptimeStr = formatUptime(uptime);
+
for (final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region : regions) {
final TickData.TickReportData report = region.getData().getRegionSchedulingHandle().getTickReport15s(currTime);
tpsByRegion.add(report == null ? 20.0 : report.tpsData().segmentAll().average());
@@ -174,31 +195,27 @@ public final class CommandServerHealth extends Command {
final double loadRate = ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkFullTask.loadRate(currTime);
totalUtil += globalTickReport.utilisation();
+ final TextColor utilisationColor = CommandUtil.getUtilisationColourRegion(totalUtil / (double)maxThreadCount);
tpsByRegion.sort(null);
if (!tpsByRegion.isEmpty()) {
minTps = tpsByRegion.getDouble(0);
maxTps = tpsByRegion.getDouble(tpsByRegion.size() - 1);
-
final int middle = tpsByRegion.size() >> 1;
if ((tpsByRegion.size() & 1) == 0) {
- // even, average the two middle points
medianTps = (tpsByRegion.getDouble(middle - 1) + tpsByRegion.getDouble(middle)) / 2.0;
} else {
- // odd, can just grab middle
medianTps = tpsByRegion.getDouble(middle);
}
} else {
- // no regions = green
minTps = medianTps = maxTps = 20.0;
}
final List<ObjectObjectImmutablePair<ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData>, TickData.TickReportData>>
- regionsBelowThreshold = new ArrayList<>();
+ regionsBelowThreshold = new ArrayList<>();
for (int i = 0, len = regions.size(); i < len; ++i) {
final TickData.TickReportData report = reportsByRegion.get(i);
-
regionsBelowThreshold.add(new ObjectObjectImmutablePair<>(regions.get(i), report));
}
@@ -207,11 +224,18 @@ public final class CommandServerHealth extends Command {
final TickData.TickReportData report2 = p2.right();
final double util1 = report1 == null ? 0.0 : report1.utilisation();
final double util2 = report2 == null ? 0.0 : report2.utilisation();
-
- // we want the largest first
return Double.compare(util2, util1);
});
+ long totalChunks = 0;
+ long totalEntities = 0;
+
+ for (final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region : regions) {
+ final TickRegions.RegionStats stats = region.getData().getRegionStats();
+ totalChunks += stats.getChunkCount();
+ totalEntities += stats.getEntityCount();
+ }
+
final TextComponent.Builder lowestRegionsBuilder = Component.text();
if (sender instanceof Player) {
@@ -219,23 +243,18 @@ public final class CommandServerHealth extends Command {
}
for (int i = 0, len = Math.min(lowestRegionsCount, regionsBelowThreshold.size()); i < len; ++i) {
final ObjectObjectImmutablePair<ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData>, TickData.TickReportData>
- pair = regionsBelowThreshold.get(i);
+ pair = regionsBelowThreshold.get(i);
final TickData.TickReportData report = pair.right();
final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region =
- pair.left();
+ pair.left();
- if (report == null) {
- // skip regions with no data
- continue;
- }
+ if (report == null) continue;
final ServerLevel world = region.regioniser.world;
final ChunkPos chunkCenter = region.getCenterChunk();
- if (chunkCenter == null) {
- // region does not exist anymore
- continue;
- }
+ if (chunkCenter == null) continue;
+
final int centerBlockX = ((chunkCenter.x() << 4) | 7);
final int centerBlockZ = ((chunkCenter.z() << 4) | 7);
final double util = report.utilisation();
@@ -243,18 +262,21 @@ public final class CommandServerHealth extends Command {
final double mspt = report.timePerTickData().segmentAll().average() / 1.0E6;
final int yLoc = 80;
- final String location = "[w:'" + world.getWorld().getName() + "'," + centerBlockX + "," + yLoc + "," + centerBlockZ + "]";
+ final String location = world.getWorld().getName() + " (" + centerBlockX + ", " + centerBlockZ + ")";
+
final Component line = Component.text()
.append(Component.text(" - ", LIST, TextDecoration.BOLD))
- .append(Component.text("Region around block ", PRIMARY))
+ .append(Component.text("Region at ", PRIMARY))
.append(Component.text(location, INFORMATION))
.append(Component.text(":\n", PRIMARY))
.append(Component.text(" ", PRIMARY))
.append(Component.text(ONE_DECIMAL_PLACES.get().format(util * 100.0), CommandUtil.getUtilisationColourRegion(util)))
- .append(Component.text("% util at ", PRIMARY))
+ .append(Component.text("% util", PRIMARY))
+ .append(Component.text(" | ", SECONDARY))
.append(Component.text(TWO_DECIMAL_PLACES.get().format(mspt), CommandUtil.getColourForMSPT(mspt)))
- .append(Component.text(" MSPT at ", PRIMARY))
+ .append(Component.text(" mspt", PRIMARY))
+ .append(Component.text(" | ", SECONDARY))
.append(Component.text(TWO_DECIMAL_PLACES.get().format(tps), CommandUtil.getColourForTPS(tps)))
.append(Component.text(" TPS\n", PRIMARY))
@@ -270,29 +292,53 @@ public final class CommandServerHealth extends Command {
sender.sendMessage(
Component.text()
- .append(Component.text("Server Health Report\n", HEADER, TextDecoration.BOLD))
+ .append(Component.text("Server Health Report", HEADER, TextDecoration.BOLD))
+ .append(Component.text(" (Uptime: ", SECONDARY))
+ .append(Component.text(uptimeStr, utilisationColor))
+ .append(Component.text(")\n", SECONDARY))
.append(Component.text(" - ", LIST, TextDecoration.BOLD))
.append(Component.text("Online Players: ", PRIMARY))
- .append(Component.text(Bukkit.getOnlinePlayers().size() + "\n", INFORMATION))
+ .append(Component.text(Bukkit.getOnlinePlayers().size(), INFORMATION))
+ .append(Component.newline())
.append(Component.text(" - ", LIST, TextDecoration.BOLD))
.append(Component.text("Total regions: ", PRIMARY))
- .append(Component.text(regions.size() + "\n", INFORMATION))
+ .append(Component.text(regions.size(), INFORMATION))
+ .append(Component.text(", ", PRIMARY))
+ .append(Component.text("Total Chunks: ", PRIMARY))
+ .append(Component.text(NO_DECIMAL_PLACES.get().format(totalChunks), INFORMATION))
+ .append(Component.text(", ", PRIMARY))
+ .append(Component.text("Total Entities: ", PRIMARY))
+ .append(Component.text(NO_DECIMAL_PLACES.get().format(totalEntities) + "\n", INFORMATION))
.append(Component.text(" - ", LIST, TextDecoration.BOLD))
.append(Component.text("Utilisation: ", PRIMARY))
- .append(Component.text(ONE_DECIMAL_PLACES.get().format(totalUtil * 100.0), CommandUtil.getUtilisationColourRegion(totalUtil / (double)maxThreadCount)))
- .append(Component.text("% / ", PRIMARY))
+ .append(Component.text(ONE_DECIMAL_PLACES.get().format(totalUtil * 100.0), utilisationColor))
+ .append(Component.text("%", PRIMARY))
+ .append(Component.text(" / ", SECONDARY))
.append(Component.text(ONE_DECIMAL_PLACES.get().format(maxThreadCount * 100.0), INFORMATION))
.append(Component.text("%\n", PRIMARY))
.append(Component.text(" - ", LIST, TextDecoration.BOLD))
.append(Component.text("Load rate: ", PRIMARY))
- .append(Component.text(TWO_DECIMAL_PLACES.get().format(loadRate) + ", ", INFORMATION))
+ .append(Component.text(TWO_DECIMAL_PLACES.get().format(loadRate), INFORMATION))
+ .append(Component.text(", ", PRIMARY))
.append(Component.text("Gen rate: ", PRIMARY))
.append(Component.text(TWO_DECIMAL_PLACES.get().format(genRate) + "\n", INFORMATION))
+ .append(Component.text(" - ", LIST, TextDecoration.BOLD))
+ .append(Component.text("Memory: ", PRIMARY))
+ .append(Component.text(NO_DECIMAL_PLACES.get().format(usedMemory), memColor))
+ .append(Component.text(" MB", memColor))
+ .append(Component.text(" / ", SECONDARY))
+ .append(Component.text(NO_DECIMAL_PLACES.get().format(maxMemory), INFORMATION))
+ .append(Component.text(" MB", INFORMATION))
+ .append(Component.text(" (", SECONDARY))
+ .append(Component.text(ONE_DECIMAL_PLACES.get().format(memPercent) + "%", memColor))
+ .append(Component.text(")", SECONDARY))
+ .append(Component.newline())
+
.append(Component.text(" - ", LIST, TextDecoration.BOLD))
.append(Component.text("Lowest Region TPS: ", PRIMARY))
.append(Component.text(TWO_DECIMAL_PLACES.get().format(minTps) + "\n", CommandUtil.getColourForTPS(minTps)))
@@ -361,4 +407,21 @@ public final class CommandServerHealth extends Command {
}
return new ArrayList<>();
}
+
+ // Luminol start - Improved Server Health Report
+ private static @NotNull String formatUptime(long uptimeMillis) {
+ long days = TimeUnit.MILLISECONDS.toDays(uptimeMillis);
+ long hours = TimeUnit.MILLISECONDS.toHours(uptimeMillis) % 24;
+ long minutes = TimeUnit.MILLISECONDS.toMinutes(uptimeMillis) % 60;
+ long seconds = TimeUnit.MILLISECONDS.toSeconds(uptimeMillis) % 60;
+
+ StringBuilder sb = new StringBuilder();
+ if (days > 0) sb.append(days).append("d ");
+ if (hours > 0) sb.append(hours).append("h ");
+ if (minutes > 0) sb.append(minutes).append("m ");
+ sb.append(seconds).append("s");
+
+ return sb.toString();
+ }
+ // Luminol end
}
\ No newline at end of file
@@ -0,0 +1,522 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Fri, 1 May 2026 18:37:50 +0800
Subject: [PATCH] World load/unload APIs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
好吧这里这一坨需要说明的太多了而且我也不是很会翻译所以就用中文注释了)
这一整个实现使用了一种安全点的思路,相对于folia pr里和canvas的让每个region自己卸载, 我更偏向于将调度器拆分到每个level中然后实现类似于单个level停服的逻辑,这样会更加符合直觉而且实现要更加快速而且易于排查问题
由于世界卸载可能发生在玩家登录或者传送的过程中,所以我使用了引用计数器来避免这个竟态条件的发生
diff --git a/io/papermc/paper/threadedregions/RegionShutdownThread.java b/io/papermc/paper/threadedregions/RegionShutdownThread.java
index f2208bcd5ccf3bd5a428e8ea3bf3ff26b3c9483b..d5c4e9b82f2ebc135b6747d19678b1a15e808ec8 100644
--- a/io/papermc/paper/threadedregions/RegionShutdownThread.java
+++ b/io/papermc/paper/threadedregions/RegionShutdownThread.java
@@ -14,11 +14,11 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
-public final class RegionShutdownThread extends ca.spottedleaf.moonrise.common.util.TickThread {
+public class RegionShutdownThread extends ca.spottedleaf.moonrise.common.util.TickThread { // Luminol - Level schedulers - Make public
- private static final Logger LOGGER = LogUtils.getClassLogger();
+ public static final Logger LOGGER = LogUtils.getClassLogger(); // Luminol - Level schedulers - Make public
- ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> shuttingDown;
+ public ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> shuttingDown; // Luminol - Level schedulers - Make public
public RegionShutdownThread(final String name) {
super(name);
@@ -52,7 +52,7 @@ public final class RegionShutdownThread extends ca.spottedleaf.moonrise.common.u
// the data required to do that is regionised, and we can only access it when we OWN the region, and we do not.
// Thus, the only operation that the shutdown thread will perform
- private void saveLevelData(final ServerLevel world) {
+ public void saveLevelData(final ServerLevel world) { // Luminol - Level hot unload apis - Make public
try {
world.saveLevelData(true);
} catch (final Throwable thr) {
@@ -60,7 +60,7 @@ public final class RegionShutdownThread extends ca.spottedleaf.moonrise.common.u
}
}
- private void finishTeleportations(final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region,
+ public void finishTeleportations(final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region, // Luminol - Level hot unload apis - Make public
final ServerLevel world) {
try {
this.shuttingDown = region;
@@ -94,7 +94,7 @@ public final class RegionShutdownThread extends ca.spottedleaf.moonrise.common.u
}
}
- private void saveRegionChunks(final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region,
+ public void saveRegionChunks(final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region, // Luminol - Level hot unload apis - Make public
final boolean last) {
ChunkPos center = null;
try {
@@ -109,7 +109,7 @@ public final class RegionShutdownThread extends ca.spottedleaf.moonrise.common.u
}
}
- private void haltChunkSystem(final ServerLevel world) {
+ public void haltChunkSystem(final ServerLevel world) { // Luminol - Level hot unload apis - Make public
try {
world.moonrise$getChunkTaskScheduler().chunkHolderManager.close(false, true, true, false, false);
} catch (final Throwable thr) {
@@ -117,7 +117,7 @@ public final class RegionShutdownThread extends ca.spottedleaf.moonrise.common.u
}
}
- private void closePlayerInventories(final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region) {
+ public void closePlayerInventories(final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region) { // Luminol - Level hot unload apis - Make public
ChunkPos center = null;
try {
this.shuttingDown = region;
@@ -150,7 +150,7 @@ public final class RegionShutdownThread extends ca.spottedleaf.moonrise.common.u
}
@Override
- public final void run() {
+ public void run() { // Luminol - Level hot unload apis - Make public
// await scheduler termination
LOGGER.info("Awaiting scheduler termination for 60s...");
if (TickRegions.getScheduler().halt(true, TimeUnit.SECONDS.toNanos(60L))) {
diff --git a/io/papermc/paper/threadedregions/RegionizedServer.java b/io/papermc/paper/threadedregions/RegionizedServer.java
index aa575f3b76ef70ffb9f0410e7e5cfe7af384bfbd..ff90abb47203c03636d3175f2b3efcf43517b3b0 100644
--- a/io/papermc/paper/threadedregions/RegionizedServer.java
+++ b/io/papermc/paper/threadedregions/RegionizedServer.java
@@ -63,6 +63,12 @@ public final class RegionizedServer {
this.worlds.add(world);
}
+ // Luminol start - Level hot unload apis
+ public void removeWorld(final ServerLevel world) {
+ this.worlds.remove(world);
+ }
+ // Luminol end
+
public void init() {
// call init event _before_ scheduling anything
new RegionizedServerInitEvent().callEvent();
diff --git a/io/papermc/paper/threadedregions/TickRegionScheduler.java b/io/papermc/paper/threadedregions/TickRegionScheduler.java
index 5a72fa0d72bfdb927293abe921c57f6ae964a6ec..f8344f640f119e3865f2718d1abd0ede9bdb8aa8 100644
--- a/io/papermc/paper/threadedregions/TickRegionScheduler.java
+++ b/io/papermc/paper/threadedregions/TickRegionScheduler.java
@@ -49,7 +49,7 @@ public final class TickRegionScheduler {
}
// Folia end - watchdog
- private final Scheduler scheduler;
+ public final Scheduler scheduler; // Luminol - Level schedulers - Make public
public static enum SchedulerType {
EDF,
@@ -220,7 +220,15 @@ public final class TickRegionScheduler {
*/
public void scheduleRegion(final RegionScheduleHandle region) {
region.scheduler = this;
+ // this.scheduler.schedule(region); // Luminol - Level schedulers
+ // Luminol start - Level schedulers
+ if(region.region != null) {
+ region.region.world.levelScheduler.schedule(region);
+ return;
+ }
+
this.scheduler.schedule(region);
+ // Luminol end
}
/**
@@ -233,13 +241,77 @@ public final class TickRegionScheduler {
region.markNonSchedulable();
}
+ // Luminol start - Level schedulers
+ private void handleParentSchedulerDead() {
+ for (ServerLevel level : MinecraftServer.getServer().getAllLevels()) {
+ level.levelScheduler.onParentHalted();
+ }
+ }
+
+ public boolean haltAndWaitLevelSchedulerHalt(final boolean sync, final long maxWaitNS) {
+ final long deadline = System.nanoTime() + maxWaitNS;
+
+ boolean hasUnhaltedLevelScheduler = false;
+ for (net.minecraft.server.level.ServerLevel serverLevel : MinecraftServer.getServer().getAllLevels()) {
+ serverLevel.levelScheduler.halt(); // ensure it's halted so that we could skip running any other new tasks
+
+ // block any new reading reference acquiring going into this lock
+ serverLevel.levelUnloadStateLock.blockReadingReferencing();
+
+ // sync not required, directly go next
+ if (!sync) {
+ hasUnhaltedLevelScheduler |= !serverLevel.levelScheduler.isFullyExited();
+ continue;
+ }
+
+ final boolean writeLockHeld = serverLevel.levelUnloadStateLock.acquireWrite(deadline);
+ // we got the write lock, abort the others unloading logics
+ if (writeLockHeld) {
+ serverLevel.levelUnloadStateLock.acquireUnreachable();
+
+ // since there is no unloading tasks, so the callback would be always empty, just directly go next,
+ // and we'll handle the final saving in global shutdown logics
+ continue;
+ }
+
+ // cannot get write lock, might be marked as unreachable
+ final boolean isUnreachable = serverLevel.levelUnloadStateLock.isUnreachable();
+ // already fully terminated (we mark it as unreachable after all separate unload saving logics), check the scheduler state
+ if (isUnreachable) {
+ continue;
+ }
+
+ // try linearly block wait for halt
+ long failures = 0L;
+ while (!serverLevel.levelScheduler.isFullyExited()) {
+ // check deadline
+ if (deadline - System.nanoTime() <= 0) {
+ return false;
+ }
+
+ failures = ca.spottedleaf.concurrentutil.util.ConcurrentUtil.linearLongBackoffDeadline(failures, 100, 1000L, deadline);
+ }
+ }
+
+ return !hasUnhaltedLevelScheduler;
+ }
+ // Luminol end - Level schedulers
+
public boolean halt(final boolean sync, final long maxWaitNS) {
- this.scheduler.halt();
+ final boolean allLevelSchedulersExited = this.haltAndWaitLevelSchedulerHalt(sync, maxWaitNS); // Luminol - Level schedulers
+ if (allLevelSchedulersExited) this.scheduler.halt(); // Luminol - Level schedulers
if (!sync) {
return this.scheduler.getAliveThreads().length == 0;
}
- return this.scheduler.join(maxWaitNS == 0L ? 0L : Math.max(1L, TimeUnit.NANOSECONDS.toMillis(maxWaitNS)));
+ // return this.scheduler.join(maxWaitNS == 0L ? 0L : Math.max(1L, TimeUnit.NANOSECONDS.toMillis(maxWaitNS))); // Luminol - Level schedulers
+ // Luminol start - Level schedulers
+ final boolean waitResult = allLevelSchedulersExited && this.scheduler.join(maxWaitNS == 0L ? 0L : Math.max(1L, TimeUnit.NANOSECONDS.toMillis(maxWaitNS)));
+
+ this.handleParentSchedulerDead();
+
+ return waitResult;
+ // Luminol - end
}
void dumpAliveThreadTraces(final String reason) {
@@ -251,7 +323,15 @@ public final class TickRegionScheduler {
}
public void setHasTasks(final RegionScheduleHandle region) {
+ //this.scheduler.notifyTasks(region); // Luminol - Level schedulers
+ // Luminol start - Level schedulers
+ if (region.region != null) {
+ region.region.world.levelScheduler.notifyTasks(region);
+ return;
+ }
+
this.scheduler.notifyTasks(region);
+ // Luminol end
}
private void uncaughtException(final Thread thread, final Throwable thr) {
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 88ccf32b3c1b3506fb76be5ae520168b6b167aa3..8bc18bf35537d5bbf2dacef34886b9a95d44a340 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -236,7 +236,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
private String localIp;
private int port = -1;
private final LayeredRegistryAccess<RegistryLayer> registries;
- private Map<ResourceKey<Level>, ServerLevel> levels = Maps.newLinkedHashMap();
+ private volatile Map<ResourceKey<Level>, ServerLevel> levels = Maps.newLinkedHashMap(); // Luminol - World hot load/unload apis
private PlayerList playerList;
private volatile boolean running = true;
private volatile boolean isRestarting = false; // Paper - flag to signify we're attempting to restart
@@ -2047,14 +2047,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
// CraftBukkit start
- public void addLevel(ServerLevel level) {
+ public synchronized void addLevel(ServerLevel level) { // Luminol - World hot load/unload apis
Map<ResourceKey<Level>, ServerLevel> oldLevels = this.levels;
Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
newLevels.put(level.dimension(), level);
this.levels = Collections.unmodifiableMap(newLevels);
}
- public void removeLevel(ServerLevel level) {
+ public synchronized void removeLevel(ServerLevel level) { // Luminol - World hot load/unload apis
Map<ResourceKey<Level>, ServerLevel> oldLevels = this.levels;
Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
newLevels.remove(level.dimension());
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 6950fa2462353f30ee02e1d0091cfcf289f63939..def5d4ce012dba5f819680d671235173467bfe7a 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -221,6 +221,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
private final StructureCheck structureCheck;
public final boolean tickTime; // Folia - region threading
private final LevelDebugSynchronizers debugSynchronizers = new LevelDebugSynchronizers(this);
+ // Luminol start - Level schedulers
+ private static final java.util.concurrent.atomic.AtomicInteger UNLOADER_ID_GEN = new java.util.concurrent.atomic.AtomicInteger(0);
+ public final me.earthme.luminol.utils.thread.LevelAwareRegionScheduler levelScheduler = new me.earthme.luminol.utils.thread.LevelAwareRegionScheduler(io.papermc.paper.threadedregions.TickRegions.getScheduler().scheduler);
+ public final me.earthme.luminol.utils.thread.SimpleReferenceRWLock levelUnloadStateLock = new me.earthme.luminol.utils.thread.SimpleReferenceRWLock();
+ public final me.earthme.luminol.utils.thread.LevelSubRegionShutdownThread levelUnloader = new me.earthme.luminol.utils.thread.LevelSubRegionShutdownThread("Level Unload Thread - " + UNLOADER_ID_GEN.getAndIncrement(), this);
+ // Luminol end
// CraftBukkit start
private final ResourceKey<LevelStem> typeKey;
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 0802483484015fd92dec211aae9fd33e45621f74..4c9915087798f6548bb22574051c7b0b5aed7732 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -1694,7 +1694,30 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
ServerLevel origin = this.level();
ServerPlayer.RespawnConfig respawnConfig = this.getRespawnConfig();
- ServerLevel respawnWorld = this.server.getLevel(ServerPlayer.RespawnConfig.getDimensionOrDefault(respawnConfig));
+ ServerLevel respawnWorldLookup = this.server.getLevel(ServerPlayer.RespawnConfig.getDimensionOrDefault(respawnConfig)); // Luminol - Level hot unload apis
+ // Luminol start - Level hot unload apis
+ var toLockRelease = (ServerLevel)null;
+ if (respawnWorldLookup != null) {
+ if (respawnWorldLookup.levelUnloadStateLock.acquireRead()) {
+ toLockRelease = respawnWorldLookup;
+ } else {
+ respawnWorldLookup = null;
+ }
+ }
+
+ final ServerLevel respawnWorld = respawnWorldLookup;
+ final ServerLevel finalToLockRelease = toLockRelease;
+
+ var finalRespawnCompleteCallback = (java.util.function.Consumer<ServerPlayer>) player -> {
+ if (finalToLockRelease != null) {
+ finalToLockRelease.levelUnloadStateLock.releaseRead();
+ }
+
+ if (respawnComplete != null) {
+ respawnComplete.accept(player);
+ }
+ };
+ // Luminol end
// modified based off PlayerList#respawn
@@ -1745,8 +1768,8 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
// now the respawn logic is complete
// last, call the function callback
- if (respawnComplete != null) {
- respawnComplete.accept(ServerPlayer.this);
+ if (finalRespawnCompleteCallback != null) { // Luminol - Level hot unload apis
+ finalRespawnCompleteCallback.accept(ServerPlayer.this); // Luminol - Level hot unload apis
}
}
);
diff --git a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
index 111789b4aca281dd1899dfbcc4e7e0ca36d74b51..2d3b0243f061aeaa979ddfc89c0361ecc29edcde 100644
--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
@@ -219,8 +219,23 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis
this.switchToMain = serverPlayer;
// now the connection responsibility is transferred to the region
- final net.minecraft.server.level.ServerLevel world = this.prepareSpawnTask.getSpawnWorld();
+ net.minecraft.server.level.ServerLevel worldLookup = this.prepareSpawnTask.getSpawnWorld(); // Luminol - Level hot unload apis - world -> worldLookup, make mutable
final net.minecraft.world.level.ChunkPos chunkPos = net.minecraft.world.level.ChunkPos.containing(net.minecraft.core.BlockPos.containing(this.prepareSpawnTask.getSpawnPosition()));
+ // Luminol start - Level hot unload apis
+ if (!worldLookup.levelUnloadStateLock.acquireRead()) {
+ LOGGER.warn("Failed to acquire read reference for level {} for player placement, sending back to overworld!", worldLookup.dimension());
+
+ worldLookup = net.minecraft.server.MinecraftServer.getServer().overworld();
+ if (!worldLookup.levelUnloadStateLock.acquireRead()) {
+ throw new java.lang.IllegalStateException("Reference read for overworld should be always acquirable!");
+ }
+
+ serverPlayer.setServerLevel(worldLookup);
+ }
+ var world = worldLookup;
+ // note: the following logics would always be successfully executed, otherwise the server go crashed
+ // so do not care the exception handling of the callbacks following
+ // Luminol end
world.moonrise$getChunkTaskScheduler().scheduleTickingState(
chunkPos.x(), chunkPos.z(), net.minecraft.server.level.FullChunkStatus.ENTITY_TICKING, true,
ca.spottedleaf.concurrentutil.util.Priority.HIGHER,
@@ -232,9 +247,15 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis
);
io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
world, chunkPos.x(), chunkPos.z(), () -> {
+ try { // Luminol - Level hot unload apis
net.minecraft.server.network.ServerConfigurationPacketListenerImpl.this.prepareSpawnTask.spawnPlayer(
this.connection, commonListenerCookie, serverPlayer
);
+ // Luminol start - Level hot unload apis
+ } finally {
+ world.levelUnloadStateLock.releaseRead();
+ }
+ // Luminol end
}
);
}
diff --git a/net/minecraft/server/network/config/PrepareSpawnTask.java b/net/minecraft/server/network/config/PrepareSpawnTask.java
index 9fad06eda81b4c2c1889a9cfa7a6948e7ee01862..854f8954ccfd8c34c9f9c91fd1805260f9b429b5 100644
--- a/net/minecraft/server/network/config/PrepareSpawnTask.java
+++ b/net/minecraft/server/network/config/PrepareSpawnTask.java
@@ -104,6 +104,16 @@ public class PrepareSpawnTask implements ConfigurationTask {
spawnLevel = spawnDataLevel;
}
}
+ // Luminol start - Level hot unload apis
+ if (!spawnLevel.levelUnloadStateLock.acquireRead()) {
+ spawnLevel = spawnDataLevel;
+
+ // the apis don't allow devs to unload overworld, so just simply do a check withoud fail-processing logics on it
+ if (!spawnLevel.levelUnloadStateLock.acquireRead()) {
+ throw new java.lang.IllegalStateException("Default level should be acquirable!");
+ }
+ }
+ // Luminol end
// Folia start - region threading
CompletableFuture<Vec3> spawnPosition = new java.util.concurrent.CompletableFuture<>();
if (loadedPosition.position().isPresent()) {
@@ -123,6 +133,10 @@ public class PrepareSpawnTask implements ConfigurationTask {
}
// Folia end - region threading
// Paper end - move logic in Entity to here, to use bukkit supplied world UUID & reset to main world spawn if no valid world is found
+ // Luminol start - Level hot unload apis
+ final ServerLevel finalSpawnLevel = spawnLevel;
+ spawnPosition.whenComplete((_, _) -> finalSpawnLevel.levelUnloadStateLock.releaseRead());
+ // Luminol end
Vec2 spawnAngle = loadedPosition.rotation().orElse(new Vec2(respawnData.yaw(), respawnData.pitch()));
this.state = new PrepareSpawnTask.Preparing(spawnLevel, spawnPosition, spawnAngle);
}
@@ -272,9 +286,20 @@ public class PrepareSpawnTask implements ConfigurationTask {
this.spawnPosition = CompletableFuture.completedFuture(spawnPosition);
this.spawnAngle = new Vec2(location.getYaw(), location.getPitch());
}
+ // Luminol start - Level hot unload apis
+ if (!this.spawnLevel.levelUnloadStateLock.acquireRead()) {
+ LOGGER.warn("Could not fetch read reference lock for level {}! Falling back to overworld!", this.spawnLevel);
+
+ this.spawnLevel = net.minecraft.server.MinecraftServer.getServer().overworld();
+ if (!this.spawnLevel.levelUnloadStateLock.acquireRead()) {
+ throw new java.lang.IllegalStateException();
+ }
+ }
+ // Luminol end
// Paper end - PlayerSpawnLocationEvent
ChunkPos spawnChunk = ChunkPos.containing(BlockPos.containing(spawnPosition));
this.chunkLoadFuture = ((ca.spottedleaf.moonrise.patches.chunk_system.MoonriseChunkLoadCounter)this.chunkLoadCounter).trackLoadWithRadius(this.spawnLevel, spawnChunk, 3, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, ca.spottedleaf.concurrentutil.util.Priority.HIGH, () -> { Preparing.this.spawnLevel.getChunkSource().addTicketWithRadius(TicketType.PLAYER_SPAWN, spawnChunk, 3); }); // Paper - rewrite chunk system
+ this.chunkLoadFuture.whenComplete((_,_) -> this.spawnLevel.levelUnloadStateLock.releaseRead()); // Luminol - Level hot unload apis
PrepareSpawnTask.this.loadListener.start(LevelLoadListener.Stage.LOAD_PLAYER_CHUNKS, this.chunkLoadCounter.totalChunks());
PrepareSpawnTask.this.loadListener.updateFocus(this.spawnLevel.dimension(), spawnChunk);
}
@@ -291,7 +316,7 @@ public class PrepareSpawnTask implements ConfigurationTask {
}
private final class Ready implements PrepareSpawnTask.State {
- private final ServerLevel spawnLevel;
+ private ServerLevel spawnLevel; // Luminol - Level hot unloaad apis - Make public
private final Vec3 spawnPosition;
private final Vec2 spawnAngle;
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 33c9692a59c58c44a77b1077553e2bda88cb8305..e8047a06a2df17fde9965508c1ebcf1fc4d25989 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -392,6 +392,9 @@ public abstract class Entity
public void inactiveTick() {
}
// Paper end - EAR 2
+ // Luminol start - Level hot unload apis
+ public me.earthme.luminol.utils.TeleportRecord lastTeleportRecord;
+ // Luminol end
// CraftBukkit end
// Paper start
@@ -4534,6 +4537,17 @@ public abstract class Entity
}
}
+ // Luminol start - Level hot unload apis
+ if (!destination.levelUnloadStateLock.acquireRead()) {
+ return false;
+ }
+ final java.util.function.Consumer<Entity> finalTeleportComplete = entity -> {
+ destination.levelUnloadStateLock.releaseRead();
+
+ if (teleportComplete != null) teleportComplete.accept(entity);
+ };
+ // Luminol end
+
// TODO any events that can modify go HERE
// check for same region
@@ -4552,6 +4566,14 @@ public abstract class Entity
}
for (EntityTreeNode entity : passengerTree.getFullTree()) {
+ // Luminol start - Level hot unload apis
+ entity.root.lastTeleportRecord = me.earthme.luminol.utils.TeleportRecord.create(
+ (ServerLevel) entity.root.level,
+ entity.root.position,
+ entity.root.yRot,
+ entity.root.xRot
+ );
+ // Luminol end
entity.root.teleportSyncSameRegion(pos, yaw, pitch, velocity);
}
@@ -4567,8 +4589,8 @@ public abstract class Entity
// performs add/remove from world logic which will also perform add/remove tracker logic
}
- if (teleportComplete != null) {
- teleportComplete.accept(this);
+ if (finalTeleportComplete != null) { // Luminol - Level hot unload apis
+ finalTeleportComplete.accept(this); // Luminol - Level hot unload apis
}
return true;
}
@@ -4585,7 +4607,7 @@ public abstract class Entity
node.root = node.root.transformForAsyncTeleport(destination, pos, yaw, pitch, velocity);
}
- passengerTree.root.placeInAsync(originWorld, destination, teleportFlags, passengerTree, teleportComplete);
+ passengerTree.root.placeInAsync(originWorld, destination, teleportFlags, passengerTree, finalTeleportComplete); // Luminol - Level hot unload apis
return true;
}
@@ -4864,6 +4886,13 @@ public abstract class Entity
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(initialPosition),
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(initialPosition)
);
+ // Luminol start - Level hot load/unload apis
+ // let's acquire read reference first
+ if (!destination.levelUnloadStateLock.acquireRead()) {
+ // failed, destination is under unloading process
+ return false;
+ }
+ // Luminol end
// first, remove entity/passengers from world
EntityTreeNode passengerTree = this.detachPassengers();
@@ -4921,6 +4950,10 @@ public abstract class Entity
if (info.postTeleportTransition() != null) {
info.postTeleportTransition().onTransition(teleported);
}
+ // Luminol start - Level hot load/unload apis
+ // all done, release the read reference
+ destination.levelUnloadStateLock.releaseRead();
+ // Luminol end
if (teleportComplete != null) {
teleportComplete.accept(teleported);
@@ -0,0 +1,23 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Bacteriawa <A3167717663@hotmail.com>
Date: Tue, 21 Apr 2026 01:32:38 +0800
Subject: [PATCH] Kaiiju: Do not pathfind outside region
Co-authored by: Sofiane H. Djerbi <46628754+kugge@users.noreply.github.com>
As part of: Kaiiju (https://github.com/KaiijuMC/Kaiiju/blob/c2b7aec8f7b418a39a2ec408e6411e6f752379da/patches/server/0028-Don-t-pathfind-outside-region.patch)
Licensed under: GPL-3.0 (https://github.com/KaiijuMC/Kaiiju/blob/c2b7aec8f7b418a39a2ec408e6411e6f752379da/LICENSE)
diff --git a/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java b/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java
index b40da004b5281a041ee896ae176bfb9c4660f353..ffb8a725c41d49a0f0e023ab37bb77062d67eccc 100644
--- a/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java
+++ b/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java
@@ -117,7 +117,9 @@ public class MoveToTargetSink extends Behavior<Mob> {
private boolean tryComputePath(final Mob body, final WalkTarget walkTarget, final long timestamp) {
BlockPos targetPos = walkTarget.getTarget().currentBlockPosition();
+ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(body.level(), targetPos)) // Kaiiju - Don't pathfind outside region
this.path = body.getNavigation().createPath(targetPos, 0);
+ else this.path = null; // Kaiiju - Don't pathfind outside region
this.speedModifier = walkTarget.getSpeedModifier();
Brain<?> brain = body.getBrain();
if (this.reachedTarget(body, walkTarget)) {
@@ -0,0 +1,67 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Bacteriawa <A3167717663@hotmail.com>
Date: Sat, 8 Aug 2026 01:27:42 +0800
Subject: [PATCH] Kaiiju: Entity tick and removal limiter
Co-authored by: Xymb <xymb@endcrystal.me>
As part of: Kaiiju (https://github.com/KaiijuMC/Kaiiju/blob/c2b7aec8f7b418a39a2ec408e6411e6f752379da/patches/server/0021-Entity-ticking-throttling-removal-to-prevent-lag.patch)
Licensed under: GPL-3.0 (https://github.com/KaiijuMC/Kaiiju/blob/c2b7aec8f7b418a39a2ec408e6411e6f752379da/LICENSE)
diff --git a/io/papermc/paper/threadedregions/RegionizedWorldData.java b/io/papermc/paper/threadedregions/RegionizedWorldData.java
index c8f13da82c55cc5c1fe1c922a87e3eccb1117937..981458f6697a79dbc9fc8cf41c088c98e556c6dd 100644
--- a/io/papermc/paper/threadedregions/RegionizedWorldData.java
+++ b/io/papermc/paper/threadedregions/RegionizedWorldData.java
@@ -349,6 +349,7 @@ public final class RegionizedWorldData {
private final IteratorSafeOrderedReferenceSet<Mob> navigatingMobs = new IteratorSafeOrderedReferenceSet<>();
public ReferenceList<Entity> trackerEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker
public boolean iteratingTrackerEntities;
+ public final dev.kaiijumc.kaiiju.KaiijuEntityThrottler entityThrottler = new dev.kaiijumc.kaiiju.KaiijuEntityThrottler(); // Kaiiju
// block ticking
private final ObjectLinkedOpenHashSet<BlockEventData> blockEvents = new ObjectLinkedOpenHashSet<>();
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index def5d4ce012dba5f819680d671235173467bfe7a..1c01ec9597c47b7296f498a897f1fd2333c8c4d4 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -917,6 +917,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
foliaProfiler.startTimer(ca.spottedleaf.leafprofiler.LProfilerRegistry.ACTIVATE_ENTITIES); try { // Folia - profiler
+ if (dev.kaiijumc.kaiiju.KaiijuEntityLimits.enabled) regionizedWorldData.entityThrottler.tickLimiterStart(); // Kaiiju
io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR
} finally { foliaProfiler.stopTimer(ca.spottedleaf.leafprofiler.LProfilerRegistry.ACTIVATE_ENTITIES); } // Folia - profiler
foliaProfiler.startTimer(ca.spottedleaf.leafprofiler.LProfilerRegistry.ENTITY_TICK); try { // Folia - profiler
@@ -938,6 +939,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
entity.stopRiding();
}
+ // Kaiiju start
+ if (dev.kaiijumc.kaiiju.KaiijuEntityLimits.enabled) {
+ dev.kaiijumc.kaiiju.KaiijuEntityThrottler.EntityThrottlerReturn throttle = regionizedWorldData.entityThrottler.tickLimiterShouldSkip(entity);
+ if (throttle.remove && !entity.hasCustomName()) entity.remove(Entity.RemovalReason.DISCARDED);
+ if (throttle.skip) return;
+ }
+ // Kaiiju end
profiler.push("tick");
this.guardEntityTick(this::tickNonPassenger, entity);
@@ -947,6 +955,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
}
);
+ if (dev.kaiijumc.kaiiju.KaiijuEntityLimits.enabled) regionizedWorldData.entityThrottler.tickLimiterFinish(regionizedWorldData); // Kaiiju
} finally { foliaProfiler.stopTimer(ca.spottedleaf.leafprofiler.LProfilerRegistry.ENTITY_TICK); } // Folia - profiler
if (this.paperConfig().unsupportedSettings.ticking.blockEntities) { // Paper - option to disable ticking
profiler.popPush("blockEntities");
diff --git a/net/minecraft/world/entity/EntityType.java b/net/minecraft/world/entity/EntityType.java
index 6efaa78faca15c7a48bdffb24480d1bac29f3ba8..31619890f1bd4f458b6f9a31fac8cefaafe09e8e 100644
--- a/net/minecraft/world/entity/EntityType.java
+++ b/net/minecraft/world/entity/EntityType.java
@@ -73,6 +73,7 @@ public class EntityType<T extends Entity> implements EntityTypeTest<Entity, T>,
private final float spawnDimensionsScale;
private final FeatureFlagSet requiredFeatures;
private final boolean allowedInPeaceful;
+ public volatile dev.kaiijumc.kaiiju.KaiijuEntityLimits.EntityLimit entityLimit;
public static Identifier getKey(final EntityType<?> type) {
return BuiltInRegistries.ENTITY_TYPE.getKey(type);
@@ -0,0 +1,82 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Sat, 18 Apr 2026 09:21:31 +0800
Subject: [PATCH] Kaiiju: Vanilla end portal teleportation
Co-authored by: Sofiane H. Djerbi <46628754+kugge@users.noreply.github.com>
As part of: Kaiiju (https://github.com/KaiijuMC/Kaiiju/blob/c2b7aec8f7b418a39a2ec408e6411e6f752379da/patches/server/0024-Vanilla-end-portal-teleportation.patch)
Licensed under: GPL-3.0 (https://github.com/KaiijuMC/Kaiiju/blob/c2b7aec8f7b418a39a2ec408e6411e6f752379da/LICENSE)
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index e8047a06a2df17fde9965508c1ebcf1fc4d25989..3b98ef4310d9f996437af9f12ab3de2cf6f4dd63 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -4672,15 +4672,19 @@ public abstract class Entity
targetPos, 16, // load 16 blocks to be safe from block physics
ca.spottedleaf.concurrentutil.util.Priority.HIGH,
(chunks) -> {
- net.minecraft.world.level.levelgen.feature.EndPlatformFeature.createEndPlatform(destination, targetPos.below(), true, null);
-
+ //net.minecraft.world.level.levelgen.feature.EndPlatformFeature.createEndPlatform(destination, targetPos.below(), true, null); // Kaiiju - Vanilla end teleportation - moved down
+ // Kaiiju start - Vanilla end teleportation
+ Vec3 finalPos;
+ if (this instanceof Player) finalPos = Vec3.atBottomCenterOf(targetPos.below());
+ else finalPos = Vec3.atBottomCenterOf(targetPos);
+ // Kaiiju end
// the portal obsidian is placed at targetPos.y - 2, so if we want to place the entity
// on the obsidian, we need to spawn at targetPos.y - 1
portalInfoCompletable.complete(
new net.minecraft.world.level.portal.TeleportTransition(
- destination, Vec3.atBottomCenterOf(targetPos.below()), Vec3.ZERO, Direction.WEST.toYRot(), 0.0f,
+ destination, finalPos, this.getDeltaMovement(), Direction.WEST.toYRot(), 0.0f, // Kaiiju - Vanilla end teleportation
false, false,
- Relative.union(Relative.DELTA, Set.of(Relative.X_ROT)),
+ /*Relative.union(Relative.DELTA, Set.of(Relative.X_ROT))*/Set.of(), // Kaiiju - Vanilla end teleportation
TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET),
org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_PORTAL,
TeleportTransition.PassengerTeleportationMode.POSITION_RIDER
@@ -4696,11 +4700,15 @@ public abstract class Entity
ca.spottedleaf.concurrentutil.util.Priority.HIGH,
(chunks) -> {
BlockPos adjustedSpawn = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, spawnPos);
-
+ // Kaiiju start - Vanilla end teleportation
+ Vec3 finalPos;
+ if (this instanceof Player) finalPos = Vec3.atBottomCenterOf(adjustedSpawn.below());
+ else finalPos = Vec3.atBottomCenterOf(adjustedSpawn);
+ // Kaiiju end
// done
portalInfoCompletable.complete(
new net.minecraft.world.level.portal.TeleportTransition(
- destination, Vec3.atBottomCenterOf(adjustedSpawn), Vec3.ZERO, 0.0f, 0.0f,
+ destination, finalPos, this.getDeltaMovement(), 0.0f, 0.0f, // Kaiiju - Vanilla end teleportation
false, false,
Relative.union(Relative.DELTA, Relative.ROTATION),
TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET),
@@ -4881,6 +4889,10 @@ public abstract class Entity
return false;
}
+ // Kaiiju start - sync end platform spawning & entity teleportation
+ final java.util.function.Consumer<Entity> tpComplete = type == PortalType.END && destination.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.END ?
+ e -> {net.minecraft.world.level.levelgen.feature.EndPlatformFeature.createEndPlatform(destination, ServerLevel.END_SPAWN_POINT.below(), true, null); if (teleportComplete != null) {teleportComplete.accept(e);}} : teleportComplete;
+ // Kaiiju end
Vec3 initialPosition = this.position();
ChunkPos initialPositionChunk = new ChunkPos(
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(initialPosition),
@@ -4955,9 +4967,14 @@ public abstract class Entity
destination.levelUnloadStateLock.releaseRead();
// Luminol end
- if (teleportComplete != null) {
+ // Kaiiju start - vanilla end teleportation
+ /*if (teleportComplete != null) {
teleportComplete.accept(teleported);
+ }*/
+ if (tpComplete != null){
+ tpComplete.accept(teleported);
}
+ // Kaiiju end
}
);
});
@@ -0,0 +1,288 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Bacteriawa <A3167717663@hotmail.com>
Date: Sat, 8 Aug 2026 01:29:57 +0800
Subject: [PATCH] Async protocol switching optimization
diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
index 40f54536422fa38bd84017fc634808016b4de58b..ecaa296f576c5c90fda89e0406d6552628ef5de3 100644
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -1067,4 +1067,127 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
}
}
// Paper end - Optimize network
+ // Luminol start - async protocol switcher
+ public <T extends PacketListener> void setupInboundProtocolAsync(
+ ProtocolInfo<T> protocol,
+ T packetListener,
+ @Nullable Runnable callback,
+ boolean resumeAutoReading
+ ) {
+ this.validateListener(protocol, packetListener);
+ if (protocol.flow() != this.getReceiving()) {
+ throw new IllegalStateException("Invalid inbound protocol: " + protocol.id());
+ } else {
+ this.packetListener = packetListener;
+ this.disconnectListener = null;
+
+ UnconfiguredPipelineHandler.InboundConfigurationTask configMessage = UnconfiguredPipelineHandler.setupInboundProtocol(protocol);
+ BundlerInfo bundlerInfo = protocol.bundlerInfo();
+ if (bundlerInfo != null) {
+ PacketBundlePacker newBundler = new PacketBundlePacker(bundlerInfo);
+ configMessage = configMessage.andThen(context -> context.pipeline().addAfter("decoder", "bundler", newBundler));
+ }
+
+ // Here we could execute it in event loop async to prevent waiting io task on main
+ // stop reading new packets to prevent some packets came into pipeline too early
+ this.channel.config().setAutoRead(false);
+
+ // do our configuration task
+ final UnconfiguredPipelineHandler.InboundConfigurationTask finalInboundConfigurationTask = configMessage;
+ Runnable toExecute = () -> this.channel.writeAndFlush(finalInboundConfigurationTask).addListener(future -> {
+ try {
+ if (future.isSuccess()) {
+ if (callback != null) callback.run(); // retire callback if there have one
+ return;
+ }
+
+ final Throwable ex = future.cause();
+
+ // here we process our exceptions like that blocking one
+ if (ex instanceof ClosedChannelException) {
+ LOGGER.info("Connection closed during protocol change");
+ } else {
+ this.channel.pipeline().fireExceptionCaught(ex);
+ }
+ }finally {
+ // reset auto back and resume reading if needed
+ if (resumeAutoReading) {
+ this.channel.config().setAutoRead(true);
+ this.channel.read();
+ }
+ }
+ });
+
+ // we need to do this inside the event loop
+ if (!this.channel.eventLoop().inEventLoop()) {
+ this.channel.eventLoop().execute(toExecute);
+ return;
+ }
+
+ toExecute.run();
+ }
+ }
+
+ public void setupOutboundProtocolAsync(
+ ProtocolInfo<?> protocol,
+ @Nullable Runnable callback,
+ boolean resumeAutoReading
+ ) {
+ if (protocol.flow() != this.getSending()) {
+ throw new IllegalStateException("Invalid outbound protocol: " + protocol.id());
+ } else {
+ UnconfiguredPipelineHandler.OutboundConfigurationTask configMessage = UnconfiguredPipelineHandler.setupOutboundProtocol(protocol);
+ BundlerInfo bundlerInfo = protocol.bundlerInfo();
+ if (bundlerInfo != null) {
+ PacketBundleUnpacker newUnbundler = new PacketBundleUnpacker(bundlerInfo);
+ configMessage = configMessage.andThen(
+ context -> context.pipeline().addAfter("encoder", "unbundler", newUnbundler)
+ );
+ }
+
+ boolean isLoginProtocol = protocol.id() == ConnectionProtocol.LOGIN;
+
+ // Here we could execute it in event loop async to prevent waiting io task on main
+ // stop reading new packets to prevent some packets came into pipeline too early
+ this.channel.config().setAutoRead(false);
+
+ // do our configuration task
+ final UnconfiguredPipelineHandler.OutboundConfigurationTask finalOutboundConfigurationTask = configMessage;
+ final Runnable writeTask = () -> this.channel.writeAndFlush(
+ finalOutboundConfigurationTask.andThen(context -> this.sendLoginDisconnect = isLoginProtocol)
+ ).addListener(future -> {
+ try {
+ if (future.isSuccess()) {
+ if (callback != null) callback.run(); // retire callback if there have one
+ return;
+ }
+
+ final Throwable ex = future.cause();
+
+ // here we process our exceptions like that blocking one
+ if (ex instanceof ClosedChannelException) {
+ LOGGER.info("Connection closed during protocol change");
+ } else {
+ this.channel.pipeline().fireExceptionCaught(ex);
+ }
+ }finally {
+ // reset auto back and resume reading if needed
+ if (resumeAutoReading) {
+ this.channel.config().setAutoRead(true);
+ this.channel.read(); // read once
+ }
+ }
+ });
+
+ // we need to do this inside the event loop
+ if (!this.channel.eventLoop().inEventLoop()) {
+ this.channel.eventLoop().execute(writeTask);
+ return;
+ }
+
+ writeTask.run();
+ }
+ }
+ // Luminol end
+
}
diff --git a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
index 2d3b0243f061aeaa979ddfc89c0361ecc29edcde..e074fe9eb6a1e6cb30b7de522fcac673dcb81960 100644
--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
@@ -197,8 +197,9 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis
public void handleConfigurationFinished(final ServerboundFinishConfigurationPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.server.packetProcessor());
this.finishCurrentTask(JoinWorldTask.TYPE);
- this.connection.setupOutboundProtocol(GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess())));
+ // this.connection.setupOutboundProtocol(GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess()))); // Luminol - Async protocol switch - move down
+ Runnable afterSwitch = () -> { // Luminol - Async protocol switch
try {
PlayerList playerList = this.server.getPlayerList();
if (playerList.getPlayer(this.gameProfile.id()) != null) {
@@ -265,6 +266,18 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis
LOGGER.error("Couldn't place player in world", e);
this.disconnect(DISCONNECT_REASON_INVALID_DATA);
}
+ // Luminol start - Async protocol switch
+ };
+ if (!me.earthme.luminol.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) {
+ this.connection.setupOutboundProtocol(GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess())));
+ afterSwitch.run(); // directly run callback as we won't process any packet this time
+ } else {
+ this.connection.setupOutboundProtocolAsync(GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess())), () -> {
+ // push back
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(afterSwitch);
+ }, false); // we will start auto read once we set up inbound handler at placeNewPlayer in PlayerList
+ }
+ // Luminol end
}
@Override
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 9be6d486082d344fb91fd74207eee83ca2f8e869..40b409ad07eaa03473712ba83bc4ccd9a5c0f932 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -2902,7 +2902,13 @@ public class ServerGamePacketListenerImpl
} // Folia end - rewrite login process - move connection ownership to global region
this.waitingForSwitchToConfig = true; // Folia - rewrite login process - fix bad ordering of this field write - moved down
this.send(ClientboundStartConfigurationPacket.INSTANCE);
+ if (!me.earthme.luminol.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { // Luminol - Async protocol switch
this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND);
+ // Luminol start - Async protcol switch
+ } else {
+ this.connection.setupOutboundProtocolAsync(ConfigurationProtocols.CLIENTBOUND, null, true);
+ }
+ // Luminol end
}
@Override
@@ -3787,12 +3793,26 @@ public class ServerGamePacketListenerImpl
}
final ServerConfigurationPacketListenerImpl listener = new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation())); // Paper
+ if (!me.earthme.luminol.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { // Luminol - Async protocol switch
this.connection
.setupInboundProtocol(
ConfigurationProtocols.SERVERBOUND,
listener // Paper
);
new io.papermc.paper.event.connection.configuration.PlayerConnectionReconfigureEvent(listener.paperConnection).callEvent(); // Paper
+ } // Luminol - Async protocol switch - add "{"
+ // Luminol start - Async protocol switch - move up
+ else
+ this.connection.setupInboundProtocolAsync(
+ ConfigurationProtocols.SERVERBOUND,
+ listener,
+ () -> {
+ new io.papermc.paper.event.connection.configuration.PlayerConnectionReconfigureEvent(listener.paperConnection).callEvent(); // Paper
+ },
+ true
+ );
+ // Luminol end
+ // new io.papermc.paper.event.connection.configuration.PlayerConnectionReconfigureEvent(listener.paperConnection).callEvent(); // Paper // Luminol - Async protocol switch - move up
}
@Override
diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
index f2329d1a9d9d4bf4c9d771e25e54f2f9ef65a76c..deee051c103f9797f4612e06c12f293ec54724ac 100644
--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
@@ -435,12 +435,30 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
public void handleLoginAcknowledgement(final ServerboundLoginAcknowledgedPacket packet) {
net.minecraft.network.protocol.PacketUtils.ensureRunningOnSameThread(packet, this, this.server.packetProcessor()); // CraftBukkit
Validate.validState(this.state == ServerLoginPacketListenerImpl.State.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet");
- this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND);
+ /*this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); // Luminol - Async protocol switch - Rewrite
CommonListenerCookie cookie = CommonListenerCookie.createInitial(Objects.requireNonNull(this.authenticatedProfile), this.transferred);
ServerConfigurationPacketListenerImpl configPacketListener = new ServerConfigurationPacketListenerImpl(this.server, this.connection, cookie);
this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, configPacketListener);
configPacketListener.startConfiguration();
- this.state = ServerLoginPacketListenerImpl.State.ACCEPTED;
+ this.state = ServerLoginPacketListenerImpl.State.ACCEPTED;*/ // Luminol - Async protocol switch - Rewrite
+
+ // Luminol start - Async protocol switch
+ CommonListenerCookie cookie = CommonListenerCookie.createInitial(Objects.requireNonNull(this.authenticatedProfile), this.transferred);
+ ServerConfigurationPacketListenerImpl configPacketListener = new ServerConfigurationPacketListenerImpl(this.server, this.connection, cookie);
+
+ Runnable afterSwitch = () -> io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(configPacketListener::startConfiguration); // push back to main thread
+
+ if (!me.earthme.luminol.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) {
+ this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND);
+ this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, configPacketListener);
+ afterSwitch.run();
+ return;
+ }
+
+ this.connection.setupInboundProtocolAsync(ConfigurationProtocols.SERVERBOUND, configPacketListener, () -> {
+ this.connection.setupOutboundProtocolAsync(ConfigurationProtocols.CLIENTBOUND, afterSwitch, true); // start auto read when everything is ready
+ }, false); // we will resume auto reading once the outbound protocol is also setup
+ // Luminol end
}
@Override
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index 259eb7574841b1b885093b534440f8a40db4e8e2..2975a879320faed5f6591489f86d3f909072cbaa 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -237,9 +237,11 @@ public abstract class PlayerList {
// only after setting the connection listener to game type, add the connection to this regions list
level.getCurrentWorldData().addConnection(player);
// Folia end - rewrite login process
+ if (!me.earthme.luminol.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { // Luminol - Async protocol switch // we will run async switch once these main thread logics became done
connection.setupInboundProtocol(
GameProtocols.SERVERBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess()), playerConnection), playerConnection
);
+ } // Luminol - Async protocol switch
playerConnection.suspendFlushing();
GameRules gameRules = level.getGameRules();
boolean immediateRespawn = gameRules.get(GameRules.IMMEDIATE_RESPAWN);
@@ -403,6 +405,17 @@ public abstract class PlayerList {
);
}
// Paper end - Send empty chunk
+ // Luminol start - Async protocol switch
+ if (me.earthme.luminol.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) {
+ // auto read will be enabled once the async switch is done
+ connection.setupInboundProtocolAsync(
+ GameProtocols.SERVERBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess()), playerConnection),
+ playerConnection,
+ null, // we don't need to do anything more
+ true // start auto read which we have disabled in configuration handler
+ );
+ }
+ // Luminol end
}
public void updateEntireScoreboard(final ServerScoreboard scoreboard, final ServerPlayer player) {
@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Sat, 18 Apr 2026 09:08:52 +0800
Subject: [PATCH] Add config for server mod name
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 8bc18bf35537d5bbf2dacef34886b9a95d44a340..daf45761bdc88848ba81b7595b5abd5635dfd607 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -2085,7 +2085,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
public String getServerModName() {
- return io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); // Paper
+ return me.earthme.luminol.config.modules.misc.ServerModNameConfig.fakeVanilla ? "vanilla" : me.earthme.luminol.config.modules.misc.ServerModNameConfig.serverModName; // Paper // Luminol - Add config for server mod name
}
public ServerClockManager clockManager() {
@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Sat, 18 Apr 2026 09:09:33 +0800
Subject: [PATCH] Add config for offline mode warning
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
index 1145d5839e621a02b7972c2ff1142f51db2237de..81934fb56744dbbb3fdd100e8ec83cb634034d04 100644
--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -304,7 +304,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
String proxyFlavor = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "Velocity" : "BungeeCord";
String proxyLink = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "https://docs.papermc.io/velocity/security" : "http://www.spigotmc.org/wiki/firewall-guide/";
// Paper end - Add Velocity IP Forwarding Support
- if (!this.usesAuthentication()) {
+ if (!this.usesAuthentication() && !me.earthme.luminol.config.modules.misc.DisableWarningConfig.disableOfflineModeWarning) { //Luminol - Add config for offline mod warning
LOGGER.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!");
LOGGER.warn("The server will make no attempt to authenticate usernames. Beware.");
// Spigot start
@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Fri, 17 Apr 2026 22:47:34 +0800
Subject: [PATCH] Add config for cpu affinity
diff --git a/io/papermc/paper/threadedregions/TickRegionScheduler.java b/io/papermc/paper/threadedregions/TickRegionScheduler.java
index f8344f640f119e3865f2718d1abd0ede9bdb8aa8..58e557923dfe6cee39ec45e7f5aa43a5ded9f107 100644
--- a/io/papermc/paper/threadedregions/TickRegionScheduler.java
+++ b/io/papermc/paper/threadedregions/TickRegionScheduler.java
@@ -66,7 +66,7 @@ public final class TickRegionScheduler {
@Override
public Thread newThread(final Runnable run) {
- final Thread ret = new TickThreadRunner(this.threadGroup, run, "Folia Region Scheduler Thread #" + this.idGenerator.getAndIncrement());
+ final Thread ret = new TickThreadRunner(this.threadGroup, me.earthme.luminol.config.modules.optimizations.CpuAffinityConfig.wrapForTickRegion(run), "Folia Region Scheduler Thread #" + this.idGenerator.getAndIncrement()); // Luminol - cpu affinity
ret.setUncaughtExceptionHandler(TickRegionScheduler.this::uncaughtException);
return ret;
}
@@ -0,0 +1,45 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Bacteriawa <A3167717663@hotmail.com>
Date: Tue, 21 Apr 2026 01:47:51 +0800
Subject: [PATCH] Add config for unsafe teleportation
diff --git a/net/minecraft/world/entity/item/FallingBlockEntity.java b/net/minecraft/world/entity/item/FallingBlockEntity.java
index 480b6b2405706fd4d7158d0e160adafd84099887..137389f7ee97fc3a15c8f5f87c058065af91264e 100644
--- a/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -70,7 +70,7 @@ public class FallingBlockEntity extends Entity {
public int fallDamageMax = 40;
public float fallDamagePerDistance = 0.0F;
public @Nullable CompoundTag blockData;
- public boolean forceTickAfterTeleportToDuplicate;
+ public boolean forceTickAfterTeleportToDuplicate = me.earthme.luminol.config.modules.fixes.UnsafeTeleportationConfig.enabled; // Luminol - Unsafe teleportation
protected static final EntityDataAccessor<BlockPos> DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS);
public boolean autoExpire = true; // Paper - Expand FallingBlock API
@@ -384,7 +384,7 @@ public class FallingBlockEntity extends Entity {
ResourceKey<Level> oldDimension = this.level().dimension();
boolean fromOrToEnd = (oldDimension == Level.END || newDimension == Level.END) && oldDimension != newDimension;
Entity newEntity = super.teleport(transition);
- this.forceTickAfterTeleportToDuplicate = newEntity != null && fromOrToEnd && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowUnsafeEndPortalTeleportation; // Paper
+ this.forceTickAfterTeleportToDuplicate = newEntity != null && fromOrToEnd && (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowUnsafeEndPortalTeleportation || me.earthme.luminol.config.modules.fixes.UnsafeTeleportationConfig.enabled); // Paper // Luminol - Unsafe teleportation
return newEntity;
}
}
diff --git a/net/minecraft/world/level/block/EndPortalBlock.java b/net/minecraft/world/level/block/EndPortalBlock.java
index 123df098fa987030fe3789f36ff95aeee2979834..35b0632faf2709f65f731cd42e575ee967937254 100644
--- a/net/minecraft/world/level/block/EndPortalBlock.java
+++ b/net/minecraft/world/level/block/EndPortalBlock.java
@@ -76,6 +76,12 @@ public class EndPortalBlock extends BaseEntityBlock implements Portal {
if (level.paperConfig().misc.disableEndCredits) {player.seenCredits = true; return;} // Paper - Option to disable end credits
player.showEndCredits();
} else {
+ // Luminol start - unsafe teleportation
+ if (me.earthme.luminol.config.modules.fixes.UnsafeTeleportationConfig.enabled && !(entity instanceof net.minecraft.world.entity.player.Player)) {
+ entity.endPortalLogicAsync(pos);
+ return;
+ }
+ // Luminol end
entity.setAsInsidePortal(this, pos);
}
}
@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 28 Jun 2026 20:04:06 +0800
Subject: [PATCH] Add config for vanilla random
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 3b98ef4310d9f996437af9f12ab3de2cf6f4dd63..a7f4a9f8bca00fda1b57e2245c57726bb1ce41ed 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -298,7 +298,7 @@ public abstract class Entity
public double yOld;
public double zOld;
public boolean noPhysics;
- protected final RandomSource random = SHARED_RANDOM; // Paper - Share random for entities to make them more random
+ protected final RandomSource random = me.earthme.luminol.config.modules.fixes.VanillaRandomSourceConfig.useLegacyRandomSourceForPlayers ? RandomSource.create() : SHARED_RANDOM; // Paper - Share random for entities to make them more random // Luminol - Add config for vanilla random SHARED_RANDOM
public int tickCount;
private int remainingFireTicks;
private final EntityFluidInteraction fluidInteraction = new EntityFluidInteraction(Set.of(FluidTags.WATER, FluidTags.LAVA));
@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Sat, 18 Apr 2026 09:12:26 +0800
Subject: [PATCH] Add config for watchdog timeout
diff --git a/io/papermc/paper/threadedregions/FoliaWatchdogThread.java b/io/papermc/paper/threadedregions/FoliaWatchdogThread.java
index e9ca1a15049b0211d10401cb78e953b93afaf6c7..fe1560579f9c9572082d52cf910f5916957f5601 100644
--- a/io/papermc/paper/threadedregions/FoliaWatchdogThread.java
+++ b/io/papermc/paper/threadedregions/FoliaWatchdogThread.java
@@ -65,7 +65,7 @@ public final class FoliaWatchdogThread extends Thread {
for (final RunningTick tick : ticks) {
final long elapsed = now - tick.lastPrint;
- if (elapsed <= TimeUnit.SECONDS.toNanos(5L)) {
+ if (elapsed <= TimeUnit.MILLISECONDS.toNanos(me.earthme.luminol.config.modules.misc.FoliaWatchogConfig.tickRegionTimeOutMs)) { // Luminol - Add config for watchdog timeout
continue;
}
tick.lastPrint = now;
@@ -0,0 +1,18 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 28 Jun 2026 20:10:51 +0800
Subject: [PATCH] Add config to disable entity tick catchers
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
index dc28223684091cd0d8dbf7ff01626b20e4f1b662..6fd7bfa561f71cfd043d7a1092d77b01bb320640 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -1591,6 +1591,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
), null, 1L);
// Luminol end
} catch (Throwable t) {
+ if (me.earthme.luminol.config.modules.experiment.DisableEntityCatchConfig.enabled) throw t; // Luminol
// Paper start - Prevent block entity and entity crashes
final String msg = String.format("Entity threw exception at %s:%s,%s,%s", io.papermc.paper.util.MCUtil.getLevelName(entity.level()), entity.getX(), entity.getY(), entity.getZ());
MinecraftServer.LOGGER.error(msg, t);
@@ -0,0 +1,21 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Sat, 18 Apr 2026 09:14:31 +0800
Subject: [PATCH] Add config for heightmap warning
diff --git a/net/minecraft/world/level/levelgen/Heightmap.java b/net/minecraft/world/level/levelgen/Heightmap.java
index dffa0d4859ecd7e21a9345f946083c56205145a3..21f0214992b82554c9532797cdc6fc593af1efe5 100644
--- a/net/minecraft/world/level/levelgen/Heightmap.java
+++ b/net/minecraft/world/level/levelgen/Heightmap.java
@@ -128,7 +128,9 @@ public class Heightmap {
if (rawData.length == data.length) {
System.arraycopy(data, 0, rawData, 0, data.length);
} else {
- LOGGER.warn("Ignoring heightmap data for chunk {}, size does not match; expected: {}, got: {}", chunk.getPos(), rawData.length, data.length);
+ // Luminol - Add config for heightmap warning
+ if (!me.earthme.luminol.config.modules.misc.DisableWarningConfig.disableHeightmapWarning)
+ LOGGER.warn("Ignoring heightmap data for chunk {}, size does not match; expected: {}, got: {}", chunk.getPos(), rawData.length, data.length); // Luminol - Add config for heightmap warning
primeHeightmaps(chunk, EnumSet.of(type));
}
}
@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Sat, 18 Apr 2026 09:15:04 +0800
Subject: [PATCH] Add config gui support
diff --git a/net/minecraft/server/dialog/action/ParsedTemplate.java b/net/minecraft/server/dialog/action/ParsedTemplate.java
index 8a720ef5cd10bf04e97fb34c1ca0f0b265e98468..f1807523e1e6dbd13093f195c368b43a92b82af9 100644
--- a/net/minecraft/server/dialog/action/ParsedTemplate.java
+++ b/net/minecraft/server/dialog/action/ParsedTemplate.java
@@ -13,7 +13,7 @@ public class ParsedTemplate {
private final String raw;
private final StringTemplate parsed;
- private ParsedTemplate(final String raw, final StringTemplate parsed) {
+ public ParsedTemplate(final String raw, final StringTemplate parsed) { // Luminol - make public
this.raw = raw;
this.parsed = parsed;
}
@@ -0,0 +1,21 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Sat, 18 Apr 2026 09:15:22 +0800
Subject: [PATCH] Add force the data command to be enabled config
diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java
index 4ddb5e7ce3529466c3930caa9aa6b18158e9a63d..9b1dcfadd6d5ae868b087a18983d03e8bb86b56a 100644
--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -202,7 +202,9 @@ public class Commands {
ClearInventoryCommands.register(this.dispatcher, context);
//CloneCommands.register(this.dispatcher, context); // Folia - region threading - TODO
DamageCommand.register(this.dispatcher, context);
- //DataCommands.register(this.dispatcher); // Folia - region threading - TODO
+ if(me.earthme.luminol.config.modules.experiment.CommandConfig.data) { // Luminol - Config for data command
+ DataCommands.register(this.dispatcher); // Folia - region threading - TODO // Luminol - Config for data command
+ } // Luminol - Config for data command
//DataPackCommand.register(this.dispatcher, context); // Folia - region threading - TODO
//DebugCommand.register(this.dispatcher); // Folia - region threading - TODO
DefaultGameModeCommands.register(this.dispatcher);
@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Sat, 18 Apr 2026 09:15:32 +0800
Subject: [PATCH] Add experimental config for command block command execution
diff --git a/net/minecraft/world/level/BaseCommandBlock.java b/net/minecraft/world/level/BaseCommandBlock.java
index 31f1583f29eebe5e13b5a7ac5116fdafc3592e5c..47fe63e49a7cfb77f8fe5cc95ab9faf46d244ba9 100644
--- a/net/minecraft/world/level/BaseCommandBlock.java
+++ b/net/minecraft/world/level/BaseCommandBlock.java
@@ -91,7 +91,7 @@ public abstract class BaseCommandBlock {
}
public boolean performCommand(final ServerLevel level) {
- if (true) return false; // Folia - region threading
+ if (!me.earthme.luminol.config.modules.experiment.CommandConfig.commandBlock) return false; // Folia - region threading // Luminol - Add experimental config for command block command execution
if (level.getGameTime() == this.lastExecution) {
return false;
}
@@ -1,52 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Tue, 18 Nov 2025 23:26:27 +0800
Subject: [PATCH] Leaves: Do not reset placed block on exception & Do not
prevent block entity and entity crash at LevelChunk
Co-authored by: MC_XiaoHei <xiaohei.xor7@outlook.com>
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/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java
index 06160c24f1ffbe1445284bc81598cabb3ad20477..ca3f31a72060f797115787302d32a019fa8243e0 100644
--- a/net/minecraft/world/item/BlockItem.java
+++ b/net/minecraft/world/item/BlockItem.java
@@ -69,20 +69,8 @@ public class BlockItem extends Item {
BlockState placedState = level.getBlockState(pos);
if (placedState.is(placementState.getBlock())) {
placedState = this.updateBlockStateFromTag(pos, level, itemStack, placedState);
- // Paper start - Reset placed block on exception
- try {
this.updateCustomBlockEntityTag(pos, level, player, itemStack, placedState);
updateBlockEntityComponents(level, pos, itemStack);
- } catch (Exception ex) {
- ((org.bukkit.craftbukkit.block.CraftBlockState) previousState).revertPlace();
- if (player instanceof ServerPlayer serverPlayer) {
- net.minecraft.server.MinecraftServer.LOGGER.warn("Player {} tried placing invalid block", player.getScoreboardName(), ex);
- serverPlayer.connection.disconnect(net.minecraft.network.chat.Component.literal("Packet processing error"));
- return InteractionResult.FAIL;
- }
- throw ex; // Rethrow exception if not placed by a player
- }
- // Paper end - Reset placed block on exception
placedState.getBlock().setPlacedBy(level, pos, placedState, player, itemStack);
// CraftBukkit start - special case for handling block placement with water lilies, frog spawn and snow buckets
if (player != null && (this instanceof PlaceOnWaterBlockItem || this instanceof SolidBucketItem)) {
diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
index 7e7292dd8fcb27fdb8e69e42e5383b6c22154747..b7ba4d8ed2cc65ba0e85a3cdf8b6568a7c9c02f1 100644
--- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
@@ -1000,12 +1000,6 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot
profiler.pop();
} catch (Throwable var5) {
- // Paper start - Prevent block entity and entity crashes
- final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", io.papermc.paper.util.MCUtil.getLevelName(LevelChunk.this.getLevel()), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ());
- net.minecraft.server.MinecraftServer.LOGGER.error(msg, var5);
- net.minecraft.world.level.chunk.LevelChunk.this.level.getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, var5))); // Paper - ServerExceptionEvent
- LevelChunk.this.removeBlockEntity(this.getPos());
- // Paper end - Prevent block entity and entity crashes
}
}
}
@@ -0,0 +1,20 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Sat, 18 Apr 2026 09:16:40 +0800
Subject: [PATCH] Add config to verify signature only in online-mode
Co-authored-by: Stabrinai <wujiaxin752@outlook.com>
diff --git a/net/minecraft/world/entity/player/ProfilePublicKey.java b/net/minecraft/world/entity/player/ProfilePublicKey.java
index 5e29a9de070e9922513e972254fc7ec43e8774f2..2de1886f9d9f06f8b92755e8421c176679e8b5ce 100644
--- a/net/minecraft/world/entity/player/ProfilePublicKey.java
+++ b/net/minecraft/world/entity/player/ProfilePublicKey.java
@@ -23,7 +23,7 @@ public record ProfilePublicKey(ProfilePublicKey.Data data) {
public static final Codec<ProfilePublicKey> TRUSTED_CODEC = ProfilePublicKey.Data.CODEC.xmap(ProfilePublicKey::new, ProfilePublicKey::data);
public static ProfilePublicKey createValidated(final SignatureValidator validator, final UUID profileId, final ProfilePublicKey.Data data) throws ProfilePublicKey.ValidationException {
- if (!data.validateSignature(validator, profileId)) {
+ if (!data.validateSignature(validator, profileId) && (org.bukkit.Bukkit.getServer().getOnlineMode() && me.earthme.luminol.config.modules.misc.PublickeyVerifyConfig.enabled)) { // Luminol - Verify signature only in online-mode
throw new ProfilePublicKey.ValidationException(INVALID_SIGNATURE, org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PUBLIC_KEY_SIGNATURE); // Paper - kick event causes
} else {
return new ProfilePublicKey(data);
@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Sat, 18 Apr 2026 09:17:02 +0800
Subject: [PATCH] Add config for whether to save portal tickets
diff --git a/net/minecraft/server/level/TicketType.java b/net/minecraft/server/level/TicketType.java
index 7e612e03087d63f9b500f7e9f3442b1931bae7ee..083a82b5200a251e85f155d1df016cd8b4f18db5 100644
--- a/net/minecraft/server/level/TicketType.java
+++ b/net/minecraft/server/level/TicketType.java
@@ -60,7 +60,7 @@ public final class TicketType<T> implements ca.spottedleaf.moonrise.patches.chun
public static final TicketType PLAYER_LOADING = register("player_loading", NO_TIMEOUT, FLAG_LOADING);
public static final TicketType PLAYER_SIMULATION = register("player_simulation", NO_TIMEOUT, FLAG_SIMULATION | FLAG_KEEP_DIMENSION_ACTIVE);
public static final TicketType FORCED = register("forced", NO_TIMEOUT, FLAG_PERSIST | FLAG_LOADING | FLAG_SIMULATION | FLAG_KEEP_DIMENSION_ACTIVE);
- public static final TicketType PORTAL = register("portal", 300L, FLAG_PERSIST | FLAG_LOADING | FLAG_SIMULATION | FLAG_KEEP_DIMENSION_ACTIVE);
+ public static final TicketType PORTAL = register("portal", 300L, me.earthme.luminol.config.modules.misc.SavePortalTicketsConfig.doSave ? FLAG_PERSIST | FLAG_LOADING | FLAG_SIMULATION | FLAG_KEEP_DIMENSION_ACTIVE : FLAG_LOADING | FLAG_SIMULATION | FLAG_KEEP_DIMENSION_ACTIVE);
public static final TicketType ENDER_PEARL = register("ender_pearl", 40L, FLAG_LOADING | FLAG_SIMULATION | FLAG_KEEP_DIMENSION_ACTIVE);
public static final TicketType UNKNOWN = register("unknown", 1L, FLAG_CAN_EXPIRE_IF_UNLOADED | FLAG_LOADING);
public static final TicketType PLUGIN = register("plugin", NO_TIMEOUT, FLAG_LOADING | FLAG_SIMULATION); // CraftBukkit
@@ -1,14 +1,28 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Wed, 2 Jul 2025 23:36:25 +0800
Subject: [PATCH] Fix datapack command save function
Subject: [PATCH] Add back read-only datapack command & Fix datapack command
save function
diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java
index 9b1dcfadd6d5ae868b087a18983d03e8bb86b56a..21f2b9866e1cf36421e10a945fb6fbaa251009ed 100644
--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -205,7 +205,7 @@ public class Commands {
if(me.earthme.luminol.config.modules.experiment.CommandConfig.data) { // Luminol - Config for data command
DataCommands.register(this.dispatcher); // Folia - region threading - TODO // Luminol - Config for data command
} // Luminol - Config for data command
- //DataPackCommand.register(this.dispatcher, context); // Folia - region threading - TODO
+ DataPackCommand.register(this.dispatcher, context); // Folia - region threading - TODO // Luminol - Add back read-only datapack command
//DebugCommand.register(this.dispatcher); // Folia - region threading - TODO
DefaultGameModeCommands.register(this.dispatcher);
//DialogCommand.register(this.dispatcher, context); // Folia - region threading - TODO
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 35c1b7f40563823401ca204ec41ef57220b5ad55..f993a4de22558e7b9598aa7500cd3cc1225e4c5b 100644
index daf45761bdc88848ba81b7595b5abd5635dfd607..b9ff4628421ba0c5bdaec13c9dc1b35e8d194d92 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -2472,6 +2472,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -2428,6 +2428,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
return this.reloadResources(packsToEnable, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN);
}
@@ -25,42 +39,6 @@ index 35c1b7f40563823401ca204ec41ef57220b5ad55..f993a4de22558e7b9598aa7500cd3cc1
public CompletableFuture<Void> reloadResources(
final Collection<String> packsToEnable, final io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause cause
) {
diff --git a/net/minecraft/server/commands/DataPackCommand.java b/net/minecraft/server/commands/DataPackCommand.java
index 0c7c829270d3cd3dd05a3dc7dfe82ee01d5c6bd8..04fa1c5bb1c0b025a56941aae7d2151d1087e257 100644
--- a/net/minecraft/server/commands/DataPackCommand.java
+++ b/net/minecraft/server/commands/DataPackCommand.java
@@ -98,7 +98,7 @@ public class DataPackCommand {
dispatcher.register(
Commands.literal("datapack")
.requires(Commands.hasPermission(Commands.LEVEL_GAMEMASTERS))
- /*.then( // Luminol - Add back read-only datapack command
+ .then(
Commands.literal("enable")
.then(
Commands.argument("name", StringArgumentType.string())
@@ -145,11 +145,11 @@ public class DataPackCommand {
.suggests(SELECTED_PACKS)
.executes(c -> disablePack(c.getSource(), getPack(c, "name", false)))
)
- )*/ // Luminol - Add back read-only datapack command
+ )
.then(
Commands.literal("list")
.executes(c -> listPacks(c.getSource()))
- //.then(Commands.literal("available").executes(c -> listAvailablePacks(c.getSource()))) // Luminol - Add back read-only datapack command
+ .then(Commands.literal("available").executes(c -> listAvailablePacks(c.getSource()))) // Luminol - Add back read-only datapack command
.then(Commands.literal("enabled").executes(c -> listEnabledPacks(c.getSource())))
)
.then(
@@ -235,7 +235,7 @@ public class DataPackCommand {
}
private static int listPacks(final CommandSourceStack source) {
- return listEnabledPacks(source) ;// + listAvailablePacks(source); // Luminol - Add back read-only datapack command
+ return listEnabledPacks(source) + listAvailablePacks(source);
}
private static int listAvailablePacks(final CommandSourceStack source) {
diff --git a/net/minecraft/server/commands/ReloadCommand.java b/net/minecraft/server/commands/ReloadCommand.java
index 80fef4277b9e986c17a4cb895e130726bdb63229..4bed8d0432508cbe5c0d1fc7d68052ec2af7260f 100644
--- a/net/minecraft/server/commands/ReloadCommand.java

Some files were not shown because too many files have changed in this diff Show More