Compare commits

..

51 Commits

Author SHA1 Message Date
Helvetica Volubi 3d57a67913 Update upstream 2025-08-17 22:25:14 +08:00
Helvetica Volubi ecc514b340 Update upstream(Luminol-lithium-sleeping-block-entity) 2025-08-16 02:51:48 +08:00
Helvetica Volubi 75baf3dfaf Update some patches from Leaves 2025-08-15 03:21:36 +08:00
Helvetica Volubi 4ed8600511 Update upstream(Luminol-lithium-sleeping-block-entity) 2025-08-13 22:27:33 +08:00
Helvetica Volubi 0160aac4c6 Update upstream (Luminol) 2025-08-11 13:35:13 +08:00
Helvetica Volubi cbed24020a Update upstream (Luminol) 2025-08-09 02:37:40 +08:00
Helvetica Volubi 682029a451 fix: fix some bugs
fix crash in contain view (#44)

fix tps and mspt send in servux
2025-08-08 00:59:22 +08:00
Helvetica Volubi f3334ae6ec Update Upstream(Luminol) 2025-08-04 20:00:05 +08:00
Helvetica Volubi df0e0eb82b [ci skip]add missing license 2025-08-04 18:01:47 +08:00
Helvetica Volubi d09469e84b servux protocol & unapply old replaceable by mushrooms [(#38)(#40)](#43)
* feat: add a config to allow to disable some check for operator

* feat: leaves servux protocol

* fix: upload missing files

* Update Upstream(Luminol)

* fix: upload missing files - 2

* fix: reuse tickCount

* fix: fix

* fix: adjust folia

* add leaves license

* add infinite support
2025-08-04 15:19:09 +08:00
Helvetica Volubi a5e56ff2ca feat: add a config to allow to disable some check for operator 2025-08-03 16:55:38 +08:00
Helvetica Volubi e38108c99b fix: temporarily fix endermite spawn position 2025-08-02 12:44:30 +08:00
Helvetica Volubi ab31ebc674 Update Upstream(Luminol) 2025-08-01 13:40:00 +08:00
Helvetica Volubi 87ba8b9746 Update Upstream(Luminol) 2025-07-30 21:55:01 +08:00
Helvetica Volubi e71b36e331 [ci skip]docs: refactor format 2025-07-30 16:18:31 +08:00
Helvetica Volubi cfb1ad88f4 Update Upstream(Luminol) 2025-07-28 23:03:43 +08:00
Helvetica Volubi b40276f037 feat: use new limit of 524288 instead of 16384 2025-07-28 17:25:16 +08:00
Helvetica Volubi e1b3a14ece feat: Leaves Vanilla hopper 2025-07-28 01:11:40 +08:00
Helvetica Volubi c257ebd19c fix: fix redstone shear wrench
origin: https://github.com/LeavesMC/Leaves/pull/621
2025-07-27 18:37:33 +08:00
Helvetica Volubi 52b6803d68 feat: leaves disable packet limit 2025-07-27 18:19:20 +08:00
Helvetica Volubi 71f83be588 fix: fix infinite trade 2025-07-27 00:38:31 +08:00
Helvetica Volubi 5733cc4e35 fix: fix a config name error 2025-07-26 18:59:50 +08:00
Helvetica Volubi a8147bb171 Fix quick craft 2025-07-26 13:41:28 +08:00
Helvetica Volubi 17f0269ee6 Update Upstream(Luminol) 2025-07-26 13:31:38 +08:00
Helvetica Volubi f574f277eb feat: use leaves-ver Item overstack util instead of Stackable ShulkerBoxes 2025-07-26 01:20:32 +08:00
Helvetica Volubi 94e7c4e822 Update Upstream(Luminol) 2025-07-26 00:01:35 +08:00
Helvetica Volubi 01582648ee feat: add flipping shears support(#29)
Co-authored-by: violetc <58360096+s-yh-china@users.noreply.github.com>
2025-07-23 20:30:45 +08:00
Helvetica Volubi db9bd6e469 Update Upstream(Luminol) 2025-07-23 19:30:21 +08:00
Helvetica Volubi 0eebea7a21 Update Upstream(Luminol) 2025-07-20 13:16:27 +08:00
Helvetica Volubi 1707673c8d feat: add missing command for unsafe waypoint
Update Upstream(Luminol)
2025-07-18 22:15:13 +08:00
Bacteriawa 7179463c36 Start 1.21.8 2025-07-18 19:19:55 +08:00
Bacteriawa 509e782d1a Update Upstream(Luminol) 2025-07-18 16:58:46 +08:00
Helvetica Volubi bfb5f74600 fix: fix a config name error 2025-07-17 21:43:24 +08:00
Helvetica Volubi a697b179e7 Update Upstream(Luminol) 2025-07-17 00:37:28 +08:00
Helvetica Volubi 904abaa564 Update Upstream(Luminol) 2025-07-16 00:12:42 +08:00
Bacteriawa d1aeaa83f4 Update Upstream(Lumniol) 2025-07-13 22:26:19 +08:00
Helvetica Volubi 145eae3c41 refactor: push update to upstream 2025-07-13 02:53:40 +08:00
Helvetica Volubi 6e61e12285 Update upstream(Luminol) 2025-07-12 14:56:28 +08:00
Klop233 56e870d999 Update Upstream(Luminol) 2025-07-11 11:09:37 +00:00
Helvetica Volubi 7f8135b193 fix: fix #26 & Update upstream(Luminol) 2025-07-10 03:10:32 +08:00
Helvetica Volubi 31096e1a48 Add config to enable unsafe waypoint bar 2025-07-07 18:25:45 +08:00
Helvetica Volubi f261b9fe58 feat: add config to allow infinite trade 2025-07-07 16:40:42 +08:00
Helvetica Volubi 92412e0a72 feat: add Old Explosion Damage Calculator support(#25)
Co-authored by: MC_XiaoHei <xor7xiaohei@gmail.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/3e96b237749a960f297f211d439ffc9ea7fd2381/leaves-server/minecraft-patches/features/0134-Old-wet-tnt-explode-behavior.patch)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
2025-07-07 15:37:26 +08:00
Helvetica Volubi 23f4d70fca Update Upstream(Luminol) 2025-07-07 01:19:23 +08:00
Helvetica Volubi c613aaa191 fix: fix a bug in datapack reload - hot reload unsupported 2025-07-05 18:51:35 +08:00
Helvetica Volubi 692c38f7ac Update upstream(Luminol) 2025-07-04 18:21:23 +08:00
Helvetica Volubi 96683209f5 Add config to enable datapack command - only support some function 2025-07-03 00:36:15 +08:00
Helvetica Volubi 6d9458fa01 Update Upstream(Luminol) 2025-07-02 23:31:19 +08:00
Helvetica Volubi 333a7fc17c Update to 1.21.7 2025-07-01 21:54:38 +08:00
Helvetica Volubi 914d8cc1dd remove Better ShulkerBox and Update Upstream(Luminol) 2025-06-30 13:51:32 +08:00
Helvetica Volubi 6f9e4abcbf Start 1.21.6 2025-06-28 00:41:56 +08:00
109 changed files with 10998 additions and 11324 deletions
+2
View File
@@ -9,6 +9,8 @@ body:
- type: markdown
attributes:
value: |
在提问之前,请您先阅读 [提问的智慧](https://lug.ustc.edu.cn/wiki/doc/smart-questions/) ,我们不会为任何无意义的问题作回复。
Please read [smart questions](http://www.catb.org/~esr/faqs/smart-questions.html) before asking questions, we won't respond to any meaningless questions.
请详细描述您所遇到的Bug。确保您**真的**遇到了**服务端**Bug,而非原版特性或客户端Bug。
Please describe the bug you encountered in detail. Make sure you're **actually** hitting a **server-side** bug, not a vanilla feature or client-side bug.<br>
如果您确定的话,就按照下面的模板填充信息,并提交反馈。
@@ -11,6 +11,8 @@ body:
value: |
感谢您为 Lophine 提交新的需求!请尽可能详细地描述,以便我们可以更轻松地考虑和审查请求。
Thank you for filling out a feature request for Lophine! Please be as detailed as possible so that we may consider and review the request easier.<br>
在提问之前,请您先阅读 [提问的智慧](https://lug.ustc.edu.cn/wiki/doc/smart-questions/) ,我们不会为任何无意义的问题作回复。
Please read [smart questions](http://www.catb.org/~esr/faqs/smart-questions.html) before asking questions, we won't respond to any meaningless questions.
请搜索所有问题以避免重复的功能请求。如果您期望的功能已存在,请回复如果您有什么要补充的。
We ask that you search all the issues to avoid a duplicate feature request. If one exists, please reply if you have anything to add.<br>
在请求新功能之前,请确保您使用的是最新版本并且您请求的功能不在 Lophine 中。
@@ -1,10 +1,12 @@
name: Lophine CI - ver/1.21.5
name: Lophine CI
on:
push:
branches: [ "ver/1.21.5" ]
branches:
- "**"
pull_request:
branches: [ "ver/1.21.5" ]
branches:
- "**"
permissions: write-all
@@ -20,7 +22,7 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 22
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
@@ -32,18 +34,10 @@ jobs:
run: git config --global user.email "ci@luminolmc.com" && git config --global user.name "LuminolMC CI"
- name: Apply All Patches
run: ./gradlew applyAllPatches
run: ./gradlew --refresh-dependencies applyAllPatches
- name: CreateMojmapPaperclipJar
run: ./gradlew createMojmapPaperclipJar
- name: Publish to repo
if: github.event_name != 'pull_request' && false # do not push to any repo
continue-on-error: true
run: ./gradlew generateDevelopmentBundle publish -PpublishDevBundle=true
env:
PRIVATE_MAVEN_REPO_PASSWORD: ${{ secrets.PRIVATE_MAVEN_REPO_PASSWORD }}
PRIVATE_MAVEN_REPO_USERNAME: ${{ secrets.PRIVATE_MAVEN_REPO_USERNAME }}
run: ./gradlew --refresh-dependencies createMojmapPaperclipJar
- name: Upload Artifact
uses: actions/upload-artifact@v4
@@ -55,8 +49,16 @@ jobs:
if: github.event_name != 'pull_request'
run: sh scripts/SetENV.sh
- name: Publish to repo
if: github.event_name != 'pull_request' && env.flag_release == 'true' && false # do not push to any repo
continue-on-error: true
run: ./gradlew --refresh-dependencies generateDevelopmentBundle publish -PpublishDevBundle=true
env:
PRIVATE_MAVEN_REPO_PASSWORD: ${{ secrets.PRIVATE_MAVEN_REPO_PASSWORD }}
PRIVATE_MAVEN_REPO_USERNAME: ${{ secrets.PRIVATE_MAVEN_REPO_USERNAME }}
- name: Create Release
if: github.event_name != 'pull_request'
if: github.event_name != 'pull_request' && env.flag_release == 'true'
uses: ncipollo/release-action@v1
with:
tag: ${{ env.tag }}
+1 -1
View File
@@ -2,7 +2,7 @@ Lophine inherits its licensing from the included upstream projects.
Lophine从上游继承了许可证。
As such, Lophine is licensed under the
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.
+20 -5
View File
@@ -4,6 +4,7 @@
<h4>Lophine 是一个基于Luminol的分支,具有许多有用的优化和可配置的原版特性,目标是在Folia上实现更多生电的实现</h4>
![Created At](https://img.shields.io/github/created-at/LuminolMC/Lophine?style=flat-square)
[![License](https://img.shields.io/github/license/LuminolMC/Lophine?style=flat-square)](LICENSE.md)
[![Issues](https://img.shields.io/github/issues/LuminolMC/Lophine?style=flat-square)](https://github.com/LuminolMC/Lophine/issues)
![Commit Activity](https://img.shields.io/github/commit-activity/w/LuminolMC/Lophine?style=flat-square)
@@ -13,21 +14,29 @@
[English](./README_EN.md) | **中文**
## 特性
- 支持部分Bukkit插件
- 可配置的原版特性
- Tpsbar 支持
- 对单线程区域性能的优化
- 更多有用的功能
- 支持部分Bukkit插件
- 可配置的原版特性
- Tpsbar 支持
- 针对folia bug的一些修复
- linear和b_linear(linear的重新实现)存档格式支持
- 对单线程区域性能的优化
- 更多有用的功能
## 下载
任何版本都可以在 [Release](https://github.com/LuminolMC/Lophine/releases), 中找到,你也可以通过[以下步骤](./README.md#构建)自己构建。
## 构建
要构建一个paperclip jar,你需要运行以下命令。你可以在lophine-server/build/libs中找到jar(注意:需要`JDK21`
```shell
./gradlew applyAllPatches && ./gradlew createMojmapPaperclipJar
```
## 使用API
使用 Gradle:
```kotlin
@@ -62,26 +71,32 @@ 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)
## 关于 Issue
当您遇到任何问题时,请向我们提问,我们将尽力解决,但请记得清楚地描述您的问题并提供足够的日志等信息。
## 贡献代码
可查看 [Contributing](./docs/CONTRIBUTING.md)
## BStats
![bStats](https://bstats.org/signatures/server-implementation/Lophine.svg "bStats")
## 特别感谢
<b>感谢[LegacyLands](https://github.com/LegacyLands)对本项目的赞助,如果你想开发一些跨folia/非folia平台的插件,[legacy-lands-library](https://github.com/LegacyLands/legacy-lands-library/)将会是个不错的lib</b>
![legacy-lands-logo](public/image/legacy-lands-logo.png)
## 请给我们一个 ⭐Star
> 你的每一个免费的 ⭐Star 就是我们每一个前进的动力。
<a href="https://star-history.com/#LuminolMC/Luminol&LuminolMC/LightingLuminol&LuminolMC/Lophine&Date">
+22 -6
View File
@@ -2,8 +2,10 @@
# Lophine
<h4>LightingLuminol is a Luminol fork with many useful optimizations and configurable vanilla features, aims to provide more function for survival-usable circuit on folia</h4>
<h4>Lophine is a Luminol fork with many useful optimizations and configurable vanilla features, aims to provide
more function for survival-usable circuit on folia</h4>
![Created At](https://img.shields.io/github/created-at/LuminolMC/Lophine?style=flat-square)
[![License](https://img.shields.io/github/license/LuminolMC/Lophine?style=flat-square)](LICENSE.md)
[![Issues](https://img.shields.io/github/issues/LuminolMC/Lophine?style=flat-square)](https://github.com/LuminolMC/Lophine/issues)
![Commit Activity](https://img.shields.io/github/commit-activity/w/LuminolMC/Lophine?style=flat-square)
@@ -13,21 +15,29 @@
**English** | [中文](./README.md)
## Features
- Supported some Bukkit plugin
- Configurable vanilla features
- Tpsbar support
- Useful optimizations to improve the performance of single threaded region
- More functions
- Supported some Bukkit plugin
- Configurable vanilla features
- Tpsbar support
- Useful fixes of bugs of folia
- Linear and BufferedLinear(AKA. b_linear)(a reimplementation of linear) region format support
- Useful optimizations to improve the performance of single threaded region
- More functions
## Download
Any versions are available in the [release](https://github.com/LuminolMC/Lophine/releases), also you can build it by yourself through [the following steps](./README_EN.md#Build).
## Build
To build a paperclip jar, you need to run the following command. You can find the jar in lophine-server/build/libs(Note: `JDK21` is needed)
```shell
./gradlew applyAllPatches && ./gradlew createMojmapPaperclipJar
```
## Using API
For gradle:
```kotlin
@@ -62,25 +72,31 @@ For maven
```
## Contact
> If you are interested in this project or have any issue, feel free to ask us.
**QQ Group: [1015048616](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=hTPlI5j6XB8pgk4sdx6RkjhBPGG1r4IR&authKey=pnu6uCKQP7Sja2CJWC15Qi3BeI%2FAsh8tU4m5muufMBjbB3zz%2BwHBZCTRRdSNKhld&noverify=0&group_code=1015048616)** | QQ Channel: [Click To Join](https://pd.qq.com/s/eq9krf9j) | Telegram: [Click To Join](https://t.me/LuminolMinecraft) | Discord: [Click To Join](https://discord.gg/Qd7m3V6eDx)
## About Issue
When you meet any problems, just ask us, we will do our best to solve it, but remember to state your problem clear and provide enough logs etc.</br>
## Pull Requests
See [Contributing](./docs/CONTRIBUTING_EN.md)
## BStats
![bStats](https://bstats.org/signatures/server-implementation/Lophine.svg "bStats")
## Special Thanks
Thanks [LegacyLands](https://github.com/LegacyLands) for sponsoring this project.If you want to develop some crossing folia/non-folia platform plugins, [legacy-lands-library](https://github.com/LegacyLands/legacy-lands-library/) will be a nice choice for you
![legacy-lands-logo](public/image/legacy-lands-logo.png)
## Please ⭐star us!
<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" />
+10 -9
View File
@@ -3,7 +3,7 @@ import org.gradle.api.tasks.testing.logging.TestLogEvent
plugins {
java // TODO java launcher tasks
id("io.papermc.paperweight.patcher") version "2.0.0-beta.17"
id("moe.luminolmc.hyacinthusweight.patcher") version "+" // Automatically pulls the latest
}
paperweight {
@@ -41,6 +41,7 @@ paperweight {
}
val paperMavenPublicUrl = "https://repo.papermc.io/repository/maven-public/"
val menthaMavenPublicUrl = "https://repo.menthamc.com/repository/maven-public/";
subprojects {
apply(plugin = "java-library")
@@ -55,6 +56,7 @@ subprojects {
repositories {
mavenCentral()
maven(paperMavenPublicUrl)
maven(menthaMavenPublicUrl)
}
dependencies {
@@ -65,16 +67,15 @@ subprojects {
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
}
tasks.withType<JavaCompile> {
tasks.withType<JavaCompile>().configureEach {
options.encoding = Charsets.UTF_8.name()
options.release = 21
options.isFork = true
options.forkOptions.memoryMaximumSize = "6g" // Prevent OOM during building
}
tasks.withType<Javadoc> {
tasks.withType<Javadoc>().configureEach {
options.encoding = Charsets.UTF_8.name()
}
tasks.withType<ProcessResources> {
tasks.withType<ProcessResources>().configureEach {
filteringCharset = Charsets.UTF_8.name()
}
tasks.withType<Test> {
@@ -90,14 +91,14 @@ subprojects {
maven("https://repo.menthamc.com/repository/maven-snapshots/") {
name = "MenthaMC"
credentials(PasswordCredentials::class) {
username = System.getenv("MAVEN_REPO_USER")
password = System.getenv("MAVEN_REPO_PASSWORD")
username = System.getenv("PRIVATE_MAVEN_REPO_USERNAME")
password = System.getenv("PRIVATE_MAVEN_REPO_PASSWORD")
}
}
}
}
tasks.withType<Javadoc> {
tasks.withType<Javadoc>().configureEach {
options {
(this as StandardJavadocDocletOptions).apply {
addStringOption("-add-modules", "jdk.incubator.vector")
@@ -105,4 +106,4 @@ subprojects {
}
}
}
}
}
+14 -9
View File
@@ -1,5 +1,5 @@
为Lophine贡献代码
===========
===============
[English](./CONTRIBUTING_EN.md) | **中文**
@@ -41,19 +41,24 @@ Lophine 使用和 Folia 一样的补丁系统,并为了针对不同部分的
1. 将你的仓库 clone 到本地;
2. 在你的 IDE 或 终端 内执行 Gradle 的 `applyAllPatches` 任务,如果是在终端内,你可以执行 `./gradlew applyAllPatches`
3. 在执行操作后,仓库根目录下应该存在以下目录对: `lophine-api``lophine-server` `folia-api``folia-server` 以及 `paper-api``paper-server`(下文称作 `*-api``*-server` );
3. 在执行操作后,仓库根目录下应该存在以下目录对: `lophine-api``lophine-server` `luminol-api``luminol-server` `folia-api``folia-server` 以及 `paper-api``paper-server`(下文称作 `*-api``*-server` );
4. 进入 仓库根目录下的 `*-api``*-server` 文件夹进行修改。
以下为对上述各个文件夹的简单描述,详细描述可以参考[这里](https://github.com/Toffikk/paperweight-examples/blob/18241979c88068d5b061d95ad69c98ecb201c246/README.md)
1. API部分
- `lophine-api` :暂无有效文件
- `folia-api` :对folia-API的修改应当在此文件夹下进行
- `paper-api` :对paper-API/spigot-API/bukkit-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原版服务器的修改应当在此文件夹下进行
- `folia-server` 暂无有效文件
- `paper-server` :对于非原版的对服务器逻辑的修改应当在此文件夹下进行
- `lophine-server` 对Minecraft原版服务器的修改和新增文件应当在此文件夹下进行
- `luminol-server` :对luminol-server的修改应当在此文件夹下进行
- `folia-server` :对folia-Server的修改应当在此文件夹下进行
- `paper-server` :对于paper对服务器逻辑的修改应当在此文件夹下进行
顺便一提,仓库根目录下的 `*-api``*-server` 并不是正常的 git 仓库:
@@ -80,7 +85,7 @@ Lophine 使用和 Folia 一样的补丁系统,并为了针对不同部分的
1. 在 HEAD 上直接进行修改;
2. 使用 `git commit -a --fixup <hash>` 来进行一个更正提交;(不要提交对在lophine新建文件的修改)
- 如果你想要更改提交信息,你也可以用 `--squash` 来代替 `--fixup`
- 如果你想要更改提交信息,你也可以用 `--squash` 来代替 `--fixup`
3. 使用 `git rebase -i --autosquash base` 来进行自动变基,你只需要输入 `:q` 来关闭确认页面即可;
4. 运行 Gradle 任务 `fixupPaperApiFilePatches` 来修改已被修改的在lophine新建文件的补丁(注意不要提交);
5. 运行 Gradle 任务 `rebuildAllServerPatches` 来修改已被修改的补丁;
+12 -8
View File
@@ -1,5 +1,5 @@
Contributing to Lophine
===========
=======================
**English** | [中文](./CONTRIBUTING.md)
@@ -44,20 +44,24 @@ 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` , `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` , `luminol-api` and `luminol-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):
1. API part
- `lophine-api` : There is no valid file
- `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
- `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` : Modifications to Minecraft Vanilla Server's source logic should be carried out in this folder
- `folia-server` : There is no valid file
- `paper-server` : Modifications to non-vanilla server logic should be carried out in this folder
- `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
BTW, `*-api` and `*-server` and are not normal git repositories.
+5 -3
View File
@@ -1,8 +1,10 @@
group=fun.bm.lophine
version=1.21.5-R0.1-SNAPSHOT
mcVersion=1.21.5
version=1.21.8-R0.1-SNAPSHOT
mcVersion=1.21.8
release=2
# 0 for skip release, 1 for pre-release, 2 for release
luminolRef=3eedf8615328bd7738d01e04694aacfa9601b17d
luminolRef=f8bd34a2dff31a137b0808b83b7503a22fcd0527
org.gradle.configuration-cache=true
org.gradle.caching=true
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
+71 -70
View File
@@ -1,4 +1,5 @@
### GNU GENERAL PUBLIC LICENSE
==============================
Version 3, 29 June 2007
@@ -217,23 +218,23 @@ produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these
conditions:
- a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
- b) The work must carry prominent notices stating that it is
released under this License and any conditions added under
section 7. This requirement modifies the requirement in section 4
to "keep intact all notices".
- c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
- d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
- a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
- b) The work must carry prominent notices stating that it is
released under this License and any conditions added under
section 7. This requirement modifies the requirement in section 4
to "keep intact all notices".
- c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
- d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
@@ -252,42 +253,42 @@ sections 4 and 5, provided that you also convey the machine-readable
Corresponding Source under the terms of this License, in one of these
ways:
- a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
- b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the Corresponding
Source from a network server at no charge.
- c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
- d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
- e) Convey the object code using peer-to-peer transmission,
provided you inform other peers where the object code and
Corresponding Source of the work are being offered to the general
public at no charge under subsection 6d.
- a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
- b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the Corresponding
Source from a network server at no charge.
- c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
- d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
- e) Convey the object code using peer-to-peer transmission,
provided you inform other peers where the object code and
Corresponding Source of the work are being offered to the
public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
@@ -363,23 +364,23 @@ Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders
of that material) supplement the terms of this License with terms:
- a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
- b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
- c) Prohibiting misrepresentation of the origin of that material,
or requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
- d) Limiting the use for publicity purposes of names of licensors
or authors of the material; or
- e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
- f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions
of it) with contractual assumptions of liability to the recipient,
for any liability that these contractual assumptions directly
impose on those licensors and authors.
- a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
- b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
- c) Prohibiting misrepresentation of the origin of that material,
or requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
- d) Limiting the use for publicity purposes of names of licensors
or authors of the material; or
- e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
- f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions
of it) with contractual assumptions of liability to the recipient,
for any liability that these contractual assumptions directly
impose on those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
+2 -2
View File
@@ -1,5 +1,5 @@
The MIT License (MIT)
=====================
### The MIT License (MIT)
=========================
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
+16 -7
View File
@@ -1,6 +1,6 @@
--- a/luminol-server/build.gradle.kts
+++ b/luminol-server/build.gradle.kts
@@ -54,7 +_,23 @@
@@ -52,7 +_,23 @@
}
}
@@ -23,8 +23,8 @@
+
+ activeFork = lophine
spigot {
buildDataRef = "702e1a0a5072b2c4082371d5228cb30525687efc"
@@ -142,12 +_,16 @@
resources { srcDir("../paper-server/src/main/resources") }
java { srcDir("../folia-server/src/main/java") }
@@ -47,11 +47,11 @@
dependencies {
- implementation(project(":luminol-api")) // Luminol
+ implementation(project(":lophine-api")) // Luminol // Lophine
implementation("com.electronwill.night-config:toml:3.8.2") // Luminol - Night config
// Abomination start
+ implementation(project(":lophine-api")) // Lophine
// Luminol start - Dependenices insert
implementation("com.electronwill.night-config:toml:3.8.2") // Night config
implementation("com.github.luben:zstd-jni:1.5.4-1")
@@ -265,14 +_,14 @@
@@ -276,14 +_,14 @@
val gitBranch = git.exec(providers, "rev-parse", "--abbrev-ref", "HEAD").get().trim()
attributes(
"Main-Class" to "org.bukkit.craftbukkit.Main",
@@ -62,7 +62,7 @@
- "Specification-Title" to "Luminol",
+ "Specification-Title" to "Lophine",
"Specification-Version" to project.version,
"Specification-Vendor" to "Luminol Team",
"Specification-Vendor" to "LuminolMC org",
- "Brand-Id" to "luminolmc:luminol",
- "Brand-Name" to "Luminol",
+ "Brand-Id" to "luminolmc:lophine",
@@ -70,3 +70,12 @@
"Build-Number" to (build ?: ""),
"Build-Time" to buildTime.toString(),
"Git-Branch" to gitBranch,
@@ -447,7 +_,7 @@
}
fill {
- project("luminol")
+ project("lophine")
versionFamily(paperweight.minecraftVersion.map { it.split(".", "-").takeWhile { part -> part.toIntOrNull() != null }.take(2).joinToString(".") })
version(paperweight.minecraftVersion)
@@ -4,302 +4,18 @@ Date: Sun, 8 Jun 2025 00:58:13 +0800
Subject: [PATCH] Rebrand to Lophine
diff --git a/src/main/java/me/earthme/luminol/commands/LuminolConfigCommand.java b/src/main/java/me/earthme/luminol/commands/LuminolConfigCommand.java
index fb63e2a3205ea9f7aaee0ff0f4dc0cc1a5268507..645f2f41de74f4461685c753dcd6cfc5dfa8b2ab 100644
--- a/src/main/java/me/earthme/luminol/commands/LuminolConfigCommand.java
+++ b/src/main/java/me/earthme/luminol/commands/LuminolConfigCommand.java
@@ -13,11 +13,17 @@ import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/me/earthme/luminol/config/ConfigManager.java b/src/main/java/me/earthme/luminol/config/ConfigManager.java
index a67fc80eb3182415557b4bdf7f146aefb43103da..52fec4c88fee3c027c954365538516aaa8335c7f 100644
--- a/src/main/java/me/earthme/luminol/config/ConfigManager.java
+++ b/src/main/java/me/earthme/luminol/config/ConfigManager.java
@@ -21,6 +21,7 @@ public class ConfigManager {
public class LuminolConfigCommand extends Command {
- public LuminolConfigCommand() {
- super("luminolconfig");
- this.setPermission("luminol.commands.luminolconfig");
+ private LuminolConfig config;
+
+ public LuminolConfigCommand(String name) {
+ super(name + "config");
+ this.setPermission(name + ".commands." + name + "config");
this.setDescription("Manage config file");
- this.setUsage("/luminolconfig");
+ this.setUsage("/" + name + "config");
+ }
+
+ public void initConfig(LuminolConfig config) {
+ this.config = config;
public static void initConfigs() {
configfiles.put("luminol", ConfigsInstance.of(new File("luminol_config"), "luminol", "me.earthme.luminol.config.modules"));
+ configfiles.put("lophine", ConfigsInstance.of(new File("lophine_config"), "lophine", "fun.bm.lophine.config.modules")); // add lophine config to global config
preLoad();
}
public void wrongUse(CommandSender sender) {
@@ -38,7 +44,7 @@ public class LuminolConfigCommand extends Command {
result.add("reset");
result.add("reload");
} else if (args.length == 2 && (args[0].equals("query") || args[0].equals("set") || args[0].equals("reset"))) {
- result.addAll(LuminolConfig.completeConfigPath(args[1]));
+ result.addAll(config.completeConfigPath(args[1]));
}
return result;
}
@@ -59,7 +65,7 @@ public class LuminolConfigCommand extends Command {
switch (args[0]) {
case "reload" -> {
- LuminolConfig.reloadAsync().thenAccept(nullValue -> sender.sendMessage(
+ config.reloadAsync().thenAccept(nullValue -> sender.sendMessage(
Component
.text("Reloaded config file!")
.color(TextColor.color(0, 255, 0))
@@ -69,8 +75,8 @@ public class LuminolConfigCommand extends Command {
if (args.length == 2 || args.length > 3) {
wrongUse(sender);
return true;
- } else if (LuminolConfig.setConfig(args[1], args[2])) {
- LuminolConfig.reloadAsync().thenAccept(nullValue -> sender.sendMessage(
+ } else if (config.setConfig(args[1], args[2])) {
+ config.reloadAsync().thenAccept(nullValue -> sender.sendMessage(
Component
.text("Set Config " + args[1] + " to " + args[2] + " successfully!")
.color(TextColor.color(0, 255, 0))
@@ -88,10 +94,10 @@ public class LuminolConfigCommand extends Command {
wrongUse(sender);
return true;
} else {
- LuminolConfig.resetConfig(args[1]);
- LuminolConfig.reloadAsync().thenAccept(nullValue -> sender.sendMessage(
+ config.resetConfig(args[1]);
+ config.reloadAsync().thenAccept(nullValue -> sender.sendMessage(
Component
- .text("Reset Config " + args[1] + " to " + LuminolConfig.getConfig(args[1]) + " successfully!")
+ .text("Reset Config " + args[1] + " to " + config.getConfig(args[1]) + " successfully!")
.color(TextColor.color(0, 255, 0))
));
}
@@ -103,7 +109,7 @@ public class LuminolConfigCommand extends Command {
} else {
sender.sendMessage(
Component
- .text("Config " + args[1] + " is " + LuminolConfig.getConfig(args[1]) + "!")
+ .text("Config " + args[1] + " is " + config.getConfig(args[1]) + "!")
.color(TextColor.color(0, 255, 0))
);
}
diff --git a/src/main/java/me/earthme/luminol/config/LuminolConfig.java b/src/main/java/me/earthme/luminol/config/LuminolConfig.java
index 4526eba8e9dfb605bc4672a3eaf0688a5af66049..c05aa7296cd41e21306acd0b8fbc6d6684f56d9a 100644
--- a/src/main/java/me/earthme/luminol/config/LuminolConfig.java
+++ b/src/main/java/me/earthme/luminol/config/LuminolConfig.java
@@ -29,22 +29,33 @@ import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class LuminolConfig {
- public static final Logger logger = LogManager.getLogger();
- private static final File baseConfigFolder = new File("luminol_config");
- private static final File baseConfigFile = new File(baseConfigFolder, "luminol_global_config.toml");
- private static final Set<IConfigModule> allInstanced = new HashSet<>();
- private static final Map<String, Object> stagedConfigMap = new HashMap<>();
- private static final Map<String, Object> defaultvalueMap = new HashMap<>();
- public static boolean alreadyInit = false;
- private static CommentedFileConfig configFileInstance;
-
- public static void setupLatch() {
- Bukkit.getCommandMap().register("luminolconfig", "luminol", new LuminolConfigCommand());
+ public final Logger logger = LogManager.getLogger();
+ private final File baseConfigFolder;
+ private final File baseConfigFile;
+ private final String name;
+ private final String pack;
+ private final Set<IConfigModule> allInstanced = new HashSet<>();
+ private final Map<String, Object> stagedConfigMap = new HashMap<>();
+ private final Map<String, Object> defaultvalueMap = new HashMap<>();
+ public boolean alreadyInit = false;
+ private CommentedFileConfig configFileInstance;
+
+ public LuminolConfig(@NotNull File base, @NotNull String name, @NotNull String pack) {
+ this.baseConfigFolder = base;
+ this.name = name;
+ this.pack = pack;
+ this.baseConfigFile = new File(base, name + "_global_config.toml");
+ }
+
+ public void setupLatch() {
+ LuminolConfigCommand command = new LuminolConfigCommand(name);
+ Bukkit.getCommandMap().register(name + "config", name, command);
+ command.initConfig(this);
alreadyInit = true;
}
- public static void reload() {
- RegionizedServer.ensureGlobalTickThread("Reload luminol config off global region thread!");
+ public void reload() {
+ RegionizedServer.ensureGlobalTickThread("Reload " + name + " config off global region thread!");
dropAllInstanced();
try {
@@ -56,8 +67,8 @@ public class LuminolConfig {
}
@Contract(" -> new")
- public static @NotNull CompletableFuture<Void> reloadAsync() {
- return CompletableFuture.runAsync(LuminolConfig::reload, task -> RegionizedServer.getInstance().addTask(() -> {
+ public @NotNull CompletableFuture<Void> reloadAsync() {
+ return CompletableFuture.runAsync(this::reload, task -> RegionizedServer.getInstance().addTask(() -> {
try {
task.run();
} catch (Exception e) {
@@ -66,17 +77,17 @@ public class LuminolConfig {
}));
}
- public static void dropAllInstanced() {
+ public void dropAllInstanced() {
allInstanced.clear();
}
- public static void finalizeLoadConfig() {
+ public void finalizeLoadConfig() {
for (IConfigModule module : allInstanced) {
module.onLoaded(configFileInstance);
}
}
- public static void preLoadConfig() throws IOException {
+ public void preLoadConfig() throws IOException {
baseConfigFolder.mkdirs();
if (!baseConfigFile.exists()) {
@@ -98,21 +109,21 @@ public class LuminolConfig {
saveConfigs();
}
- private static void loadAllModules() throws IllegalAccessException {
+ private void loadAllModules() throws IllegalAccessException {
for (IConfigModule instanced : allInstanced) {
loadForSingle(instanced);
}
}
- private static void instanceAllModule() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
- for (Class<?> clazz : getClasses("me.earthme.luminol.config.modules")) {
+ private void instanceAllModule() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
+ for (Class<?> clazz : getClasses(pack)) {
if (IConfigModule.class.isAssignableFrom(clazz)) {
allInstanced.add((IConfigModule) clazz.getConstructor().newInstance());
}
}
}
- private static void loadForSingle(@NotNull IConfigModule singleConfigModule) throws IllegalAccessException {
+ private void loadForSingle(@NotNull IConfigModule singleConfigModule) throws IllegalAccessException {
final EnumConfigCategory category = singleConfigModule.getCategory();
Field[] fields = singleConfigModule.getClass().getDeclaredFields();
@@ -203,7 +214,7 @@ public class LuminolConfig {
}
}
- public static void removeConfig(String name, String[] keys) {
+ public void removeConfig(String name, String[] keys) {
configFileInstance.remove(name);
Object configAtPath = configFileInstance.get(String.join(".", keys));
if (configAtPath instanceof UnmodifiableConfig && ((UnmodifiableConfig) configAtPath).isEmpty()) {
@@ -211,7 +222,7 @@ public class LuminolConfig {
}
}
- public static void removeConfig(String[] keys) {
+ public void removeConfig(String[] keys) {
configFileInstance.remove(String.join(".", keys));
Object configAtPath = configFileInstance.get(String.join(".", Arrays.copyOfRange(keys, 1, keys.length)));
if (configAtPath instanceof UnmodifiableConfig && ((UnmodifiableConfig) configAtPath).isEmpty()) {
@@ -219,11 +230,11 @@ public class LuminolConfig {
}
}
- public static boolean setConfig(String[] keys, Object value) {
+ public boolean setConfig(String[] keys, Object value) {
return setConfig(String.join(".", keys), value);
}
- public static boolean setConfig(String key, Object value) {
+ public boolean setConfig(String key, Object value) {
if (configFileInstance.contains(key) && configFileInstance.get(key) != null) {
stagedConfigMap.put(key, value);
return true;
@@ -231,7 +242,7 @@ public class LuminolConfig {
return false;
}
- private static Object tryTransform(Class<?> targetType, Object value) {
+ private Object tryTransform(Class<?> targetType, Object value) {
if (!targetType.isAssignableFrom(value.getClass())) {
try {
if (targetType == Integer.class) {
@@ -255,27 +266,27 @@ public class LuminolConfig {
return value;
}
- public static void saveConfigs() {
+ public void saveConfigs() {
configFileInstance.save();
}
- public static void resetConfig(String[] keys) {
+ public void resetConfig(String[] keys) {
resetConfig(String.join(".", keys));
}
- public static void resetConfig(String key) {
+ public void resetConfig(String key) {
stagedConfigMap.put(key, null);
}
- public static String getConfig(String[] keys) {
+ public String getConfig(String[] keys) {
return getConfig(String.join(".", keys));
}
- public static String getConfig(String key) {
+ public String getConfig(String key) {
return configFileInstance.get(key).toString();
}
- public static List<String> completeConfigPath(String partialPath) {
+ public List<String> completeConfigPath(String partialPath) {
List<String> allPaths = getAllConfigPaths(partialPath);
List<String> result = new ArrayList<>();
@@ -295,13 +306,13 @@ public class LuminolConfig {
return result;
}
- private static List<String> getAllConfigPaths(String currentPath) {
+ private List<String> getAllConfigPaths(String currentPath) {
return defaultvalueMap.keySet().stream()
.filter(k -> k.startsWith(currentPath))
.toList();
}
- public static @NotNull Set<Class<?>> getClasses(String pack) {
+ public @NotNull Set<Class<?>> getClasses(String pack) {
Set<Class<?>> classes = new LinkedHashSet<>();
String packageDirName = pack.replace('.', '/');
Enumeration<URL> dirs;
@@ -332,7 +343,7 @@ public class LuminolConfig {
return classes;
}
- private static void findClassesInPackageByFile(String packageName, String packagePath, Set<Class<?>> classes) {
+ private void findClassesInPackageByFile(String packageName, String packagePath, Set<Class<?>> classes) {
File dir = new File(packagePath);
if (!dir.exists() || !dir.isDirectory()) {
@@ -356,7 +367,7 @@ public class LuminolConfig {
}
}
- private static void findClassesInPackageByJar(String packageName, Enumeration<JarEntry> entries, String packageDirName, Set<Class<?>> classes) {
+ private void findClassesInPackageByJar(String packageName, Enumeration<JarEntry> entries, String packageDirName, Set<Class<?>> classes) {
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
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 ded1f0667327a70a923ebda55c41b1c9094d3e37..57ddc0832b919b115b85c81ac30f136e32f1e77e 100644
--- a/src/main/java/me/earthme/luminol/config/modules/misc/ServerModNameConfig.java
@@ -0,0 +1,30 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Mon, 28 Jul 2025 23:02:47 +0800
Subject: [PATCH] Transformed Configs
diff --git a/src/main/java/me/earthme/luminol/config/modules/experiment/CommandBlockExecutionConfig.java b/src/main/java/me/earthme/luminol/config/modules/experiment/CommandBlockExecutionConfig.java
index 531214d7ab28ece4bf84e82b39d1c9f983f0fb0c..b68fa0427bb975e91e76f73ab4cc089a424937a3 100644
--- a/src/main/java/me/earthme/luminol/config/modules/experiment/CommandBlockExecutionConfig.java
+++ b/src/main/java/me/earthme/luminol/config/modules/experiment/CommandBlockExecutionConfig.java
@@ -5,6 +5,7 @@ import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigInfo;
public class CommandBlockExecutionConfig implements IConfigModule {
+ @me.earthme.luminol.config.flags.TransformedConfig(name = "command_block_enabled", category = {"experiment", "command"}, originInstance = "lophine")
@ConfigInfo(baseName = "enabled")
public static boolean enabled = false;
diff --git a/src/main/java/me/earthme/luminol/config/modules/misc/PaperPacketLimiterConfig.java b/src/main/java/me/earthme/luminol/config/modules/misc/PaperPacketLimiterConfig.java
index c269446dda05cfc848407f80f53820c8fcd0c7b4..e69ffae53aa24496a29a4c4890cdcb366d2bec12 100644
--- a/src/main/java/me/earthme/luminol/config/modules/misc/PaperPacketLimiterConfig.java
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/PaperPacketLimiterConfig.java
@@ -5,6 +5,7 @@ import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigInfo;
public class PaperPacketLimiterConfig implements IConfigModule {
+ @me.earthme.luminol.config.flags.TransformedConfig(name = "unlimit-packet", category = {"misc", "network"}, originInstance = "lophine")
@ConfigInfo(baseName = "force_disable")
public static boolean forceDisable = false;
@@ -0,0 +1,121 @@
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
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/d93e9766d3797d130f66179e70a1b374c546fef7/leaves-server/src/main/java/org/leavesmc/leaves/util/ItemOverstackUtils.java)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/src/main/java/org/leavesmc/leaves/util/ItemOverstackUtils.java b/src/main/java/org/leavesmc/leaves/util/ItemOverstackUtils.java
index 5995c499298454a060433746c64be5fcc8d202e2..3452399de0fa464ce04a7cf1a05b440346bef662 100644
--- a/src/main/java/org/leavesmc/leaves/util/ItemOverstackUtils.java
+++ b/src/main/java/org/leavesmc/leaves/util/ItemOverstackUtils.java
@@ -17,29 +17,30 @@
package org.leavesmc.leaves.util;
+import fun.bm.lophine.config.modules.misc.ContainerExpansionConfig;
//import io.papermc.paper.datacomponent.DataComponentTypes;
//import io.papermc.paper.datacomponent.item.ItemEnchantments;
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.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 net.minecraft.world.item.component.ItemContainerContents;
+import net.minecraft.world.level.block.ShulkerBoxBlock;
//import org.bukkit.enchantments.Enchantment;
-//import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.NotNull;
//import org.leavesmc.leaves.LeavesConfig;
import java.util.List;
-//import java.util.Objects;
+import java.util.Objects;
import java.util.Optional;
public class ItemOverstackUtils {
private static final List<ItemUtil> overstackUtils = List.of(
-/* new ShulkerBox(),
- new CurseEnchantedBook()*/
+ new ShulkerBox()
+ // new CurseEnchantedBook() // only use shulker box part
);
public static int getItemStackMaxCount(ItemStack stack) {
@@ -109,6 +110,7 @@ public class ItemOverstackUtils {
public static float getItemStackSignalStrength(int maxStackSize, ItemStack itemStack) {
float result = (float) itemStack.getCount() / Math.min(maxStackSize, itemStack.getMaxStackSize());
+ // only use shulker box part
/* if (LeavesConfig.modify.oldMC.allowGrindstoneOverstacking && CurseEnchantedBook.isCursedEnchantedBook(itemStack)) {
return result;
}*/
@@ -134,9 +136,9 @@ public class ItemOverstackUtils {
}
}
-/* private static class ShulkerBox implements ItemUtil {
+ private static class ShulkerBox implements ItemUtil {
public static boolean shulkerBoxCheck(@NotNull ItemStack stack1, @NotNull ItemStack stack2) {
- if (LeavesConfig.modify.shulkerBox.sameNbtStackable) {
+ if (ContainerExpansionConfig.nbtShulkerStackable) {
return Objects.equals(stack1.getComponents(), stack2.getComponents());
}
return shulkerBoxNoItem(stack1) && shulkerBoxNoItem(stack2) && Objects.equals(stack1.getComponents(), stack2.getComponents());
@@ -148,24 +150,24 @@ public class ItemOverstackUtils {
@Override
public boolean isEnabled() {
- return LeavesConfig.modify.shulkerBox.shulkerBoxStackSize > 1;
+ return ContainerExpansionConfig.shulkerCount > 1;
}
@Override
public boolean tryStackItems(ItemEntity self, ItemEntity other) {
ItemStack selfStack = self.getItem();
if (!isEnabled() ||
- !(selfStack.getItem() instanceof BlockItem blockItem) ||
- !(blockItem.getBlock() instanceof ShulkerBoxBlock)
+ !(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() != org.leavesmc.leaves.LeavesConfig.modify.shulkerBox.shulkerBoxStackSize) {
- int amount = Math.min(otherStack.getCount(), org.leavesmc.leaves.LeavesConfig.modify.shulkerBox.shulkerBoxStackSize - selfStack.getCount());
+ && shulkerBoxCheck(selfStack, otherStack)
+ && selfStack.getCount() != ContainerExpansionConfig.shulkerCount) {
+ int amount = Math.min(otherStack.getCount(), ContainerExpansionConfig.shulkerCount - selfStack.getCount());
selfStack.grow(amount);
self.setItem(selfStack);
@@ -187,14 +189,15 @@ public class ItemOverstackUtils {
@Override
public int getMaxServerStackCount(ItemStack stack) {
if (isEnabled() && stack.getItem() instanceof BlockItem bi &&
- bi.getBlock() instanceof ShulkerBoxBlock && (LeavesConfig.modify.shulkerBox.sameNbtStackable || shulkerBoxNoItem(stack))) {
- return LeavesConfig.modify.shulkerBox.shulkerBoxStackSize;
+ bi.getBlock() instanceof ShulkerBoxBlock && (ContainerExpansionConfig.nbtShulkerStackable || shulkerBoxNoItem(stack))) {
+ return ContainerExpansionConfig.shulkerCount;
}
return -1;
}
}
- public static class CurseEnchantedBook implements ItemUtil {
+// only use shulker box part
+/* public static class CurseEnchantedBook implements ItemUtil {
public static boolean isCursedEnchantedBook(ItemStack stack) {
ItemEnchantments enchantments = stack.getBukkitStack().getData(DataComponentTypes.STORED_ENCHANTMENTS);
if (enchantments == null || enchantments.enchantments().size() != 1) {
@@ -0,0 +1,60 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Mon, 7 Jul 2025 16:30:54 +0800
Subject: [PATCH] Add config to allow infinite trade
diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java
index 79242037e868a140513f032e74e8594c0c6ff41c..8570c5089b605125680f8558ae201550df2d0a47 100644
--- a/net/minecraft/world/entity/npc/Villager.java
+++ b/net/minecraft/world/entity/npc/Villager.java
@@ -490,8 +490,15 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
}
private void resendOffersToTradingPlayer() {
- MerchantOffers offers = this.getOffers();
+ // Lophine start - infinite trade
+ MerchantOffers offers = this.getOffers().copy();
Player tradingPlayer = this.getTradingPlayer();
+ if (fun.bm.lophine.config.modules.misc.VillagerConfig.villagerInfiniteTrade) {
+ for (MerchantOffer merchantOffer : offers) {
+ merchantOffer.maxUses = 524288;
+ }
+ }
+ // Lophine end - infinite trade
if (tradingPlayer != null && !offers.isEmpty()) {
tradingPlayer.sendMerchantOffers(
tradingPlayer.containerMenu.containerId,
diff --git a/net/minecraft/world/item/trading/Merchant.java b/net/minecraft/world/item/trading/Merchant.java
index 6e2fa442ddcd4ef3c4fe789b9ebac9dd409c9de7..28d743cc7beeb92a33683b8eea84096f94d90fb2 100644
--- a/net/minecraft/world/item/trading/Merchant.java
+++ b/net/minecraft/world/item/trading/Merchant.java
@@ -42,7 +42,14 @@ public interface Merchant {
new SimpleMenuProvider((containerId, inventory, player1) -> new MerchantMenu(containerId, inventory, this), displayName)
);
if (optionalInt.isPresent()) {
- MerchantOffers offers = this.getOffers();
+ // Lophine start - infinite trade
+ MerchantOffers offers = this.getOffers().copy();
+ if (fun.bm.lophine.config.modules.misc.VillagerConfig.villagerInfiniteTrade) {
+ for (MerchantOffer merchantOffer : offers) {
+ merchantOffer.maxUses = 5242884;
+ }
+ }
+ // Lophine end - infinite trade
if (!offers.isEmpty()) {
player.sendMerchantOffers(optionalInt.getAsInt(), offers, level, this.getVillagerXp(), this.showProgressBar(), this.canRestock());
}
diff --git a/net/minecraft/world/item/trading/MerchantOffer.java b/net/minecraft/world/item/trading/MerchantOffer.java
index 64c99df8ff305fa28c75dc03fc5ef8c61634ad84..21ec77d5167cfdf05043ea474165148fb2b0c2d9 100644
--- a/net/minecraft/world/item/trading/MerchantOffer.java
+++ b/net/minecraft/world/item/trading/MerchantOffer.java
@@ -196,7 +196,7 @@ public class MerchantOffer {
}
public boolean isOutOfStock() {
- return this.uses >= this.maxUses;
+ return (this.uses >= this.maxUses && !fun.bm.lophine.config.modules.misc.VillagerConfig.villagerInfiniteTrade); // Lophine - infinite trade
}
public void setToOutOfStock() {
@@ -1,33 +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:12 +0800
Subject: [PATCH] Rebrand to Lophine
diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
index fcf599846a38beeb8f094d5376c01d5a690f7a2f..48ef71f32b9aa385a8caecafcc8442f932ef8470 100644
--- a/net/minecraft/server/Main.java
+++ b/net/minecraft/server/Main.java
@@ -108,7 +108,7 @@ public class Main {
JvmProfiler.INSTANCE.start(Environment.SERVER);
}
- me.earthme.luminol.config.LuminolConfig.preLoadConfig(); // Luminol - Luminol config
+ fun.bm.lophine.config.LophineConfig.initConfigs(); // Lophine - Lophine config
io.papermc.paper.plugin.PluginInitializerManager.load(optionSet); // Paper
Bootstrap.bootStrap();
Bootstrap.validate();
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
index 5290b9cb524160717fb40704fb10fc0d0a37941b..1b60378cdbf5bc88f996010a61cceec7c2b9e2dc 100644
--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -171,8 +171,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.LuminolConfig.finalizeLoadConfig(); //Luminol - load config file
- me.earthme.luminol.config.LuminolConfig.setupLatch(); //Luminol - load config file
+ fun.bm.lophine.config.LophineConfig.loadConfigFiles(); //Luminol - load config file // Lophine - load config file
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()) {
@@ -1,19 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <wangxyper@163.com>
Date: Sun, 12 Jan 2025 10:36:31 +0800
Subject: [PATCH] Add config for command block command execution
diff --git a/net/minecraft/world/level/BaseCommandBlock.java b/net/minecraft/world/level/BaseCommandBlock.java
index 91e30f9c83259abc0589f4ee69c429cd4305d6ea..e1cc5576017358849e7c536accd14d2e6eb5c706 100644
--- a/net/minecraft/world/level/BaseCommandBlock.java
+++ b/net/minecraft/world/level/BaseCommandBlock.java
@@ -102,7 +102,7 @@ public abstract class BaseCommandBlock implements CommandSource {
}
public boolean performCommand(Level level) {
- if (true) return false; // Folia - region threading
+ if (!fun.bm.lophine.config.modules.experiment.CommandConfig.block) return false; // Folia - region threading // Luminol
if (level.isClientSide || level.getGameTime() == this.lastExecution) {
return false;
} else if ("Searge".equalsIgnoreCase(this.command)) {
@@ -5,7 +5,7 @@ Subject: [PATCH] Add config to disable end crystal check
diff --git a/net/minecraft/world/level/dimension/end/EndDragonFight.java b/net/minecraft/world/level/dimension/end/EndDragonFight.java
index 4fa8371c212dcc02b8cf5fd267b736e1cf3f50c1..4fb9244172b74c3c5fed984d5d3b968f76dd12be 100644
index 4fa8371c212dcc02b8cf5fd267b736e1cf3f50c1..3f1520bdd5ef708824712ea62974fedb35067f23 100644
--- a/net/minecraft/world/level/dimension/end/EndDragonFight.java
+++ b/net/minecraft/world/level/dimension/end/EndDragonFight.java
@@ -547,6 +547,8 @@ public class EndDragonFight {
@@ -13,7 +13,7 @@ index 4fa8371c212dcc02b8cf5fd267b736e1cf3f50c1..4fb9244172b74c3c5fed984d5d3b968f
blockPos = this.portalLocation;
}
+ // Luminol start - Disable end crystal check
+ if (!fun.bm.lophine.config.modules.misc.DisableEndCrystalCheckConfig.disableCheck) {
+ if (!fun.bm.lophine.config.modules.fixes.DisableEndCrystalCheckConfig.disableCheck) {
// Paper start - Perf: Do crystal-portal proximity check before entity lookup
if (placedEndCrystalPos != null) {
// The end crystal must be 0 or 1 higher than the portal origin
@@ -1,14 +1,14 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Tue, 29 Apr 2025 23:03:56 +0800
Subject: [PATCH] Add config to enable Cross Region Damage trace
Subject: [PATCH] Add config to enable cross region damage trace
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index c3201cb0ba6f928f658d4c74a1ef2b7e8900b1f6..a4eeb75eeb1cd69c60cd1c273f23f9744837148f 100644
index 11e6197dec541b28733715f0d7eaa4c7b834d632..6b7c92fdf562dfa46eee8e8acbc82b02fc3226c5 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -1330,6 +1330,13 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
@@ -1345,6 +1345,13 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
this.awardStat(Stats.ENTITY_KILLED_BY.get(killCredit.getType()));
killCredit.awardKillScore(this, cause);
this.createWitherRose(killCredit);
@@ -22,7 +22,7 @@ index c3201cb0ba6f928f658d4c74a1ef2b7e8900b1f6..a4eeb75eeb1cd69c60cd1c273f23f974
}
this.level().broadcastEntityEvent(this, (byte)3);
@@ -1353,6 +1360,24 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
@@ -1368,6 +1375,24 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
}
// Leaves end - exp fix
@@ -48,40 +48,10 @@ index c3201cb0ba6f928f658d4c74a1ef2b7e8900b1f6..a4eeb75eeb1cd69c60cd1c273f23f974
AABB aabb = new AABB(this.blockPosition()).inflate(32.0, 10.0, 32.0);
this.level()
diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
index 5b5e63c4dc10076ba33a8ddf0ebae96643b1161f..f5a1b2711a4eb910decb26b26c57ebfa7cedb231 100644
index 567d63ae589705e13403814b36e35d00c241a69e..3f4b1c54f9207ba1854a8c8da965cbaca5ea517d 100644
--- a/net/minecraft/world/entity/LivingEntity.java
+++ b/net/minecraft/world/entity/LivingEntity.java
@@ -1191,6 +1191,29 @@ public abstract class LivingEntity extends Entity implements Attackable {
}
}
+ // Luminol Start - raid revert adapt Cross Region Damage trace
+ public boolean addEffect(MobEffectInstance effectInstance, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause, boolean fireEvent, boolean async) {
+ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(entity)) {
+ return addEffect(effectInstance, entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN, true);
+ } else if (fun.bm.lophine.config.modules.experiment.EntityDamageSourceTraceConfig.enabled) {
+ postToEntityThreadAddEffect(effectInstance, entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN, true);
+ return true;
+ }
+ return false;
+ }
+
+ private void postToEntityThreadAddEffect(MobEffectInstance effectInstance, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause, boolean fireEvent) {
+ if (entity != null)
+ entity.getBukkitEntity().taskScheduler.schedule((Entity nmsEntity) -> {
+ try {
+ addEffect(effectInstance, nmsEntity, cause, fireEvent);
+ } catch (Throwable ex) {
+ LOGGER.error(ex.getMessage(), ex);
+ }
+ }, null, 1L );
+ }
+ // Luminol End - raid revert adapt Cross Region Damage trace
+
public boolean canBeAffected(MobEffectInstance effectInstance) {
if (this.getType().is(EntityTypeTags.IMMUNE_TO_INFESTED)) {
return !effectInstance.is(MobEffects.INFESTED);
@@ -1916,6 +1939,13 @@ public abstract class LivingEntity extends Entity implements Attackable {
@@ -1948,6 +1948,13 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
final LivingEntity killer = this.getKillCredit();
if (killer != null) {
killer.awardKillScore(this, damageSource);
@@ -95,7 +65,7 @@ index 5b5e63c4dc10076ba33a8ddf0ebae96643b1161f..f5a1b2711a4eb910decb26b26c57ebfa
}
}); // Paper end
this.postDeathDropItems(deathEvent); // Paper
@@ -1926,6 +1956,18 @@ public abstract class LivingEntity extends Entity implements Attackable {
@@ -1958,6 +1965,18 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
return deathEvent; // Paper
}
@@ -114,7 +84,7 @@ index 5b5e63c4dc10076ba33a8ddf0ebae96643b1161f..f5a1b2711a4eb910decb26b26c57ebfa
protected void dropEquipment(ServerLevel level) {
}
protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) {} // Paper - method for post death logic that cannot be ran before the event is potentially cancelled
@@ -2524,6 +2566,18 @@ public abstract class LivingEntity extends Entity implements Attackable {
@@ -2556,6 +2575,18 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
}
}
@@ -133,22 +103,3 @@ index 5b5e63c4dc10076ba33a8ddf0ebae96643b1161f..f5a1b2711a4eb910decb26b26c57ebfa
public final float getMaxHealth() {
return (float)this.getAttributeValue(Attributes.MAX_HEALTH);
}
diff --git a/net/minecraft/world/entity/raid/Raider.java b/net/minecraft/world/entity/raid/Raider.java
index 3ab6288dc1903da0aaf93ed8b5d9848e4fe663f1..61eab8abd59914a7bf613393059ec039bab1b8ee 100644
--- a/net/minecraft/world/entity/raid/Raider.java
+++ b/net/minecraft/world/entity/raid/Raider.java
@@ -156,7 +156,13 @@ public abstract class Raider extends PatrollingMonster {
net.minecraft.world.effect.MobEffectInstance mobeffect1 = new net.minecraft.world.effect.MobEffectInstance(net.minecraft.world.effect.MobEffects.BAD_OMEN, fun.bm.lophine.config.modules.misc.RaidChangesConfig.infinite ? net.minecraft.world.effect.MobEffectInstance.INFINITE_DURATION : 120000, i, false, false, true);
if (!serverLevel.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_DISABLE_RAIDS)) {
- entityhuman.addEffect(mobeffect1, entityhuman, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN, true); // CraftBukkit
+ if (fun.bm.lophine.config.modules.experiment.EntityDamageSourceTraceConfig.enabled) {
+ // Luminol start - Raid changes adapt DamageSource trace
+ entityhuman.addEffect(mobeffect1, entityhuman, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN, true, true);
+ } else {
+ entityhuman.addEffect(mobeffect1, entityhuman, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN, true); // CraftBukkit
+ }
+ // Luminol end - Raid changes adapt DamageSource trace
}
this.setPatrolLeader(false);
}
@@ -0,0 +1,59 @@
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] Add config to enable datapack command
diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java
index 8a90aa5c145f53afbb3f2371f97cb09974f30bbb..b6e10157c572dadb1bff18f8431166966eed46d9 100644
--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -192,7 +192,9 @@ public class Commands {
if(me.earthme.luminol.config.modules.experiment.CommandDataConfig.enabled) {
DataCommands.register(this.dispatcher); // Folia - region threading - TODO
}
- //DataPackCommand.register(this.dispatcher, context); // Folia - region threading - TODO
+ if (fun.bm.lophine.config.modules.experiment.CommandConfig.datapack) {
+ DataPackCommand.register(this.dispatcher, context); // Folia - region threading - TODO
+ }
//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 100e8fdc52c201d386e3c779d19f6b5abde39820..d08a5b553f4e5932d960fbcdeb6a43419730e32a 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -2352,6 +2352,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
return this.reloadResources(selectedIds, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN);
}
+ // Lophine start - region thread skip reload // TODO - add reload support
+ public void saveDatapacks(Collection<String> selectedIds) {
+ this.packRepository.setSelected(selectedIds, false); // Paper - add pendingReload flag to determine required pack loading - false as this is *after* a reload (see above)
+ WorldDataConfiguration worldDataConfiguration = new WorldDataConfiguration(
+ getSelectedPacks(this.packRepository, true), this.worldData.enabledFeatures()
+ );
+ this.worldData.setDataConfiguration(worldDataConfiguration);
+ }
+ // Lophine end - region thread skip reload // TODO - add reload support
+
public CompletableFuture<Void> reloadResources(Collection<String> selectedIds, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause cause) {
// Paper end - Add ServerResourcesReloadedEvent
CompletableFuture<Void> completableFuture = CompletableFuture.<ImmutableList>supplyAsync(
diff --git a/net/minecraft/server/commands/ReloadCommand.java b/net/minecraft/server/commands/ReloadCommand.java
index b540a3c3a02f7242578a63477e34b9063e86f1d2..9f0adf157872e1c68cd7752a24eacdc636e8d17a 100644
--- a/net/minecraft/server/commands/ReloadCommand.java
+++ b/net/minecraft/server/commands/ReloadCommand.java
@@ -16,6 +16,12 @@ public class ReloadCommand {
private static final Logger LOGGER = LogUtils.getLogger();
public static void reloadPacks(Collection<String> selectedIds, CommandSourceStack source) {
+ // Lophine start - region thread skip reload // TODO - add reload support
+ if (true) {
+ source.getServer().saveDatapacks(selectedIds);
+ return;
+ }
+ // Lophine end - region thread skip reload // TODO - add reload support
source.getServer().reloadResources(selectedIds, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.COMMAND).exceptionally(throwable -> { // Paper - Add ServerResourcesReloadedEvent
LOGGER.warn("Failed to execute reload", throwable);
source.sendFailure(Component.translatable("commands.reload.failure"));
@@ -1,11 +1,11 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <wangxyper@163.com>
Date: Wed, 5 Feb 2025 15:22:19 +0800
Subject: [PATCH] Add config to enable Raytracing tracker
Subject: [PATCH] Add config to enable raytracing tracker
diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
index ee53f78396f4377d3e5c2998826f231208066ef6..607091ecbd4a7261f2c0d839ee4dd1d27fa2190a 100644
index 6868b915bf3deb85783a638d4441a15fea6da2dc..70740381c6501c1a518c52b24381edd16792507f 100644
--- a/net/minecraft/server/level/ChunkMap.java
+++ b/net/minecraft/server/level/ChunkMap.java
@@ -1278,7 +1278,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
@@ -18,19 +18,19 @@ index ee53f78396f4377d3e5c2998826f231208066ef6..607091ecbd4a7261f2c0d839ee4dd1d2
double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1);
if (rangeY != -1) {
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index a023cc399fc8bfee7771e5ca6716578f89b7072f..88ce4001a2fa764a4dc41ca0132ad067d40d3d03 100644
index 75f327fff68097c1c9f65767552f686421e1e397..ed7421895311d89ad9726804893eff423868d375 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -140,7 +140,7 @@ import net.minecraft.world.scores.ScoreHolder;
import net.minecraft.world.scores.Team;
@@ -147,7 +147,7 @@ import net.minecraft.world.waypoints.WaypointTransmitter;
import org.jetbrains.annotations.Contract;
import org.slf4j.Logger;
-public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, ScoreHolder, DataComponentGetter, ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity, ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity { // Paper - rewrite chunk system // Paper - optimise entity tracker
+public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, ScoreHolder, DataComponentGetter, ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity, ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity, dev.tr7zw.entityculling.versionless.access.Cullable { // Paper - rewrite chunk system // Paper - optimise entity tracker // Luminol - Ray tracing entity tracker
// CraftBukkit start
private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger();
private static final int CURRENT_LEVEL = 2;
@@ -6106,4 +6106,46 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first snapTo
@@ -6467,4 +6467,46 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
// Paper end - Expose entity id counter
public boolean shouldTickHot() { return this.tickCount > 20 * 10 && this.isAlive(); } // KioCG
@@ -78,10 +78,10 @@ index a023cc399fc8bfee7771e5ca6716578f89b7072f..88ce4001a2fa764a4dc41ca0132ad067
+
}
diff --git a/net/minecraft/world/entity/EntityType.java b/net/minecraft/world/entity/EntityType.java
index 6b72ab233508e6df1eca34360ce76d102ee25a41..f39ee4605cc15102d6560afd1dad5f56dd53cf4e 100644
index 6f00ffa05d9597917574357e0069c9b056aa5ce2..5cc7a91e32f17d8d9585ae4844a4ed3b706e3273 100644
--- a/net/minecraft/world/entity/EntityType.java
+++ b/net/minecraft/world/entity/EntityType.java
@@ -1109,6 +1109,9 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
@@ -1119,6 +1119,9 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
public final int passengerTickTimerId;
public final int passengerInactiveTickTimerId;
// Folia end - profiler
@@ -92,10 +92,10 @@ index 6b72ab233508e6df1eca34360ce76d102ee25a41..f39ee4605cc15102d6560afd1dad5f56
public EntityType(
EntityType.EntityFactory<T> factory,
diff --git a/net/minecraft/world/entity/player/Player.java b/net/minecraft/world/entity/player/Player.java
index 543750bee43bfaaaf33f2cec53eb0de05e7a1dd2..a26ec6f1a1553bf0b415f382177c75f481e21180 100644
index 2842314c712449625e9275aff7707c916c3ea767..09b549fa9568aa1ae13b1b975db14a6268fdaa46 100644
--- a/net/minecraft/world/entity/player/Player.java
+++ b/net/minecraft/world/entity/player/Player.java
@@ -220,6 +220,25 @@ public abstract class Player extends LivingEntity {
@@ -229,6 +229,25 @@ public abstract class Player extends LivingEntity {
return (org.bukkit.craftbukkit.entity.CraftHumanEntity) super.getBukkitEntity();
}
// CraftBukkit end
@@ -119,9 +119,9 @@ index 543750bee43bfaaaf33f2cec53eb0de05e7a1dd2..a26ec6f1a1553bf0b415f382177c75f4
+ }
+ // Luminol end
public Player(Level level, BlockPos pos, float yRot, GameProfile gameProfile) {
public Player(Level level, GameProfile gameProfile) {
super(EntityType.PLAYER, level);
@@ -277,6 +296,26 @@ public abstract class Player extends LivingEntity {
@@ -287,6 +306,26 @@ public abstract class Player extends LivingEntity {
@Override
public void tick() {
@@ -148,7 +148,7 @@ index 543750bee43bfaaaf33f2cec53eb0de05e7a1dd2..a26ec6f1a1553bf0b415f382177c75f4
this.noPhysics = this.isSpectator();
if (this.isSpectator() || this.isPassenger()) {
this.setOnGround(false);
@@ -1415,6 +1454,7 @@ public abstract class Player extends LivingEntity {
@@ -1426,6 +1465,7 @@ public abstract class Player extends LivingEntity {
if (this.containerMenu != null && this.hasContainerOpen()) {
this.doCloseContainer();
}
@@ -1,204 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Fri, 11 Apr 2025 16:53:57 +0800
Subject: [PATCH] Add config to revert raid changes
diff --git a/net/minecraft/world/effect/BadOmenMobEffect.java b/net/minecraft/world/effect/BadOmenMobEffect.java
index 80f17f33f670018240c854df589cf90cdeab6e70..985f27d249810bf133a520d937b7006e57f553ec 100644
--- a/net/minecraft/world/effect/BadOmenMobEffect.java
+++ b/net/minecraft/world/effect/BadOmenMobEffect.java
@@ -22,6 +22,11 @@ class BadOmenMobEffect extends MobEffect {
&& !serverPlayer.isSpectator()
&& level.getDifficulty() != Difficulty.PEACEFUL
&& level.isVillage(serverPlayer.blockPosition())) {
+ // Leaves start - Revert raid changes
+ if (fun.bm.lophine.config.modules.misc.RaidChangesConfig.trigger) {
+ return level.getRaids().createOrExtendRaid(serverPlayer, serverPlayer.blockPosition()) == null;
+ }
+ // Leaves end - Revert raid changes
Raid raidAt = level.getRaidAt(serverPlayer.blockPosition());
if (raidAt == null || raidAt.getRaidOmenLevel() < raidAt.getMaxRaidOmenLevel()) {
serverPlayer.addEffect(new MobEffectInstance(MobEffects.RAID_OMEN, 600, amplifier));
diff --git a/net/minecraft/world/entity/raid/Raid.java b/net/minecraft/world/entity/raid/Raid.java
index dbb207c638a64b733dc21704033ff55ca1f44f1d..bcafacb6fce19212e99a3f04e0356b34304d92f1 100644
--- a/net/minecraft/world/entity/raid/Raid.java
+++ b/net/minecraft/world/entity/raid/Raid.java
@@ -340,7 +340,20 @@ public class Raid {
}
if (flag1) {
- this.waveSpawnPos = this.getValidSpawnPos(level);
+ // Luminol Start - Raid revert
+ if (!fun.bm.lophine.config.modules.misc.RaidChangesConfig.posRevert) {
+ this.waveSpawnPos = this.getValidSpawnPos(level);
+ } else {
+ int n4 = 0;
+ if (this.raidCooldownTicks < 100) {
+ n4 = 1;
+ }
+ if (this.raidCooldownTicks < 40) {
+ n4 = 2;
+ }
+ this.waveSpawnPos = this.getValidSpawnPos(level, n4);
+ }
+ // Luminol End - Raid revert
}
if (this.raidCooldownTicks == 300 || this.raidCooldownTicks % 20 == 0) {
@@ -375,7 +388,14 @@ public class Raid {
int i = 0;
while (this.shouldSpawnGroup()) {
- BlockPos blockPos = this.waveSpawnPos.orElseGet(() -> this.findRandomSpawnPos(level, 20));
+ // Luminol Start - Raid revert
+ BlockPos blockPos;
+ if (!fun.bm.lophine.config.modules.misc.RaidChangesConfig.posRevert) {
+ blockPos = this.waveSpawnPos.orElseGet(() -> this.findRandomSpawnPos(level, 20));
+ } else {
+ blockPos = this.waveSpawnPos.isPresent() ? this.waveSpawnPos.get() : this.findRandomSpawnPos(level, i, 20);
+ }
+ // Luminol End - Raid revert
if (blockPos != null) {
this.started = true;
this.spawnGroup(level, blockPos);
@@ -387,7 +407,7 @@ public class Raid {
i++;
}
- if (i > 5) {
+ if (i > (fun.bm.lophine.config.modules.misc.RaidChangesConfig.posRevert ? 3 : 5)) { // Luminol - Raid revert
org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(level, this, org.bukkit.event.raid.RaidStopEvent.Reason.UNSPAWNABLE); // CraftBukkit
this.stop();
break;
@@ -458,6 +478,17 @@ public class Raid {
return blockPos != null ? Optional.of(blockPos) : Optional.empty();
}
+ // Luminol Start - Raid revert
+ private Optional<BlockPos> getValidSpawnPos(ServerLevel level, int n) {
+ for (int i = 0; i < 3; ++i) {
+ BlockPos blockPos = this.findRandomSpawnPos(level, n, 1);
+ if (blockPos == null) continue;
+ return Optional.of(blockPos);
+ }
+ return Optional.empty();
+ }
+ // Luminol End - Raid revert
+
private boolean hasMoreWaves() {
return this.hasBonusWave() ? !this.hasSpawnedBonusWave() : !this.isFinalWave();
}
@@ -683,7 +714,7 @@ public class Raid {
int i2 = this.center.getX() + Mth.floor(Mth.cos(f2) * 32.0F * f) + level.random.nextInt(3) * Mth.floor(f);
int i3 = this.center.getZ() + Mth.floor(Mth.sin(f2) * 32.0F * f) + level.random.nextInt(3) * Mth.floor(f);
int height = level.getHeight(Heightmap.Types.WORLD_SURFACE, i2, i3);
- if (Mth.abs(height - this.center.getY()) <= 96) {
+ if (fun.bm.lophine.config.modules.misc.RaidChangesConfig.heightCheck || Mth.abs(height - this.center.getY()) <= 96) { // Leaves - Disable height check
mutableBlockPos.set(i2, height, i3);
if (!level.isVillage(mutableBlockPos) || i <= 7) {
int i4 = 10;
@@ -702,6 +733,26 @@ public class Raid {
return null;
}
+ // Luminol Start - Raid revert
+ @Nullable
+ private BlockPos findRandomSpawnPos(ServerLevel level, int n, int n2) {
+ int n3 = 2 - n;
+ BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+ SpawnPlacementType spawnPlacementType = SpawnPlacements.getPlacementType(EntityType.RAVAGER);
+ for (int i = 0; i < n2; ++i) {
+ float f = level.random.nextFloat() * ((float)Math.PI * 2);
+ int n4 = this.center.getX() + Mth.floor(Mth.cos(f) * 32.0f * (float)n3) + level.random.nextInt(5);
+ int n5 = this.center.getZ() + Mth.floor(Mth.sin(f) * 32.0f * (float)n3) + level.random.nextInt(5);
+ int n6 = level.getHeight(Heightmap.Types.WORLD_SURFACE, n4, n5);
+ mutableBlockPos.set(n4, n6, n5);
+ if (level.isVillage(mutableBlockPos) && n < 2) continue;
+ if (!level.hasChunksAt(mutableBlockPos.getX() - 10, mutableBlockPos.getZ() - 10, mutableBlockPos.getX() + 10, mutableBlockPos.getZ() + 10) || !level.isPositionEntityTicking(mutableBlockPos) || !spawnPlacementType.isSpawnPositionOk(level, mutableBlockPos, EntityType.RAVAGER) && (!level.getBlockState(mutableBlockPos.below()).is(Blocks.SNOW) || !level.getBlockState(mutableBlockPos).isAir())) continue;
+ return mutableBlockPos;
+ }
+ return null;
+ }
+ // Luminol End - Raid revert
+
private boolean addWaveMob(ServerLevel level, int wave, Raider raider) {
// Folia start - make raids thread-safe
if (!this.ownsRaid(level)) {
diff --git a/net/minecraft/world/entity/raid/Raider.java b/net/minecraft/world/entity/raid/Raider.java
index f6f36c15120da6c57c0cbea3743a0819252cb6cc..3ab6288dc1903da0aaf93ed8b5d9848e4fe663f1 100644
--- a/net/minecraft/world/entity/raid/Raider.java
+++ b/net/minecraft/world/entity/raid/Raider.java
@@ -127,6 +127,41 @@ public abstract class Raider extends PatrollingMonster {
currentRaid.removeFromRaid(serverLevel, this, false);
}
+
+ // Leaves start - Revert raid changes
+ if (fun.bm.lophine.config.modules.misc.RaidChangesConfig.effect && !this.hasRaid()) {
+ ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD);
+ net.minecraft.world.entity.player.Player entityhuman = null;
+ if (entity instanceof net.minecraft.world.entity.player.Player player) {
+ entityhuman = player;
+ } else if (entity instanceof net.minecraft.world.entity.animal.wolf.Wolf wolf) {
+ LivingEntity entityliving = wolf.getOwner();
+ if (wolf.isTame() && entityliving instanceof net.minecraft.world.entity.player.Player player) {
+ entityhuman = player;
+ }
+ }
+
+ if (entityhuman != null && !itemstack.isEmpty() && this.isCaptain()) {
+ net.minecraft.world.effect.MobEffectInstance mobeffect = entityhuman.getEffect(net.minecraft.world.effect.MobEffects.BAD_OMEN);
+ int i = 1;
+
+ if (mobeffect != null) {
+ i += mobeffect.getAmplifier();
+ entityhuman.removeEffectNoUpdate(net.minecraft.world.effect.MobEffects.BAD_OMEN);
+ } else {
+ --i;
+ }
+
+ i = net.minecraft.util.Mth.clamp(i, 0, 4);
+ net.minecraft.world.effect.MobEffectInstance mobeffect1 = new net.minecraft.world.effect.MobEffectInstance(net.minecraft.world.effect.MobEffects.BAD_OMEN, fun.bm.lophine.config.modules.misc.RaidChangesConfig.infinite ? net.minecraft.world.effect.MobEffectInstance.INFINITE_DURATION : 120000, i, false, false, true);
+
+ if (!serverLevel.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_DISABLE_RAIDS)) {
+ entityhuman.addEffect(mobeffect1, entityhuman, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN, true); // CraftBukkit
+ }
+ this.setPatrolLeader(false);
+ }
+ }
+ // Leaves end - Revert raid changes
}
super.die(cause);
@@ -155,7 +190,7 @@ public abstract class Raider extends PatrollingMonster {
}
public boolean hasRaid() {
- return this.level() instanceof ServerLevel serverLevel && (this.getCurrentRaid() != null || serverLevel.getRaidAt(this.blockPosition()) != null);
+ return !fun.bm.lophine.config.modules.misc.RaidChangesConfig.selfCheck && (this.level() instanceof ServerLevel serverLevel && (this.getCurrentRaid() != null || serverLevel.getRaidAt(this.blockPosition()) != null)); // Leaves - Disable raid self check
}
public boolean hasActiveRaid() {
diff --git a/net/minecraft/world/item/component/OminousBottleAmplifier.java b/net/minecraft/world/item/component/OminousBottleAmplifier.java
index 33907bb190ffa22ccf9ea424b1e536297878711a..2fca459f2c888079322f6b957bbee3ac0e33c4bc 100644
--- a/net/minecraft/world/item/component/OminousBottleAmplifier.java
+++ b/net/minecraft/world/item/component/OminousBottleAmplifier.java
@@ -29,7 +29,7 @@ public record OminousBottleAmplifier(int value) implements ConsumableListener, T
@Override
public void onConsume(Level level, LivingEntity entity, ItemStack stack, Consumable consumable) {
- entity.addEffect(new MobEffectInstance(MobEffects.BAD_OMEN, 120000, this.value, false, false, true)); // Paper - properly resend entities - diff on change for below
+ entity.addEffect(new MobEffectInstance(MobEffects.BAD_OMEN, fun.bm.lophine.config.modules.misc.RaidChangesConfig.infinite ? net.minecraft.world.effect.MobEffectInstance.INFINITE_DURATION : 120000, this.value, false, false, true)); // Paper - properly resend entities - diff on change for below // Luminol - Raid effect infinite
}
// Paper start - properly resend entities - collect packets for bundle
@@ -41,7 +41,7 @@ public record OminousBottleAmplifier(int value) implements ConsumableListener, T
@Override
public void addToTooltip(Item.TooltipContext context, Consumer<Component> tooltipAdder, TooltipFlag flag, DataComponentGetter componentGetter) {
- List<MobEffectInstance> list = List.of(new MobEffectInstance(MobEffects.BAD_OMEN, 120000, this.value, false, false, true));
+ List<MobEffectInstance> list = List.of(new MobEffectInstance(MobEffects.BAD_OMEN, fun.bm.lophine.config.modules.misc.RaidChangesConfig.infinite ? net.minecraft.world.effect.MobEffectInstance.INFINITE_DURATION : 120000, this.value, false, false, true)); // Luminol - Raid effect infinite
PotionContents.addPotionTooltip(list, tooltipAdder, 1.0F, context.tickRate());
}
}
@@ -5,7 +5,7 @@ Subject: [PATCH] Add config to enable tick command
diff --git a/io/papermc/paper/threadedregions/RegionizedServer.java b/io/papermc/paper/threadedregions/RegionizedServer.java
index 8e91ec81128bdbd5f78e1f04fe17bcbd6e5dc280..acdc5dbc34071dc4342f17fab0d483d775c9044d 100644
index e1842ab8f20271a8b20ab8462b09a4745a612a9a..8405157ec35a443e238fa6866772c839621d73d7 100644
--- a/io/papermc/paper/threadedregions/RegionizedServer.java
+++ b/io/papermc/paper/threadedregions/RegionizedServer.java
@@ -299,6 +299,11 @@ public final class RegionizedServer {
@@ -18,9 +18,9 @@ index 8e91ec81128bdbd5f78e1f04fe17bcbd6e5dc280..acdc5dbc34071dc4342f17fab0d483d7
+ }
+ // Luminol end - Add a config to enable tick command
// expire invalid click command callbacks
io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue((int)this.tickCount);
@@ -321,6 +326,13 @@ public final class RegionizedServer {
io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.ADVENTURE_CLICK_MANAGER.handleQueue((int)this.tickCount); // Paper // Folia - region threading - moved to global tick
io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.DIALOG_CLICK_MANAGER.handleQueue((int)this.tickCount); // Paper // Folia - region threading - moved to global tick
@@ -322,6 +327,13 @@ public final class RegionizedServer {
this.globalTick(world, tickCount);
}
@@ -34,7 +34,7 @@ index 8e91ec81128bdbd5f78e1f04fe17bcbd6e5dc280..acdc5dbc34071dc4342f17fab0d483d7
// tick connections
this.tickConnections();
@@ -454,7 +466,7 @@ public final class RegionizedServer {
@@ -455,7 +467,7 @@ public final class RegionizedServer {
}
private void tickTime(final ServerLevel world, final int tickCount) {
@@ -97,10 +97,10 @@ index 0357792de0ed8ec9058d1847c8b45c33ff365af6..4945c11f573a53df2f5be4beb7af6a3f
this.scheduler.regionFailed(this, false, thr);
// regionFailed will schedule a shutdown, so we should avoid letting this region tick further
diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java
index ea50e3c0c4044f03dc8257680814f808fbf3a177..63154426854c507738d646490ac76f531145fab2 100644
index b6e10157c572dadb1bff18f8431166966eed46d9..e2fc15d84fafde6847ecead23c55169449275e1b 100644
--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -214,7 +214,11 @@ public class Commands {
@@ -245,7 +245,11 @@ public class Commands {
TeleportCommand.register(this.dispatcher);
TellRawCommand.register(this.dispatcher, context);
//TestCommand.register(this.dispatcher, context); // Folia - region threading
@@ -114,10 +114,10 @@ index ea50e3c0c4044f03dc8257680814f808fbf3a177..63154426854c507738d646490ac76f53
TitleCommand.register(this.dispatcher, context);
//TriggerCommand.register(this.dispatcher); // Folia - region threading - TODO later
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 33d596c0f07d076886c0dd8eb93e901cdb56e438..87625b365e04ce05fa2b6cdf2003c839255c4393 100644
index d08a5b553f4e5932d960fbcdeb6a43419730e32a..a0f1db50adf5163a671d3d9e9df7adb7de4250c6 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -266,7 +266,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -267,7 +267,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
private String serverId;
public MinecraftServer.ReloadableResources resources;
private final StructureTemplateManager structureTemplateManager;
@@ -153,7 +153,7 @@ index 40338efd1c0e56d869d03f1d0687e7ff0fcbf11a..d0d90a25a10bbecfffceee1992af88c6
this.sprintTimeSpend = this.sprintTimeSpend + (System.nanoTime() - this.sprintTickStartTime);
}
diff --git a/net/minecraft/server/commands/TickCommand.java b/net/minecraft/server/commands/TickCommand.java
index 6b6c8ce49eda6806c8288d70848dd143ba2c4703..5d09d2c8bb45cc10b2a13100793249adc7b5a7e9 100644
index 126685fb266692082b432ed297a499149e921dfb..9d8f6025c420e651611da17e77a362c738228b72 100644
--- a/net/minecraft/server/commands/TickCommand.java
+++ b/net/minecraft/server/commands/TickCommand.java
@@ -14,7 +14,7 @@ import net.minecraft.server.ServerTickRateManager;
@@ -0,0 +1,92 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Mon, 7 Jul 2025 18:19:00 +0800
Subject: [PATCH] Add config to enable unsafe waypoint bar
diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java
index e2fc15d84fafde6847ecead23c55169449275e1b..b0a06cdfaa49237f372ecd131d47289160476b07 100644
--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -253,7 +253,11 @@ public class Commands {
TimeCommand.register(this.dispatcher);
TitleCommand.register(this.dispatcher, context);
//TriggerCommand.register(this.dispatcher); // Folia - region threading - TODO later
- //WaypointCommand.register(this.dispatcher, context); // Folia - region threading - TODO later
+ // Lophine start - unsafe waypoint bar
+ if (fun.bm.lophine.config.modules.experiment.WaypointConfig.forceEnableWaypointUnsafe) {
+ WaypointCommand.register(this.dispatcher, context); // Folia - region threading - TODO later
+ }
+ // Lophine end - unsafe waypoint bar
WeatherCommand.register(this.dispatcher);
WorldBorderCommand.register(this.dispatcher);
if (JvmProfiler.INSTANCE.isAvailable()) {
diff --git a/net/minecraft/server/waypoints/ServerWaypointManager.java b/net/minecraft/server/waypoints/ServerWaypointManager.java
index 0f8cacbb8fe55a60e2f0c98bf36c005b29f41a4b..36e9b027ce5213f8914728293633e78e9905c1b2 100644
--- a/net/minecraft/server/waypoints/ServerWaypointManager.java
+++ b/net/minecraft/server/waypoints/ServerWaypointManager.java
@@ -22,7 +22,14 @@ public class ServerWaypointManager implements WaypointManager<WaypointTransmitte
@Override
public void trackWaypoint(WaypointTransmitter waypoint) {
- // Folia - region threading
+ // Lophine start - unsafe waypoint bar
+ if (!fun.bm.lophine.config.modules.experiment.WaypointConfig.forceEnableWaypointUnsafe) return;
+ this.waypoints.add(waypoint);
+
+ for (ServerPlayer serverPlayer : this.players) {
+ this.createConnection(serverPlayer, waypoint);
+ }
+ // Lophine end - unsafe waypoint bar
}
@Override
@@ -50,14 +57,48 @@ public class ServerWaypointManager implements WaypointManager<WaypointTransmitte
public void addPlayer(ServerPlayer player) {
// Folia - region threading
+ // Lophine start - unsafe waypoint bar
+ if (!fun.bm.lophine.config.modules.experiment.WaypointConfig.forceEnableWaypointUnsafe) return;
+ this.players.add(player);
+
+ for (WaypointTransmitter waypointTransmitter : this.waypoints) {
+ this.createConnection(player, waypointTransmitter);
+ }
+
+ if (player.isTransmittingWaypoint()) {
+ this.trackWaypoint((WaypointTransmitter)player);
+ }
+ // Lophine end - unsafe waypoint bar
}
public void updatePlayer(ServerPlayer player) {
// Folia - region threading
+ // Lophine start - unsafe waypoint bar
+ if (!fun.bm.lophine.config.modules.experiment.WaypointConfig.forceEnableWaypointUnsafe) return;
+ Map<WaypointTransmitter, WaypointTransmitter.Connection> map = this.connections.row(player);
+ SetView<WaypointTransmitter> set = Sets.difference(this.waypoints, map.keySet());
+
+ for (Entry<WaypointTransmitter, WaypointTransmitter.Connection> entry : ImmutableSet.copyOf(map.entrySet())) {
+ this.updateConnection(player, entry.getKey(), entry.getValue());
+ }
+
+ for (WaypointTransmitter waypointTransmitter : set) {
+ this.createConnection(player, waypointTransmitter);
+ }
+ // Lophine end - unsafe waypoint bar
}
public void removePlayer(ServerPlayer player) {
// Folia - region threading
+ // Lophine start - unsafe waypoint bar
+ if (!fun.bm.lophine.config.modules.experiment.WaypointConfig.forceEnableWaypointUnsafe) return;
+ this.connections.row(player).values().removeIf(connection -> {
+ connection.disconnect();
+ return true;
+ });
+ this.untrackWaypoint((WaypointTransmitter)player);
+ this.players.remove(player);
+ // Lophine end - unsafe waypoint bar
}
public void breakAllConnections() {
@@ -0,0 +1,198 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 3 Aug 2025 15:24:01 +0800
Subject: [PATCH] Leaves: Base Protocol Core
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/ea91106ae57fc4cc14e2e0225009cf9919072f7f/leaves-server/minecraft-patches/features/0004-Leaves-Protocol-Core.patch)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/io/papermc/paper/threadedregions/RegionizedServer.java b/io/papermc/paper/threadedregions/RegionizedServer.java
index 8405157ec35a443e238fa6866772c839621d73d7..4edb6e6e6287f232f7eef45c747554264ec05319 100644
--- a/io/papermc/paper/threadedregions/RegionizedServer.java
+++ b/io/papermc/paper/threadedregions/RegionizedServer.java
@@ -334,6 +334,8 @@ public final class RegionizedServer {
}
// Luminol end - Add a config to enable tick command
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleTick(tickCount); // Leaves - protocol
+
// tick connections
this.tickConnections();
@@ -443,6 +445,8 @@ public final class RegionizedServer {
world.updateTickData();
+ MinecraftServer.getServer().handleTickCount(tickCount); // Lophine - reuse tick count
+
world.moonrise$getChunkTaskScheduler().chunkHolderManager.processTicketUpdates(); // required to eventually process ticket updates
}
diff --git a/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java b/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java
index fb263fa1f30a7dfcb7ec2656abfb38e5fe88eac9..56fd1ed7ccaf96e7eedea60fbdbf7f934939d563 100644
--- a/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java
+++ b/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java
@@ -40,13 +40,22 @@ public interface CustomPacketPayload {
@Override
public void encode(B buffer, CustomPacketPayload value) {
+ // Leaves start - protocol core
+ if (value instanceof org.leavesmc.leaves.protocol.core.LeavesCustomPayload payload) {
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.encode(buffer, payload);
+ return;
+ }
+ // Leaves end - protocol core
this.writeCap(buffer, value.type(), value);
}
@Override
public CustomPacketPayload decode(B buffer) {
ResourceLocation resourceLocation = buffer.readResourceLocation();
- return (CustomPacketPayload)this.findCodec(resourceLocation).decode(buffer);
+ // Leaves start - protocol core
+ var payload = org.leavesmc.leaves.protocol.core.LeavesProtocolManager.decode(resourceLocation, buffer);
+ return java.util.Objects.requireNonNullElseGet(payload, () -> this.findCodec(resourceLocation).decode(buffer));
+ // Leaves end - protocol core
}
};
}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index a0f1db50adf5163a671d3d9e9df7adb7de4250c6..af13707933af14171447d5ab6faacb64ddf194a7 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -305,6 +305,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// Folia start - regionised ticking
public final io.papermc.paper.threadedregions.RegionizedServer regionizedServer = new io.papermc.paper.threadedregions.RegionizedServer();
+ private int tickCount;
@Override
public <V> CompletableFuture<V> submit(java.util.function.Supplier<V> task) {
@@ -2226,9 +2227,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
return false;
}
+ // Lophine start - reuse tick count
public int getTickCount() {
- throw new UnsupportedOperationException(); // Folia - region threading
+ if (false) throw new UnsupportedOperationException(); // Folia - region threading
+ return this.tickCount;
+ }
+
+ public void handleTickCount(int tickCount1) {
+ tickCount = tickCount1;
}
+ // Lophine end - reuse tick count
public int getSpawnProtectionRadius() {
return 16;
diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
index 714813cba67da4a737682fe9276cb0d96fea336d..9677a3d62de11dafc8b9debe7a18ee6d14c7a64d 100644
--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
@@ -57,6 +57,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
public @Nullable String playerBrand;
public final java.util.Set<String> pluginMessagerChannels;
// Paper end - retain certain values
+ public final GameProfile profile; // Leaves - protocol core
public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie) {
this.server = server;
@@ -70,6 +71,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
this.pluginMessagerChannels = cookie.channels();
this.keepAlive = cookie.keepAlive();
// Paper end
+ this.profile = cookie.gameProfile(); // Leaves - protocol core
}
// Paper start - configuration phase API
@@ -160,6 +162,18 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
@Override
public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {
+ // Leaves start - protocol
+ if (packet.payload() instanceof org.leavesmc.leaves.protocol.core.LeavesCustomPayload leavesPayload) {
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePayload(org.leavesmc.leaves.protocol.core.ProtocolUtils.createSelector(this), leavesPayload);
+ return;
+ }
+ if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.DiscardedPayload(net.minecraft.resources.ResourceLocation id, byte[] data)) {
+ if (org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleBytebuf(org.leavesmc.leaves.protocol.core.ProtocolUtils.createSelector(this), id, io.netty.buffer.Unpooled.wrappedBuffer(data))) {
+ return;
+ }
+ }
+ // Leaves end - protocol
+
// Paper start
if (!(packet.payload() instanceof final net.minecraft.network.protocol.common.custom.DiscardedPayload discardedPayload)) {
return;
@@ -219,10 +233,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
final String channel = new String(data, from, length, java.nio.charset.StandardCharsets.US_ASCII);
if (register) {
bridge.addChannel(channel);
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleMinecraftRegister(channel, org.leavesmc.leaves.protocol.core.ProtocolUtils.createSelector(this)); // Leaves - protocol
} else {
bridge.removeChannel(channel);
}
- // Paper end
+ // Paper end
}
@Override
@@ -385,9 +400,9 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
net.minecraft.server.level.ServerPlayer player = serverGamePacketListener.player;
org.bukkit.event.player.PlayerKickEvent.Cause cause = disconnectionDetails.disconnectionReason().orElseThrow().game().orElse(org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN);
org.bukkit.event.player.PlayerKickEvent event = new org.bukkit.event.player.PlayerKickEvent(
- player.getBukkitEntity(),
- io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionDetails.reason()),
- rawLeaveMessage, cause
+ player.getBukkitEntity(),
+ io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionDetails.reason()),
+ rawLeaveMessage, cause
);
@@ -420,10 +435,10 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
private void disconnect0(DisconnectionDetails disconnectionDetails) {
this.connection
- .send(
- new ClientboundDisconnectPacket(disconnectionDetails.reason()),
- PacketSendListener.thenRun(() -> this.connection.disconnect(disconnectionDetails))
- );
+ .send(
+ new ClientboundDisconnectPacket(disconnectionDetails.reason()),
+ PacketSendListener.thenRun(() -> this.connection.disconnect(disconnectionDetails))
+ );
this.onDisconnect(disconnectionDetails);
this.connection.setReadOnly();
// CraftBukkit - Don't wait
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index 4ef2816210f06082f2209c51d04aad6bb188232b..593dbcda4b497291eb1ecdc64b947a8d279f8be6 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -423,6 +423,8 @@ public abstract class PlayerList {
//return; // Folia - region threading - must still allow the player to connect, as we must add to chunk map before handling disconnect
}
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player);
+
final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage();
if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure
@@ -605,6 +607,7 @@ public abstract class PlayerList {
return this.remove(player, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? player.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(player.getDisplayName())));
}
public @Nullable net.kyori.adventure.text.Component remove(ServerPlayer player, net.kyori.adventure.text.Component leaveMessage) {
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerLeave(player); // Leaves - protocol
// Paper end - Fix kick event leave message not being sent
ServerLevel serverLevel = player.level();
player.awardStat(Stats.LEAVE_GAME);
@@ -1520,6 +1523,7 @@ public abstract class PlayerList {
serverPlayer.connection.send(clientboundUpdateRecipesPacket);
serverPlayer.getRecipeBook().sendInitialRecipeBook(serverPlayer);
}
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleDataPackReload(); // Leaves - protocol core
}
public boolean isAllowCommandsForAllPlayers() {
@@ -1,107 +1,111 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Mon, 9 Jun 2025 19:23:10 +0800
Subject: [PATCH] Stackable ShulkerBoxes
Date: Sat, 26 Jul 2025 01:11:54 +0800
Subject: [PATCH] Leaves: Item overstack util
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
Some of changes is a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/9d32c5bd3df7c76055aff886ed9efda02e45a45a/leaves-server/minecraft-patches/features/0034-Stackable-ShulkerBoxes.patch)
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/46cccfc39cafbca1681620d03947bc3ad558327f/leaves-server/minecraft-patches/features/0034-Item-overstack-util.patch)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/commands/arguments/item/ItemInput.java b/net/minecraft/commands/arguments/item/ItemInput.java
index 643797124fe5a4489d0b7419b7e600c04f283ef2..3b4d97c76db9c742072c92dd7c8b39553c62a1f1 100644
index 643797124fe5a4489d0b7419b7e600c04f283ef2..04b4100da96aad50f08e8c59200eec934e7e873d 100644
--- a/net/minecraft/commands/arguments/item/ItemInput.java
+++ b/net/minecraft/commands/arguments/item/ItemInput.java
@@ -39,8 +39,9 @@ public class ItemInput {
@@ -39,11 +39,13 @@ public class ItemInput {
public ItemStack createItemStack(int count, boolean allowOversizedStacks) throws CommandSyntaxException {
ItemStack itemStack = new ItemStack(this.item, count);
itemStack.applyComponents(this.components);
- if (allowOversizedStacks && count > itemStack.getMaxStackSize()) {
- throw ERROR_STACK_TOO_BIG.create(this.getItemName(), itemStack.getMaxStackSize());
+ int maxCount = fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemStack); // Lophine - Stackable ShulkerBoxes
+ if (allowOversizedStacks && count > maxCount) { // Lophine - Stackable ShulkerBoxes
+ throw ERROR_STACK_TOO_BIG.create(this.getItemName(), maxCount); // Lophine - Stackable ShulkerBoxes
+ // Leaves start - item over-stack util
+ if (allowOversizedStacks && count > org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemStack)) {
+ throw ERROR_STACK_TOO_BIG.create(this.getItemName(), org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemStack));
} else {
return itemStack;
}
+ // Leaves end - item over-stack util
}
public String serialize(HolderLookup.Provider levelRegistry) {
diff --git a/net/minecraft/server/commands/GiveCommand.java b/net/minecraft/server/commands/GiveCommand.java
index f06753a8fa1138fa165de0a8367e04d79eaad381..267d9d2532302a7fb4ba55e8920dbb8891b2c232 100644
index f7002d6752aa03603eb06e298ba1d7130cf13826..72c87f7c0392d087440db9d267a608598f6feb6d 100644
--- a/net/minecraft/server/commands/GiveCommand.java
+++ b/net/minecraft/server/commands/GiveCommand.java
@@ -52,7 +52,7 @@ public class GiveCommand {
@@ -55,7 +55,7 @@ public class GiveCommand {
private static int giveItem(CommandSourceStack source, ItemInput item, Collection<ServerPlayer> targets, int count) throws CommandSyntaxException {
ItemStack itemStack = item.createItemStack(1, false);
final Component displayName = itemStack.getDisplayName(); // Paper - get display name early
- int maxStackSize = itemStack.getMaxStackSize();
+ int maxStackSize = fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemStack); // Lophine - Stackable ShulkerBoxes
+ int maxStackSize = org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemStack); // Leaves - item over-stack util
int i = maxStackSize * 100;
if (count > i) {
source.sendFailure(Component.translatable("commands.give.failed.toomanyitems", i, itemStack.getDisplayName()));
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 4fc3c9f2b04711569c12a0efa027601fdd0a40b5..a726ce12ecac55912606c4a1958b5afa07c491bc 100644
index d74a05822c6c145971dc526a62d652d35b4f5d9d..d63d8f7523938088bf9efc1c1b8ad9f0583338bb 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -3056,7 +3056,7 @@ public class ServerGamePacketListenerImpl
@@ -3079,7 +3079,7 @@ public class ServerGamePacketListenerImpl
} else if (slot.mayPlace(cursor)) {
if (ItemStack.isSameItemSameComponents(clickedItem, cursor)) {
int toPlace = packet.buttonNum() == 0 ? cursor.getCount() : 1;
- toPlace = Math.min(toPlace, clickedItem.getMaxStackSize() - clickedItem.getCount());
+ toPlace = Math.min(toPlace, fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(clickedItem) - clickedItem.getCount()); // Lophine - Stackable ShulkerBoxes
+ toPlace = Math.min(toPlace, org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(clickedItem) - clickedItem.getCount()); // Leaves - item over-stack util
toPlace = Math.min(toPlace, slot.container.getMaxStackSize() - clickedItem.getCount());
if (toPlace == 1) {
action = InventoryAction.PLACE_ONE;
@@ -3092,7 +3092,7 @@ public class ServerGamePacketListenerImpl
@@ -3115,7 +3115,7 @@ public class ServerGamePacketListenerImpl
}
} else if (ItemStack.isSameItemSameComponents(cursor, clickedItem)) {
if (clickedItem.getCount() >= 0) {
- if (clickedItem.getCount() + cursor.getCount() <= cursor.getMaxStackSize()) {
+ if (clickedItem.getCount() + cursor.getCount() <= fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(cursor)) { // Lophine - Stackable ShulkerBoxes
+ if (clickedItem.getCount() + cursor.getCount() <= org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(cursor)) { // Leaves - item over-stack util
// As of 1.5, this is result slots only
action = InventoryAction.PICKUP_ALL;
}
@@ -3351,6 +3351,7 @@ public class ServerGamePacketListenerImpl
@@ -3326,6 +3326,7 @@ public class ServerGamePacketListenerImpl
this.player.containerMenu.broadcastFullState();
} else {
this.player.containerMenu.broadcastChanges();
+ if (fun.bm.lophine.utils.ShulkerBoxesUtil.shouldCheck()) this.player.containerMenu.broadcastCarriedItem(); // Lophine - Stackable ShulkerBoxes
+ if (org.leavesmc.leaves.util.ItemOverstackUtils.hasOverstackingItem()) this.player.containerMenu.broadcastCarriedItem(); // Leaves - item over-stack util - force send carried item
}
if (packet.buttonNum() == Inventory.SLOT_OFFHAND && this.player.containerMenu != this.player.inventoryMenu) this.player.containerSynchronizer.sendOffHandSlotChange(); // Paper - update offhand data when the player is clicking in an inventory not their own as the sychronizer does not include offhand slots
if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.updateEquipmentOnPlayerActions) this.player.detectEquipmentUpdates(); // Paper - Force update attributes.
}
@@ -3463,7 +3464,7 @@ public class ServerGamePacketListenerImpl
@@ -3437,7 +3438,7 @@ public class ServerGamePacketListenerImpl
}
boolean flag1 = packet.slotNum() >= 1 && packet.slotNum() <= 45;
- boolean flag2 = itemStack.isEmpty() || itemStack.getCount() <= itemStack.getMaxStackSize();
+ boolean flag2 = itemStack.isEmpty() || itemStack.getCount() <= fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemStack); // Lophine - Stackable ShulkerBoxes
+ boolean flag2 = itemStack.isEmpty() || itemStack.getCount() <= org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemStack); // Leaves - item over-stack util
if (flag || (flag1 && !ItemStack.matches(this.player.inventoryMenu.getSlot(packet.slotNum()).getItem(), packet.itemStack()))) { // Insist on valid slot
// CraftBukkit start - Call click event
org.bukkit.inventory.InventoryView inventory = this.player.inventoryMenu.getBukkitView();
@@ -3505,6 +3506,7 @@ public class ServerGamePacketListenerImpl
@@ -3479,6 +3480,7 @@ public class ServerGamePacketListenerImpl
this.player.inventoryMenu.getSlot(packet.slotNum()).setByPlayer(itemStack);
this.player.inventoryMenu.setRemoteSlot(packet.slotNum(), itemStack);
this.player.inventoryMenu.broadcastChanges();
+ if (fun.bm.lophine.utils.ShulkerBoxesUtil.shouldCheck()) this.player.containerMenu.broadcastCarriedItem(); // Lophine - Stackable ShulkerBoxes
+ if (org.leavesmc.leaves.util.ItemOverstackUtils.hasOverstackingItem()) this.player.containerMenu.sendSingleSlot(packet.slotNum(), itemStack); // Leaves - item over-stack util - force send carried item
if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.updateEquipmentOnPlayerActions) this.player.detectEquipmentUpdates(); // Paper - Force update attributes.
} else if (flag && flag2) {
if (this.dropSpamThrottler.isUnderThreshold()) {
diff --git a/net/minecraft/world/Container.java b/net/minecraft/world/Container.java
index b382665cc125b8b5c0938e5e55984e4bf91d37ff..5d8bd05f966cf9befe8b8f3359b2fa4ce2428e98 100644
index 2afaf6a3f18af7889f4f3b35cee9bbb5e4ec01f2..da99b4bc7fe8460945070915073be141f9bd6778 100644
--- a/net/minecraft/world/Container.java
+++ b/net/minecraft/world/Container.java
@@ -32,6 +32,12 @@ public interface Container extends Clearable, Iterable<ItemStack> {
@@ -32,6 +32,12 @@ public interface Container extends Clearable, Iterable<ItemStack>, org.leavesmc.
return Math.min(this.getMaxStackSize(), stack.getMaxStackSize());
}
+ // Leaves start - stackable shulker boxes
+ // Leaves start - item over-stack util
+ default int getMaxStackLeaves(ItemStack stack) {
+ return Math.min(this.getMaxStackSize(), fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(stack));
+ return Math.min(this.getMaxStackSize(), org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(stack));
+ }
+ // Leaves end - stackable shulker boxes
+ // Leaves end - item over-stack util
+
void setChanged();
boolean stillValid(Player player);
diff --git a/net/minecraft/world/SimpleContainer.java b/net/minecraft/world/SimpleContainer.java
index 133e042371bcf84f1935903ec57d204e3b7abd84..201599988e20219b6a99bf1594ad6c0c19e09038 100644
index d907e24d563e27acab2f2bf9711b1755ea9afd19..75170c8d3be477a6ea2a1d62018a6ab630b0e54e 100644
--- a/net/minecraft/world/SimpleContainer.java
+++ b/net/minecraft/world/SimpleContainer.java
@@ -211,7 +211,7 @@ public class SimpleContainer implements Container, StackedContentsCompatible {
@@ -109,7 +113,7 @@ index 133e042371bcf84f1935903ec57d204e3b7abd84..201599988e20219b6a99bf1594ad6c0c
public void setItem(int index, ItemStack stack) {
this.items.set(index, stack);
- stack.limitSize(this.getMaxStackSize(stack));
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - stackable shulker boxes
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - item over-stack util
this.setChanged();
}
@@ -118,114 +122,88 @@ index 133e042371bcf84f1935903ec57d204e3b7abd84..201599988e20219b6a99bf1594ad6c0c
private void moveItemsBetweenStacks(ItemStack stack, ItemStack other) {
- int maxStackSize = this.getMaxStackSize(other);
+ int maxStackSize = this.getMaxStackLeaves(other); // Leaves - stackable shulker boxes
+ int maxStackSize = this.getMaxStackLeaves(other); // Leaves - item over-stack util
int min = Math.min(stack.getCount(), maxStackSize - other.getCount());
if (min > 0) {
other.grow(min);
diff --git a/net/minecraft/world/entity/item/ItemEntity.java b/net/minecraft/world/entity/item/ItemEntity.java
index a9cc35b4b253d9a7fd90f811af9be629b164fad0..1e0c8a256b9602a56f607274e93208bf5b04ee0d 100644
index fd4f1e63f980b6959fc80084651991f8f8bc6c34..e5e8c752129a6ebb0dd1315c9b8ec459be11ef07 100644
--- a/net/minecraft/world/entity/item/ItemEntity.java
+++ b/net/minecraft/world/entity/item/ItemEntity.java
@@ -281,10 +281,45 @@ public class ItemEntity extends Entity implements TraceableEntity {
@@ -283,10 +283,15 @@ public class ItemEntity extends Entity implements TraceableEntity, ChangePublish
private boolean isMergable() {
ItemStack item = this.getItem();
- return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < this.despawnRate && item.getCount() < item.getMaxStackSize(); // Paper - Alternative item-despawn-rate
+ return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < this.despawnRate && item.getCount() < fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(item); // Paper - Alternative item-despawn-rate // Lophine - Stackable ShulkerBoxes
+ }
+
+ // Lophine start - Stackable ShulkerBoxes
+ private boolean tryMergeShulkerBox(ItemEntity ote) {
+ ItemStack sf = this.getItem();
+ ItemStack ot = ote.getItem();
+ if (!fun.bm.lophine.utils.ShulkerBoxesUtil.checkShulkerBox(sf)) return false;
+ if (sf.getItem().equals(ot.getItem())
+ && fun.bm.lophine.utils.ShulkerBoxesUtil.emptyShulkerBoxCheck(sf)
+ && fun.bm.lophine.utils.ShulkerBoxesUtil.emptyShulkerBoxCheck(ot)
+ && Objects.equals(sf.getComponents(), ot.getComponents()) // empty block entity tags are cleaned up when spawning
+ && sf.getCount() != fun.bm.lophine.utils.ShulkerBoxesUtil.getShulkerBoxesMaxCountUnsafe()) {
+ int count = Math.min(ot.getCount(), fun.bm.lophine.utils.ShulkerBoxesUtil.getShulkerBoxesMaxCountUnsafe() - sf.getCount());
+ sf.grow(count);
+ this.setItem(sf);
+
+ this.pickupDelay = Math.max(ote.pickupDelay, this.pickupDelay);
+ this.age = Math.min(ote.getAge(), this.age);
+ ot.shrink(count);
+ if (ot.isEmpty()) {
+ ote.discard();
+ }
+ else {
+ ote.setItem(ot);
+ }
+ return true;
+ }
+ return false;
+ return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < this.despawnRate && item.getCount() < org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(item); // Paper - Alternative item-despawn-rate // Leaves - item over-stack util
}
+ // Lophine end - Stackable ShulkerBoxes
private void tryToMerge(ItemEntity itemEntity) {
+ // Lophine start - Stackable ShulkerBoxes
+ if (fun.bm.lophine.utils.ShulkerBoxesUtil.shouldCheck()
+ && this.tryMergeShulkerBox(itemEntity)) {
+ // Leaves start - item over-stack util
+ if (org.leavesmc.leaves.util.ItemOverstackUtils.tryStackItems(this, itemEntity)) {
+ return;
+ }
+ // Lophine end - Stackable ShulkerBoxes
+ // Leaves end - item over-stack util
ItemStack item = this.getItem();
ItemStack item1 = itemEntity.getItem();
if (Objects.equals(this.target, itemEntity.target) && areMergable(item, item1)) {
diff --git a/net/minecraft/world/entity/player/Inventory.java b/net/minecraft/world/entity/player/Inventory.java
index d9cb4f0ed0c4f63362c837aeef3c4194911455c9..9020f7c83e5bd19e9a9d3748d200ce5772a9cefa 100644
index a6bb436dc80daf6901dc027a6011ead4b3ed27e2..71df58ae26325a2d28b6679733c474c0c59f7654 100644
--- a/net/minecraft/world/entity/player/Inventory.java
+++ b/net/minecraft/world/entity/player/Inventory.java
@@ -149,8 +149,8 @@ public class Inventory implements Container, Nameable {
@@ -165,10 +165,12 @@ public class Inventory implements Container, Nameable {
}
private boolean hasRemainingSpaceForItem(ItemStack destination, ItemStack origin) {
+ // Leaves start - item over-stack util
return !destination.isEmpty()
- && destination.isStackable()
- && destination.getCount() < this.getMaxStackSize(destination)
+ && fun.bm.lophine.utils.ShulkerBoxesUtil.isStackable(destination) // Lophine - Stackable ShulkerBoxes
+ && destination.getCount() < fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(destination) // Lophine - Stackable ShulkerBoxes
+ && org.leavesmc.leaves.util.ItemOverstackUtils.isStackable(destination)
+ && destination.getCount() < org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(destination)
&& ItemStack.isSameItemSameComponents(destination, origin); // Paper - check if itemstack is stackable first
+ // Leaves end - item over-stack util
}
@@ -164,7 +164,7 @@ public class Inventory implements Container, Nameable {
// CraftBukkit start - Watch method above! :D
@@ -181,7 +183,7 @@ public class Inventory implements Container, Nameable {
}
if (this.hasRemainingSpaceForItem(itemInSlot, itemStack)) {
- remains -= (itemInSlot.getMaxStackSize() < this.getMaxStackSize() ? itemInSlot.getMaxStackSize() : this.getMaxStackSize()) - itemInSlot.getCount();
+ remains -= (fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemInSlot) < this.getMaxStackSize() ? fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemInSlot) : this.getMaxStackSize()) - itemInSlot.getCount(); // Lophine - Stackable ShulkerBoxes
+ remains -= (org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemInSlot) < this.getMaxStackSize() ? org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemInSlot) : this.getMaxStackSize()) - itemInSlot.getCount(); // Leaves - item over-stack util
}
if (remains <= 0) {
return itemStack.getCount();
@@ -173,7 +173,7 @@ public class Inventory implements Container, Nameable {
@@ -190,7 +192,7 @@ public class Inventory implements Container, Nameable {
ItemStack itemInOffhand = this.equipment.get(EquipmentSlot.OFFHAND);
if (this.hasRemainingSpaceForItem(itemInOffhand, itemStack)) {
- remains -= (itemInOffhand.getMaxStackSize() < this.getMaxStackSize() ? itemInOffhand.getMaxStackSize() : this.getMaxStackSize()) - itemInOffhand.getCount();
+ remains -= (fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemInOffhand) < this.getMaxStackSize() ? fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemInOffhand) : this.getMaxStackSize()) - itemInOffhand.getCount(); // Lophine - Stackable ShulkerBoxes
+ remains -= (org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemInOffhand) < this.getMaxStackSize() ? org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemInOffhand) : this.getMaxStackSize()) - itemInOffhand.getCount(); // Leaves - item over-stack util
}
if (remains <= 0) {
return itemStack.getCount();
@@ -297,7 +297,7 @@ public class Inventory implements Container, Nameable {
@@ -314,7 +316,7 @@ public class Inventory implements Container, Nameable {
this.setItem(slot, item);
}
- int i = this.getMaxStackSize(item) - item.getCount();
+ int i = this.getMaxStackLeaves(item) - item.getCount();
+ int i = this.getMaxStackLeaves(item) - item.getCount(); // Leaves - item over-stack util
int min = Math.min(count, i);
if (min == 0) {
return count;
@@ -403,7 +403,7 @@ public class Inventory implements Container, Nameable {
@@ -420,7 +422,7 @@ public class Inventory implements Container, Nameable {
break;
}
- int i = stack.getMaxStackSize() - this.getItem(slotWithRemainingSpace).getCount();
+ int i = fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(stack) - this.getItem(slotWithRemainingSpace).getCount(); // Lophine - Stackable ShulkerBoxes
+ int i = org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(stack) - this.getItem(slotWithRemainingSpace).getCount(); // Leaves - item over-stack util
if (this.add(slotWithRemainingSpace, stack.split(i)) && sendPacket && this.player instanceof ServerPlayer serverPlayer) {
serverPlayer.connection.send(this.createInventoryUpdatePacket(slotWithRemainingSpace));
}
diff --git a/net/minecraft/world/entity/player/StackedItemContents.java b/net/minecraft/world/entity/player/StackedItemContents.java
index 83ccde54c625d40dc595e000c533f60aa929bd5a..3206b599481eeab29c58bc1ca9cac6935da9963a 100644
index 83ccde54c625d40dc595e000c533f60aa929bd5a..6779503888e6d311758cffde582b0efbd66a33de 100644
--- a/net/minecraft/world/entity/player/StackedItemContents.java
+++ b/net/minecraft/world/entity/player/StackedItemContents.java
@@ -23,7 +23,7 @@ public class StackedItemContents {
@@ -233,12 +211,12 @@ index 83ccde54c625d40dc595e000c533f60aa929bd5a..3206b599481eeab29c58bc1ca9cac693
public void accountStack(ItemStack stack) {
- this.accountStack(stack, stack.getMaxStackSize());
+ this.accountStack(stack, fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(stack)); // Lophine - Stackable Shulker Boxes
+ this.accountStack(stack, org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(stack)); // Leaves - item over-stack util
}
public void accountStack(ItemStack stack, int maxStackSize) {
diff --git a/net/minecraft/world/entity/vehicle/ContainerEntity.java b/net/minecraft/world/entity/vehicle/ContainerEntity.java
index feebd1610ebd3c26a337259c14f5c774dc72b937..7df6ff842e41763aec2d88d1f8a5f7503932d905 100644
index 02d2efef2dc0f0e12eac0c71fa290af706f7694d..99f109e2653eff10c011f380694bd77a76381cee 100644
--- a/net/minecraft/world/entity/vehicle/ContainerEntity.java
+++ b/net/minecraft/world/entity/vehicle/ContainerEntity.java
@@ -163,7 +163,7 @@ public interface ContainerEntity extends Container, MenuProvider {
@@ -246,62 +224,61 @@ index feebd1610ebd3c26a337259c14f5c774dc72b937..7df6ff842e41763aec2d88d1f8a5f750
this.unpackChestVehicleLootTable(null);
this.getItemStacks().set(slot, stack);
- stack.limitSize(this.getMaxStackSize(stack));
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - stackable shulker boxes
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - item over-stack util
}
default SlotAccess getChestVehicleSlot(final int index) {
diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java
index a9cf0ef95b706f05060bbcd2a4f5a03c60f783d2..45ff5b9a50bdec611f7c1aa4cb77fb0b13b6910c 100644
index fcb2e0a722ed94acf08da06f06b20f42652896bc..9489a50df2de3d641235153c5a0217d96c1f4fa9 100644
--- a/net/minecraft/world/inventory/AbstractContainerMenu.java
+++ b/net/minecraft/world/inventory/AbstractContainerMenu.java
@@ -234,6 +234,14 @@ public abstract class AbstractContainerMenu {
return list;
@@ -295,6 +295,13 @@ public abstract class AbstractContainerMenu {
this.sendAllDataToRemote();
}
+ // Lophine start - Stackable ShulkerBoxes
+ public void boardcastChangesSigleSlot(int slot, ItemStack item) {
+ // Leaves start - item over-stack util
+ public void sendSingleSlot(int slotIndex, ItemStack item) {
+ if (this.synchronizer != null) {
+ this.synchronizer.sendSlotChange(this, slot, item);
+ this.synchronizer.sendSlotChange(this, slotIndex, item);
+ }
+ }
+ // Lophine end - Stackable ShulkerBoxes
+
public void broadcastChanges() {
for (int i = 0; i < this.slots.size(); i++) {
ItemStack item = this.slots.get(i).getItem();
@@ -427,7 +435,7 @@ public abstract class AbstractContainerMenu {
+ // Leaves end - item over-stack util
private void updateDataSlotListeners(int slotIndex, int value) {
for (ContainerListener containerListener : this.containerListeners) {
containerListener.dataChanged(this, slotIndex, value);
@@ -451,7 +458,7 @@ public abstract class AbstractContainerMenu {
&& (this.quickcraftType == 2 || carried1.getCount() >= this.quickcraftSlots.size())
&& this.canDragTo(slot1)) {
int i2 = slot1.hasItem() ? slot1.getItem().getCount() : 0;
- int min = Math.min(itemStack.getMaxStackSize(), slot1.getMaxStackSize(itemStack));
+ int min = Math.min(fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemStack), slot1.getMaxStackSize(itemStack)); // Lophine - Stackable ShulkerBoxes
+ int min = Math.min(org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemStack), slot1.getMaxStackSize(itemStack)); // Leaves - item over-stack util
int min1 = Math.min(getQuickCraftPlaceCount(this.quickcraftSlots, this.quickcraftType, itemStack) + i2, min);
count -= min1 - i2;
// slot1.setByPlayer(itemStack.copyWithCount(min1));
@@ -541,7 +549,7 @@ public abstract class AbstractContainerMenu {
@@ -563,7 +570,7 @@ public abstract class AbstractContainerMenu {
slot.setByPlayer(carried2);
}
} else if (ItemStack.isSameItemSameComponents(carried, carried2)) {
- Optional<ItemStack> optional1 = slot.tryRemove(carried.getCount(), carried2.getMaxStackSize() - carried2.getCount(), player);
+ Optional<ItemStack> optional1 = slot.tryRemove(carried.getCount(), fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(carried2) - carried2.getCount(), player); // Lophine - Stackable ShulkerBoxes
+ Optional<ItemStack> optional1 = slot.tryRemove(carried.getCount(), org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(carried2) - carried2.getCount(), player); // Leaves - item over-stack util
optional1.ifPresent(itemStack2 -> {
carried2.grow(itemStack2.getCount());
slot.onTake(player, itemStack2);
@@ -603,7 +611,7 @@ public abstract class AbstractContainerMenu {
@@ -625,7 +632,7 @@ public abstract class AbstractContainerMenu {
Slot slot2 = this.slots.get(slotId);
if (slot2.hasItem()) {
ItemStack itemStack = slot2.getItem();
- this.setCarried(itemStack.copyWithCount(itemStack.getMaxStackSize()));
+ this.setCarried(itemStack.copyWithCount(fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemStack))); // Lophine - Stackable ShulkerBoxes
+ this.setCarried(itemStack.copyWithCount(org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemStack))); // Leaves - item over-stack util
}
} else if (clickType == ClickType.THROW && this.getCarried().isEmpty() && slotId >= 0) {
Slot slot2 = this.slots.get(slotId);
@@ -634,15 +642,15 @@ public abstract class AbstractContainerMenu {
@@ -656,15 +663,15 @@ public abstract class AbstractContainerMenu {
int maxStackSize = button == 0 ? 1 : -1;
for (int i3 = 0; i3 < 2; i3++) {
- for (int i4 = count; i4 >= 0 && i4 < this.slots.size() && itemStack.getCount() < itemStack.getMaxStackSize(); i4 += maxStackSize) {
+ for (int i4 = count; i4 >= 0 && i4 < this.slots.size() && itemStack.getCount() < fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemStack); i4 += maxStackSize) { // Lophine - Stackable ShulkerBoxes
+ for (int i4 = count; i4 >= 0 && i4 < this.slots.size() && itemStack.getCount() < org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemStack); i4 += maxStackSize) { // Leaves - item over-stack util
Slot slot3 = this.slots.get(i4);
if (slot3.hasItem()
&& canItemQuickReplace(slot3, itemStack, true)
@@ -310,49 +287,49 @@ index a9cf0ef95b706f05060bbcd2a4f5a03c60f783d2..45ff5b9a50bdec611f7c1aa4cb77fb0b
ItemStack item1 = slot3.getItem();
- if (i3 != 0 || item1.getCount() != item1.getMaxStackSize()) {
- ItemStack itemStack1 = slot3.safeTake(item1.getCount(), itemStack.getMaxStackSize() - itemStack.getCount(), player);
+ if (i3 != 0 || item1.getCount() != fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(item1)) { // Lophine - stackable shulker boxes
+ ItemStack itemStack1 = slot3.safeTake(item1.getCount(), fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemStack) - itemStack.getCount(), player); // Lophine - stackable shulker boxes
+ if (i3 != 0 || item1.getCount() != org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(item1)) { // Leaves - item over-stack util
+ ItemStack itemStack1 = slot3.safeTake(item1.getCount(), org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemStack) - itemStack.getCount(), player); // Leaves - item over-stack util
itemStack.grow(itemStack1.getCount());
}
}
@@ -744,7 +752,7 @@ public abstract class AbstractContainerMenu {
@@ -766,7 +773,7 @@ public abstract class AbstractContainerMenu {
i = endIndex - 1;
}
- if (stack.isStackable()) {
+ if (fun.bm.lophine.utils.ShulkerBoxesUtil.isStackable(stack)) { // Lophine - Stackable ShulkerBoxes
+ if (org.leavesmc.leaves.util.ItemOverstackUtils.isStackable(stack)) { // Leaves - item over-stack util
while (!stack.isEmpty() && (reverseDirection ? i >= startIndex : i < endIndex)) {
Slot slot = this.slots.get(i);
ItemStack item = slot.getItem();
@@ -845,7 +853,7 @@ public abstract class AbstractContainerMenu {
@@ -867,7 +874,7 @@ public abstract class AbstractContainerMenu {
public static boolean canItemQuickReplace(@Nullable Slot slot, ItemStack stack, boolean stackSizeMatters) {
boolean flag = slot == null || !slot.hasItem();
return !flag && ItemStack.isSameItemSameComponents(stack, slot.getItem())
- ? slot.getItem().getCount() + (stackSizeMatters ? 0 : stack.getCount()) <= stack.getMaxStackSize()
+ ? slot.getItem().getCount() + (stackSizeMatters ? 0 : stack.getCount()) <= fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(stack) // Lophine - Stackable ShulkerBoxes
+ ? slot.getItem().getCount() + (stackSizeMatters ? 0 : stack.getCount()) <= org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(stack) // Leaves - item over-stack util
: flag;
}
@@ -853,7 +861,7 @@ public abstract class AbstractContainerMenu {
@@ -875,7 +882,7 @@ public abstract class AbstractContainerMenu {
return switch (type) {
case 0 -> Mth.floor((float)stack.getCount() / slots.size());
case 1 -> 1;
- case 2 -> stack.getMaxStackSize();
+ case 2 -> fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(stack); // Lophine - Stackable ShulkerBoxes
+ case 2 -> org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(stack); // Leaves - item over-stack util
default -> stack.getCount();
};
}
@@ -875,7 +883,7 @@ public abstract class AbstractContainerMenu {
@@ -898,7 +905,7 @@ public abstract class AbstractContainerMenu {
for (int i = 0; i < container.getContainerSize(); i++) {
ItemStack item = container.getItem(i);
if (!item.isEmpty()) {
- f += (float)item.getCount() / container.getMaxStackSize(item);
+ f += (float)item.getCount() / container.getMaxStackLeaves(item); // Leaves - stackable shulker boxes
+ f += Math.clamp((float) item.getCount() / container.getMaxStackSize(item), 0f, 1f); // Leaves - item over-stack util
}
}
diff --git a/net/minecraft/world/inventory/MerchantContainer.java b/net/minecraft/world/inventory/MerchantContainer.java
index 1e5dfb1f9e371fa23cdfa9280797aa0e183d4cd2..cf87267130c0aebd38206556261929d6f6383bc9 100644
index 1e5dfb1f9e371fa23cdfa9280797aa0e183d4cd2..1a593d5bfc4574fd071604105422cd1d395648b8 100644
--- a/net/minecraft/world/inventory/MerchantContainer.java
+++ b/net/minecraft/world/inventory/MerchantContainer.java
@@ -109,7 +109,7 @@ public class MerchantContainer implements Container {
@@ -360,12 +337,12 @@ index 1e5dfb1f9e371fa23cdfa9280797aa0e183d4cd2..cf87267130c0aebd38206556261929d6
public void setItem(int index, ItemStack stack) {
this.itemStacks.set(index, stack);
- stack.limitSize(this.getMaxStackSize(stack));
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - stackable shulker boxes
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - item over-stack util
if (this.isPaymentSlot(index)) {
this.updateSellItem();
}
diff --git a/net/minecraft/world/inventory/Slot.java b/net/minecraft/world/inventory/Slot.java
index 5ceb8964476b40db4511bec91ff13c4f522a1357..dd978d1590fb896775a240ed87e38d85744017d3 100644
index 5ceb8964476b40db4511bec91ff13c4f522a1357..f416d7976e41a06b5a8be93485eaa233ffceda5c 100644
--- a/net/minecraft/world/inventory/Slot.java
+++ b/net/minecraft/world/inventory/Slot.java
@@ -75,7 +75,7 @@ public class Slot {
@@ -373,41 +350,41 @@ index 5ceb8964476b40db4511bec91ff13c4f522a1357..dd978d1590fb896775a240ed87e38d85
public int getMaxStackSize(ItemStack stack) {
- return Math.min(this.getMaxStackSize(), stack.getMaxStackSize());
+ return Math.min(this.getMaxStackSize(), fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(stack)); // Lophine - Stackable ShulkerBoxes
+ return Math.min(this.getMaxStackSize(), org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(stack)); // Leaves - item over-stack util
}
@Nullable
diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java
index 09455150810e4fb6e4637fdb9e2e8a30fe0bc247..9ea26913cbaa7571a107b421614380e2755f4430 100644
index 33c3a5e39e8e1aa023baab76ef94f8aed481480e..a27e1d53abc2d8b8912226add33510067e10ed52 100644
--- a/net/minecraft/world/item/ItemStack.java
+++ b/net/minecraft/world/item/ItemStack.java
@@ -165,7 +165,7 @@ public final class ItemStack implements DataComponentHolder {
@@ -166,7 +166,7 @@ public final class ItemStack implements DataComponentHolder, ChangePublisher<net
@Deprecated
@Nullable
private Item item;
- PatchedDataComponentMap components;
+ public PatchedDataComponentMap components; // Leaves - stackable shulker boxes
+ public PatchedDataComponentMap components; // Leaves - item over-stack util
@Nullable
private Entity entityRepresentation;
@@ -192,7 +192,8 @@ public final class ItemStack implements DataComponentHolder {
@@ -193,7 +193,8 @@ public final class ItemStack implements DataComponentHolder, ChangePublisher<net
} else {
Holder<Item> holder = Item.STREAM_CODEC.decode(buffer);
DataComponentPatch dataComponentPatch = codec.decode(buffer);
- return new ItemStack(holder, varInt, dataComponentPatch);
+ ItemStack itemStack = new ItemStack(holder, varInt, dataComponentPatch);
+ return fun.bm.lophine.utils.ShulkerBoxesUtil.decodeMaxStackSize(itemStack);
+ return org.leavesmc.leaves.util.ItemOverstackUtils.decodeMaxStackSize(itemStack);
}
}
@@ -201,13 +202,15 @@ public final class ItemStack implements DataComponentHolder {
@@ -202,13 +203,15 @@ public final class ItemStack implements DataComponentHolder, ChangePublisher<net
if (value.isEmpty() || value.getItem() == null) { // CraftBukkit - NPE fix itemstack.getItem()
buffer.writeVarInt(0);
} else {
- buffer.writeVarInt(io.papermc.paper.util.sanitizer.ItemComponentSanitizer.sanitizeCount(io.papermc.paper.util.sanitizer.ItemObfuscationSession.currentSession(), value, value.getCount())); // Paper - potentially sanitize count
- Item.STREAM_CODEC.encode(buffer, value.getItemHolder());
+ // Leaves start - stackable shulker boxes
+ final ItemStack itemStack = fun.bm.lophine.utils.ShulkerBoxesUtil.encodeMaxStackSize(value.copy());
+ // Leaves start - item over-stack util
+ final ItemStack itemStack = org.leavesmc.leaves.util.ItemOverstackUtils.encodeMaxStackSize(value.copy());
+ buffer.writeVarInt(io.papermc.paper.util.sanitizer.ItemComponentSanitizer.sanitizeCount(io.papermc.paper.util.sanitizer.ItemObfuscationSession.currentSession(), itemStack, itemStack.getCount())); // Paper - potentially sanitize count
+ Item.STREAM_CODEC.encode(buffer, itemStack.getItemHolder());
// Paper start - adventure; conditionally render translatable components
@@ -420,50 +397,17 @@ index 09455150810e4fb6e4637fdb9e2e8a30fe0bc247..9ea26913cbaa7571a107b421614380e2
} finally {
net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(prev);
}
@@ -302,7 +305,7 @@ public final class ItemStack implements DataComponentHolder {
@@ -303,7 +306,7 @@ public final class ItemStack implements DataComponentHolder, ChangePublisher<net
for (ItemStack itemStack : itemContainerContents.nonEmptyItems()) {
int count = itemStack.getCount();
- int maxStackSize = itemStack.getMaxStackSize();
+ int maxStackSize = fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemStack); // Leaves - stackable shulker boxes
+ int maxStackSize = org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemStack); // Leaves - item over-stack util
if (count > maxStackSize) {
return DataResult.error(() -> "Item stack with count of " + count + " was larger than maximum: " + maxStackSize);
}
diff --git a/net/minecraft/world/level/block/AbstractCauldronBlock.java b/net/minecraft/world/level/block/AbstractCauldronBlock.java
index ad3f32888afd8b5f0038445a1b0fcc8cacec9fe2..3e3d516ff62f49a3cc00375d173af137b555d515 100644
--- a/net/minecraft/world/level/block/AbstractCauldronBlock.java
+++ b/net/minecraft/world/level/block/AbstractCauldronBlock.java
@@ -62,9 +62,27 @@ public abstract class AbstractCauldronBlock extends Block {
ItemStack stack, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hitResult
) {
CauldronInteraction cauldronInteraction = this.interactions.map().get(stack.getItem());
- return cauldronInteraction.interact(state, level, pos, player, hand, stack, hitResult.getDirection()); // Paper - pass hit direction
+ return wrapInteractor(cauldronInteraction, state, level, pos, player, hand, stack, hitResult.getDirection()); // Paper - pass hit direction // Leaves - stackable shulker boxes
}
+ // Leaves start - stackable shulker boxes
+ private InteractionResult wrapInteractor(CauldronInteraction cauldronBehavior, BlockState blockState, Level world, BlockPos blockPos, Player playerEntity, InteractionHand hand, ItemStack itemStack, net.minecraft.core.Direction hitDirection) {
+ int count = -1;
+ if (fun.bm.lophine.utils.ShulkerBoxesUtil.shouldCheck() && itemStack.getItem() instanceof net.minecraft.world.item.BlockItem bi &&
+ bi.getBlock() instanceof ShulkerBoxBlock) {
+ count = itemStack.getCount();
+ }
+ InteractionResult result = cauldronBehavior.interact(blockState, world, blockPos, playerEntity, hand, itemStack, hitDirection);
+ if (count > 0 && result.consumesAction()) {
+ ItemStack current = playerEntity.getItemInHand(hand);
+ if (current.getItem() instanceof net.minecraft.world.item.BlockItem bi && bi.getBlock() instanceof ShulkerBoxBlock) {
+ current.setCount(count);
+ }
+ }
+ return result;
+ }
+ // Leaves end - stackable shulker boxes
+
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return SHAPE;
diff --git a/net/minecraft/world/level/block/CrafterBlock.java b/net/minecraft/world/level/block/CrafterBlock.java
index 38b03c7b02bdfc579e5e126c12de3d878e26d188..33e24f2c3b63b2d3b55dfae2f2e55869abeed055 100644
index 38b03c7b02bdfc579e5e126c12de3d878e26d188..caf31218a1aff84367da126ee1dc46b8c8a1e5d1 100644
--- a/net/minecraft/world/level/block/CrafterBlock.java
+++ b/net/minecraft/world/level/block/CrafterBlock.java
@@ -192,7 +192,7 @@ public class CrafterBlock extends BaseEntityBlock {
@@ -471,58 +415,58 @@ index 38b03c7b02bdfc579e5e126c12de3d878e26d188..33e24f2c3b63b2d3b55dfae2f2e55869
Container containerAt = HopperBlockEntity.getContainerAt(level, pos.relative(direction));
ItemStack itemStack = stack.copy();
- if (containerAt != null && (containerAt instanceof CrafterBlockEntity || stack.getCount() > containerAt.getMaxStackSize(stack))) {
+ if (containerAt != null && (containerAt instanceof CrafterBlockEntity || stack.getCount() > containerAt.getMaxStackLeaves(stack))) { // Leaves - stackable shulker boxes
+ if (containerAt != null && (containerAt instanceof CrafterBlockEntity || stack.getCount() > containerAt.getMaxStackLeaves(stack))) { // Leaves - item over-stack util
// CraftBukkit start - InventoryMoveItemEvent
org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack);
diff --git a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
index c5b3b5e5f621f8db152aa190374ae0fe567d6828..0ca27b83151c3e7be3d681aa087fa72fb5bef0c3 100644
index d7a211220d5eba7e34b9083f06f39e9527962599..cef2d6d9a42ae36d6e2a0b31685a7738d2a400cf 100644
--- a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
@@ -409,7 +409,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit
@@ -411,7 +411,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit
ItemStack itemStack = this.items.get(index);
boolean flag = !stack.isEmpty() && ItemStack.isSameItemSameComponents(itemStack, stack);
this.items.set(index, stack);
- stack.limitSize(this.getMaxStackSize(stack));
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - stackable shulker boxes
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - item over-stack util
if (index == 0 && !flag && this.level instanceof ServerLevel serverLevel) {
this.cookingTotalTime = getTotalCookTime(serverLevel, this, this.recipeType, this.cookSpeedMultiplier); // Paper - cook speed multiplier API
this.cookingTimer = 0;
diff --git a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
index c63370fd458fb4f7190b79b1a8174fcc92d88f9c..4cca3fbbb93bb76d5d501dfad6997d909211809e 100644
index 10b2cc20bbf3b70e4e09dbbe14b90506e6366158..1535bb8cec8692e8d26eaff090288f1b8f75c253 100644
--- a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
@@ -148,7 +148,7 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co
@@ -155,7 +155,7 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co
@Override
public void setItem(int slot, ItemStack stack) {
this.getItems().set(slot, stack);
- stack.limitSize(this.getMaxStackSize(stack));
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - stackable shulker boxes
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - item over-stack util
this.setChanged();
}
diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
index 6f5a47a3e9452f25b318e4a7a628e917da99c96f..52032c6bbc7e1c37a50d9b7334f1830896cb462d 100644
index 2f8128a11e316a6ee6b283f9a411096e582c03b7..5b93eeba6c03738011551ce18332cebe9e278a8e 100644
--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
@@ -123,7 +123,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
@@ -145,7 +145,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
public void setItem(int index, ItemStack stack) {
this.unpackLootTable(null);
this.getItems().set(index, stack);
- stack.limitSize(this.getMaxStackSize(stack));
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - stackable shulker boxes
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - item over-stack util
}
@Override
@@ -690,9 +690,9 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
@@ -742,9 +742,9 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
if (item.isEmpty()) {
// Spigot start - SPIGOT-6693, SimpleContainer#setItem
ItemStack leftover = ItemStack.EMPTY; // Paper - Make hoppers respect inventory max stack size
- if (!stack.isEmpty() && stack.getCount() > destination.getMaxStackSize()) {
+ if (!stack.isEmpty() && (stack.getCount() > destination.getMaxStackSize() || stack.getCount() > stack.getMaxStackSize())) {
+ if (!stack.isEmpty() && (stack.getCount() > destination.getMaxStackSize() || stack.getCount() > stack.getMaxStackSize())) { // Leaves - item over-stack util
leftover = stack; // Paper - Make hoppers respect inventory max stack size
- stack = stack.split(destination.getMaxStackSize());
+ stack = stack.split(Math.min(destination.getMaxStackSize(), stack.getMaxStackSize()));
+ stack = stack.split(Math.min(destination.getMaxStackSize(), stack.getMaxStackSize())); // Leaves - item over-stack util
}
// Spigot end
IGNORE_TILE_UPDATES.set(Boolean.TRUE); // Paper - Perf: Optimize Hoppers // Folia - region threading
//IGNORE_TILE_UPDATES.set(Boolean.TRUE); // Paper - Perf: Optimize Hoppers // Folia - region threading // Luminol - Use vanilla hopper logic
@@ -1,171 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sat, 14 Jun 2025 05:32:51 +0800
Subject: [PATCH] Better ShulkerBox
You can open shulker box with shift & right click
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index a4eeb75eeb1cd69c60cd1c273f23f9744837148f..5596214edb14d30c9ed5af4ffdcdd2041d135c6d 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -2351,6 +2351,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
this.containerMenu.removed(this);
this.inventoryMenu.transferState(this.containerMenu);
this.containerMenu = this.inventoryMenu;
+ fun.bm.lophine.utils.ShulkerBoxesUtil.clearMap(this); // Lophine - better shulker box
}
@Override
diff --git a/net/minecraft/world/entity/player/Inventory.java b/net/minecraft/world/entity/player/Inventory.java
index 9020f7c83e5bd19e9a9d3748d200ce5772a9cefa..5bd913911075c0d93f4434e88c9cf7f3ac7248fc 100644
--- a/net/minecraft/world/entity/player/Inventory.java
+++ b/net/minecraft/world/entity/player/Inventory.java
@@ -299,6 +299,11 @@ public class Inventory implements Container, Nameable {
int i = this.getMaxStackLeaves(item) - item.getCount();
int min = Math.min(count, i);
+ // Lophine start - better shulker box
+ if (fun.bm.lophine.config.modules.misc.ContainerExpansionConfig.betterShulker) {
+ fun.bm.lophine.utils.ShulkerBoxesUtil.inventoryCallBack(player, slot);
+ }
+ // Lophine end - better shulker box
if (min == 0) {
return count;
} else {
@@ -351,6 +356,11 @@ public class Inventory implements Container, Nameable {
if (slot >= 0) {
this.items.set(slot, stack.copyAndClear());
this.items.get(slot).setPopTime(5);
+ // Lophine start - better shulker box
+ if (fun.bm.lophine.config.modules.misc.ContainerExpansionConfig.betterShulker) {
+ fun.bm.lophine.utils.ShulkerBoxesUtil.inventoryCallBack(player, slot);
+ }
+ // Lophine end - better shulker box
return true;
} else if (this.player.hasInfiniteMaterials()) {
stack.setCount(0);
diff --git a/net/minecraft/world/entity/player/Player.java b/net/minecraft/world/entity/player/Player.java
index 1f7a97b0a52461108fe5a17d2ecc224b1a9f26a1..86854467df17cc72e48360a0f19a4bbe4aaba3e8 100644
--- a/net/minecraft/world/entity/player/Player.java
+++ b/net/minecraft/world/entity/player/Player.java
@@ -595,11 +595,13 @@ public abstract class Player extends LivingEntity {
// Paper start - special close for unloaded inventory
public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
this.containerMenu = this.inventoryMenu;
+ fun.bm.lophine.utils.ShulkerBoxesUtil.clearMap(this); // Lophine - better shulker box
}
// Paper end - special close for unloaded inventory
public void closeContainer() {
this.containerMenu = this.inventoryMenu;
+ fun.bm.lophine.utils.ShulkerBoxesUtil.clearMap(this); // Lophine - better shulker box
}
protected void doCloseContainer() {
diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java
index 45ff5b9a50bdec611f7c1aa4cb77fb0b13b6910c..606025e0e4bfbcc996ce5a54ccf74c74430531a1 100644
--- a/net/minecraft/world/inventory/AbstractContainerMenu.java
+++ b/net/minecraft/world/inventory/AbstractContainerMenu.java
@@ -658,6 +658,11 @@ public abstract class AbstractContainerMenu {
}
}
}
+ // Lophine start - better shulker box
+ if (fun.bm.lophine.config.modules.misc.ContainerExpansionConfig.betterShulker) {
+ fun.bm.lophine.utils.ShulkerBoxesUtil.inventoryCallBack(player, slotId - 54);
+ }
+ // Lophine end - better shulker box
}
private boolean tryItemClickBehaviourOverride(Player player, ClickAction action, Slot slot, ItemStack clickedItem, ItemStack carriedItem) {
diff --git a/net/minecraft/world/inventory/ShulkerBoxMenu.java b/net/minecraft/world/inventory/ShulkerBoxMenu.java
index 903025c659e6a9423224fe65973696405c69ec6a..eb20f83d5aa2c06f60c3941e8a54434737c017c4 100644
--- a/net/minecraft/world/inventory/ShulkerBoxMenu.java
+++ b/net/minecraft/world/inventory/ShulkerBoxMenu.java
@@ -53,7 +53,7 @@ public class ShulkerBoxMenu extends AbstractContainerMenu {
@Override
public boolean stillValid(Player player) {
- if (!this.checkReachable) return true; // CraftBukkit
+ if (!this.checkReachable || fun.bm.lophine.utils.ShulkerBoxesUtil.checkIfValid(player)) return true; // CraftBukkit // Lophine - better shulker box
return this.container.stillValid(player);
}
diff --git a/net/minecraft/world/item/Item.java b/net/minecraft/world/item/Item.java
index c52fb17d1e496a91223b7387cacb128c1865caee..af070f075b1b6523040a4ff29ef37c730af28427 100644
--- a/net/minecraft/world/item/Item.java
+++ b/net/minecraft/world/item/Item.java
@@ -187,6 +187,13 @@ public class Item implements FeatureElement, ItemLike {
player.startUsingItem(hand);
return InteractionResult.CONSUME;
} else {
+ // Lophine start - better shulker box
+ if (fun.bm.lophine.config.modules.misc.ContainerExpansionConfig.betterShulker
+ && player.isShiftKeyDown()
+ && fun.bm.lophine.utils.ShulkerBoxesUtil.checkIfCanOpen(itemInHand)) {
+ fun.bm.lophine.utils.ShulkerBoxesUtil.openShulkerBox(player, itemInHand, hand);
+ }
+ // Lophine end - better shulker box
return InteractionResult.PASS;
}
}
diff --git a/net/minecraft/world/item/component/ItemContainerContents.java b/net/minecraft/world/item/component/ItemContainerContents.java
index ecf794f94177fc7b6df483516d920719fbc6fa43..4fda59a4c17009a0009f3d3c13258f4ffcb300c3 100644
--- a/net/minecraft/world/item/component/ItemContainerContents.java
+++ b/net/minecraft/world/item/component/ItemContainerContents.java
@@ -33,7 +33,7 @@ public final class ItemContainerContents implements TooltipProvider {
public final NonNullList<ItemStack> items;
private final int hashCode;
- private ItemContainerContents(NonNullList<ItemStack> items) {
+ public ItemContainerContents(NonNullList<ItemStack> items) { // Lophine - better shulker box
if (items.size() > 256) {
throw new IllegalArgumentException("Got " + items.size() + " items, but maximum is 256");
} else {
diff --git a/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java b/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java
index 87ebdb6deb66662a38b3eec0dae27eaf859ecabb..a36080d522841c4d9b186b475aa6667d1942c0eb 100644
--- a/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java
@@ -46,6 +46,10 @@ public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity impl
private ShulkerBoxBlockEntity.AnimationStatus animationStatus = ShulkerBoxBlockEntity.AnimationStatus.CLOSED;
private float progress;
private float progressOld;
+ // Lophine start - better shulker box
+ public boolean haveRealBlock = true;
+ public int slot;
+ // Lophine end - better shulker box
@Nullable
private final DyeColor color;
@@ -236,6 +240,15 @@ public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity impl
return Component.translatable("container.shulkerBox");
}
+ // Lophine start - better shulker box
+ public void setItem(int index, ItemStack stack) {
+ super.setItem(index, stack);
+ if (fun.bm.lophine.config.modules.misc.ContainerExpansionConfig.betterShulker && !haveRealBlock) {
+ fun.bm.lophine.utils.ShulkerBoxesUtil.shulkerBoxEntityCallBack(this);
+ }
+ }
+ // Lophine end - better shulker box
+
@Override
protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
super.loadAdditional(tag, registries);
@@ -258,12 +271,12 @@ public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity impl
}
@Override
- protected NonNullList<ItemStack> getItems() {
+ public NonNullList<ItemStack> getItems() { // Lophine - better shulker box
return this.itemStacks;
}
@Override
- protected void setItems(NonNullList<ItemStack> items) {
+ public void setItems(NonNullList<ItemStack> items) { // Lophine - better shulker box
this.itemStacks = items;
}
@@ -0,0 +1,21 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Mon, 7 Jul 2025 15:33:57 +0800
Subject: [PATCH] Leaves: Old Explosion Damage Calculator
Co-authored by: MC_XiaoHei <xor7xiaohei@gmail.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/3e96b237749a960f297f211d439ffc9ea7fd2381/leaves-server/minecraft-patches/features/0134-Old-wet-tnt-explode-behavior.patch)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java
index b3e897a4afee742b5055d652ad7047a660d8ea04..cf8869fac003c73024634078b2f7b36cddda640e 100644
--- a/net/minecraft/world/level/ServerExplosion.java
+++ b/net/minecraft/world/level/ServerExplosion.java
@@ -729,6 +729,7 @@ public class ServerExplosion implements Explosion {
public boolean shouldAffectBlocklikeEntities() {
boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
boolean flag = this.source == null || this.source.getType() != EntityType.BREEZE_WIND_CHARGE && this.source.getType() != EntityType.WIND_CHARGE;
+ if (fun.bm.lophine.config.modules.misc.OldFeatureConfig.oldExplosionDamageCalculator) flag = flag && (this.source == null || !this.source.isInWater()); // Leaves - Old MC TNT wet explosion no item damage
return _boolean ? flag : this.blockInteraction.shouldAffectBlocklikeEntities() && flag;
}
@@ -0,0 +1,216 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Fri, 11 Apr 2025 16:53:57 +0800
Subject: [PATCH] Leaves: Old raid behavior
Co-authored by: huanli233 <392352840@qq.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/79d9ef74c2684eb49060f07e1ab31c827326c9a5/leaves-server/minecraft-patches/features/0106-Old-raid-behavior.patch)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/world/effect/BadOmenMobEffect.java b/net/minecraft/world/effect/BadOmenMobEffect.java
index 80f17f33f670018240c854df589cf90cdeab6e70..cbfb4ae2dc30c2ee98ef37f44a95434a7968e320 100644
--- a/net/minecraft/world/effect/BadOmenMobEffect.java
+++ b/net/minecraft/world/effect/BadOmenMobEffect.java
@@ -22,6 +22,11 @@ class BadOmenMobEffect extends MobEffect {
&& !serverPlayer.isSpectator()
&& level.getDifficulty() != Difficulty.PEACEFUL
&& level.isVillage(serverPlayer.blockPosition())) {
+ // Leaves start - Revert raid changes
+ if (fun.bm.lophine.config.modules.misc.OldFeatureConfig.oldRaidBehavior) {
+ return level.getRaids().createOrExtendRaid(serverPlayer, serverPlayer.blockPosition()) == null;
+ }
+ // Leaves end - Revert raid changes
Raid raidAt = level.getRaidAt(serverPlayer.blockPosition());
if (raidAt == null || raidAt.getRaidOmenLevel() < raidAt.getMaxRaidOmenLevel()) {
serverPlayer.addEffect(new MobEffectInstance(MobEffects.RAID_OMEN, 600, amplifier));
diff --git a/net/minecraft/world/entity/raid/Raid.java b/net/minecraft/world/entity/raid/Raid.java
index 1910bff3f08147bfd63703097e30b9f88c84e337..5188ae86aff3051d45fe5e7d2fcc51233b7074d4 100644
--- a/net/minecraft/world/entity/raid/Raid.java
+++ b/net/minecraft/world/entity/raid/Raid.java
@@ -263,7 +263,7 @@ public class Raid {
}
public boolean absorbRaidOmen(ServerPlayer player) {
- MobEffectInstance effect = player.getEffect(MobEffects.RAID_OMEN);
+ MobEffectInstance effect = player.getEffect(fun.bm.lophine.config.modules.misc.OldFeatureConfig.oldRaidBehavior ? MobEffects.BAD_OMEN : MobEffects.RAID_OMEN); // Leaves - old Raid Behavior
if (effect == null) {
return false;
} else {
@@ -340,7 +340,13 @@ public class Raid {
}
if (flag1) {
- this.waveSpawnPos = this.getValidSpawnPos(level);
+ // Leaves Start - old FindSpawnPosition
+ if (fun.bm.lophine.config.modules.misc.OldFeatureConfig.oldRaidBehavior) {
+ this.waveSpawnPos = this.preCalcRavagerSpawnLocation(level, this.raidCooldownTicks < 100 ? 1 : 0);
+ } else {
+ this.waveSpawnPos = this.getValidSpawnPos(level);
+ }
+ // Leaves End - old FindSpawnPosition
}
if (this.raidCooldownTicks == 300 || this.raidCooldownTicks % 20 == 0) {
@@ -375,7 +381,14 @@ public class Raid {
int i = 0;
while (this.shouldSpawnGroup()) {
- BlockPos blockPos = this.waveSpawnPos.orElseGet(() -> this.findRandomSpawnPos(level, 20));
+ // Leaves Start - old FindSpawnPosition
+ BlockPos blockPos;
+ if (fun.bm.lophine.config.modules.misc.OldFeatureConfig.oldRaidBehavior) {
+ blockPos = this.getRavagerSpawnLocation(level, i, 20);
+ } else {
+ blockPos = this.waveSpawnPos.orElseGet(() -> this.findRandomSpawnPos(level, 20));
+ }
+ // Leaves End - old FindSpawnPosition
if (blockPos != null) {
this.started = true;
this.spawnGroup(level, blockPos);
@@ -387,7 +400,7 @@ public class Raid {
i++;
}
- if (i > 5) {
+ if (i > (fun.bm.lophine.config.modules.misc.OldFeatureConfig.oldRaidBehavior ? 3 : 5)) { // Leaves - old FindSpawnPosition
org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(level, this, org.bukkit.event.raid.RaidStopEvent.Reason.UNSPAWNABLE); // CraftBukkit
this.stop();
break;
@@ -683,7 +696,7 @@ public class Raid {
int i2 = this.center.getX() + Mth.floor(Mth.cos(f2) * 32.0F * f) + level.random.nextInt(3) * Mth.floor(f);
int i3 = this.center.getZ() + Mth.floor(Mth.sin(f2) * 32.0F * f) + level.random.nextInt(3) * Mth.floor(f);
int height = level.getHeight(Heightmap.Types.WORLD_SURFACE, i2, i3);
- if (Mth.abs(height - this.center.getY()) <= 96) {
+ if (fun.bm.lophine.config.modules.misc.OldFeatureConfig.oldRaidBehavior || Mth.abs(height - this.center.getY()) <= 96) { // Leaves - skippable
mutableBlockPos.set(i2, height, i3);
if (!level.isVillage(mutableBlockPos) || i <= 7) {
int i4 = 10;
@@ -702,6 +715,36 @@ public class Raid {
return null;
}
+ // Leaves Start - old FindSpawnPosition
+ @Nullable
+ private BlockPos findRandomSpawnPos(ServerLevel level, int n, int n2) {
+ int n3 = 2 - n;
+ BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+ SpawnPlacementType spawnPlacementType = SpawnPlacements.getPlacementType(EntityType.RAVAGER);
+ for (int i = 0; i < n2; ++i) {
+ float f = level.random.nextFloat() * ((float)Math.PI * 2);
+ int n4 = this.center.getX() + Mth.floor(Mth.cos(f) * 32.0f * (float)n3) + level.random.nextInt(5);
+ int n5 = this.center.getZ() + Mth.floor(Mth.sin(f) * 32.0f * (float)n3) + level.random.nextInt(5);
+ int n6 = level.getHeight(Heightmap.Types.WORLD_SURFACE, n4, n5);
+ mutableBlockPos.set(n4, n6, n5);
+ if (level.isVillage(mutableBlockPos) && n < 2) continue;
+ if (!level.hasChunksAt(mutableBlockPos.getX() - 10, mutableBlockPos.getZ() - 10, mutableBlockPos.getX() + 10, mutableBlockPos.getZ() + 10) || !level.isPositionEntityTicking(mutableBlockPos) || !spawnPlacementType.isSpawnPositionOk(level, mutableBlockPos, EntityType.RAVAGER) && (!level.getBlockState((BlockPos)mutableBlockPos.below()).is(Blocks.SNOW) || !level.getBlockState(mutableBlockPos).isAir())) continue;
+ return mutableBlockPos;
+ }
+ return null;
+ }
+
+ private Optional<BlockPos> getValidSpawnPos(ServerLevel level, int n) {
+ for (int i = 0; i < 3; ++i) {
+ BlockPos blockPos = this.findRandomSpawnPos(level, n, 1);
+ if (blockPos == null) continue;
+ return Optional.of(blockPos);
+ }
+ return Optional.empty();
+ }
+ // Leaves End - old FindSpawnPosition
+
+
private boolean addWaveMob(ServerLevel level, int wave, Raider raider) {
// Folia start - make raids thread-safe
if (!this.ownsRaid(level)) {
@@ -864,4 +907,43 @@ public class Raid {
this.spawnsPerWaveBeforeBonus = spawnsPerWaveBeforeBonus;
}
}
+
+ // Leaves start - old FindSpawnPosition
+ private BlockPos getRavagerSpawnLocation(ServerLevel serverWorld, int proximity, int tries) {
+ int i = proximity == 0 ? 2 : 2 - proximity;
+ BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
+ SpawnPlacementType spawnLocation = SpawnPlacements.getPlacementType(EntityType.RAVAGER);
+
+ for (int j = 0; j < tries; j++) {
+ float f = serverWorld.random.nextFloat() * (float) (Math.PI * 2);
+ int k = this.center.getX() + Mth.floor(Mth.cos(f) * 32.0F * (float) i + serverWorld.random.nextInt(5));
+ int l = this.center.getZ() + Mth.floor(Mth.sin(f) * 32.0F * (float) i + serverWorld.random.nextInt(5));
+ int m = serverWorld.getHeight(Heightmap.Types.WORLD_SURFACE, k, l);
+ mutable.set(k, m, l);
+
+ if (serverWorld.isVillage(mutable) && proximity < 2) {
+ continue;
+ }
+ if (serverWorld.hasChunksAt(mutable.getX() - 10, mutable.getZ() - 10, mutable.getX() + 10, mutable.getZ() + 10)
+ && serverWorld.isPositionEntityTicking(mutable)
+ && (spawnLocation.isSpawnPositionOk(serverWorld, mutable, EntityType.RAVAGER)
+ || serverWorld.getBlockState(mutable.below()).is(Blocks.SNOW)
+ && serverWorld.getBlockState(mutable).isAir())
+ ) {
+ return mutable;
+ }
+ }
+ return null;
+ }
+
+ private Optional<BlockPos> preCalcRavagerSpawnLocation(ServerLevel serverWorld, int proximity) {
+ for (int i = 0; i < 3; i++) {
+ BlockPos blockPos = this.getRavagerSpawnLocation(serverWorld, proximity, 1);
+ if (blockPos != null) {
+ return Optional.of(blockPos);
+ }
+ }
+ return Optional.empty();
+ }
+ // Leaves end - old FindSpawnPosition
}
diff --git a/net/minecraft/world/entity/raid/Raider.java b/net/minecraft/world/entity/raid/Raider.java
index 3e30989c99cd11c8c94d6fd194d2969e3a7efda6..b1f1bcf7f5ac002550077d5e1d4efd5c67d18063 100644
--- a/net/minecraft/world/entity/raid/Raider.java
+++ b/net/minecraft/world/entity/raid/Raider.java
@@ -128,6 +128,43 @@ public abstract class Raider extends PatrollingMonster {
currentRaid.removeFromRaid(serverLevel, this, false);
}
+
+ // Leaves start - Revert raid changes
+ if (this.level() instanceof ServerLevel) {
+ if (fun.bm.lophine.config.modules.misc.OldFeatureConfig.oldRaidBehavior && !this.hasRaid()) {
+ ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD);
+ net.minecraft.world.entity.player.Player entityhuman = null;
+ if (entity instanceof net.minecraft.world.entity.player.Player player) {
+ entityhuman = player;
+ } else if (entity instanceof net.minecraft.world.entity.animal.wolf.Wolf wolf) {
+ LivingEntity entityliving = wolf.getOwner();
+ if (wolf.isTame() && entityliving instanceof net.minecraft.world.entity.player.Player player) {
+ entityhuman = player;
+ }
+ }
+
+ if (entityhuman != null && !itemstack.isEmpty() && this.isCaptain()) {
+ net.minecraft.world.effect.MobEffectInstance mobeffect = entityhuman.getEffect(net.minecraft.world.effect.MobEffects.BAD_OMEN);
+ int i = 1;
+
+ if (mobeffect != null) {
+ i += mobeffect.getAmplifier();
+ entityhuman.removeEffectNoUpdate(net.minecraft.world.effect.MobEffects.BAD_OMEN);
+ } else {
+ --i;
+ }
+
+ i = net.minecraft.util.Mth.clamp(i, 0, 4);
+ net.minecraft.world.effect.MobEffectInstance mobeffect1 = new net.minecraft.world.effect.MobEffectInstance(net.minecraft.world.effect.MobEffects.BAD_OMEN, 120000, i, false, false, true);
+
+ if (!serverLevel.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_DISABLE_RAIDS)) {
+ entityhuman.addEffect(mobeffect1, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN); // CraftBukkit
+ }
+ this.setPatrolLeader(false);
+ }
+ }
+ }
+ // Leaves end - Revert raid changes
}
super.die(cause);
@@ -0,0 +1,44 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Wed, 23 Jul 2025 20:28:40 +0800
Subject: [PATCH] Leaves: Redstone Shears Wrench
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/79d9ef74c2684eb49060f07e1ab31c827326c9a5/leaves-server/minecraft-patches/features/0009-Redstone-Shears-Wrench.patch)
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 6db566adf2d0df1d26221eda04aa01738df6d3d2..24ebfdf98e343a7892af621d032c35819156deec 100644
--- a/net/minecraft/world/item/BlockItem.java
+++ b/net/minecraft/world/item/BlockItem.java
@@ -56,6 +56,14 @@ public class BlockItem extends Item {
if (blockPlaceContext == null) {
return InteractionResult.FAIL;
} else {
+ // Leaves start - shears wrench
+ if (org.leavesmc.leaves.util.ShearsWrenchUtil.shouldSkipPlace(context)) {
+ if (context.getPlayer() != null) {
+ context.getPlayer().containerMenu.forceHeldSlot(blockPlaceContext.getHand());
+ }
+ return InteractionResult.FAIL;
+ }
+ // Leaves end - shears wrench
BlockState placementState = this.getPlacementState(blockPlaceContext);
// CraftBukkit start - special case for handling block placement with water lilies and snow buckets
org.bukkit.block.BlockState bukkitState = null;
diff --git a/net/minecraft/world/item/ShearsItem.java b/net/minecraft/world/item/ShearsItem.java
index 8cf3e51e12f9cf98836657e722edb23943f9e866..5fd5e9fd4e1a79dd1a9d62a5cf0c308805979420 100644
--- a/net/minecraft/world/item/ShearsItem.java
+++ b/net/minecraft/world/item/ShearsItem.java
@@ -80,7 +80,10 @@ public class ShearsItem extends Item {
return InteractionResult.SUCCESS;
} else {
- return super.useOn(context);
+ // Leaves start - shears wrench
+ InteractionResult result = org.leavesmc.leaves.util.ShearsWrenchUtil.tryApplyRotate(context, blockState);
+ return result == null ? super.useOn(context) : result;
+ // Leaves end - shears wrench
}
}
}
@@ -1,20 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 18 May 2025 23:48:59 +0800
Subject: [PATCH] Old nether portal collision
This patch should removed in 1.21.6 when mojang revert it.
diff --git a/net/minecraft/world/level/block/NetherPortalBlock.java b/net/minecraft/world/level/block/NetherPortalBlock.java
index 421ea9c748159ac989900427a13abe9b244dc7aa..a5a220e6d41c992d1db8617069aa00eddcf7b95a 100644
--- a/net/minecraft/world/level/block/NetherPortalBlock.java
+++ b/net/minecraft/world/level/block/NetherPortalBlock.java
@@ -65,7 +65,7 @@ public class NetherPortalBlock extends Block implements Portal {
@Override
protected VoxelShape getEntityInsideCollisionShape(BlockState state, BlockGetter level, BlockPos pos, Entity entity) {
- return state.getShape(level, pos);
+ return fun.bm.lophine.config.modules.misc.OldFeatureConfig.oldNetherPortalCollision ? Shapes.block() : state.getShape(level, pos); // Lophine - Old nether portal collision
}
@Override
@@ -0,0 +1,36 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 3 Aug 2025 15:19:23 +0800
Subject: [PATCH] Leaves: Servux Protocol
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/ea91106ae57fc4cc14e2e0225009cf9919072f7f/leaves-server/minecraft-patches/features/0090-Servux-Protocol.patch)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/server/ServerTickRateManager.java b/net/minecraft/server/ServerTickRateManager.java
index d0d90a25a10bbecfffceee1992af88c60d14fd87..187e48e78e69c80b25710f193813335ec643957f 100644
--- a/net/minecraft/server/ServerTickRateManager.java
+++ b/net/minecraft/server/ServerTickRateManager.java
@@ -134,4 +134,10 @@ public class ServerTickRateManager extends TickRateManager {
player.connection.send(ClientboundTickingStatePacket.from(this));
player.connection.send(ClientboundTickingStepPacket.from(this));
}
+
+ // Leaves start - servux
+ public long getRemainingSprintTicks() {
+ return remainingSprintTicks;
+ }
+ // Leaves end - servux
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index eb3aef1f5198592eda5dafe84b91271e861b4135..576dde41a4b48aac9079cca902f4371a31470af3 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -2368,6 +2368,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
}
this.lastSpawnChunkRadius = i;
+ org.leavesmc.leaves.protocol.servux.ServuxHudDataProtocol.refreshSpawnMetadata = true; // Leaves - servux
}
public LongSet getForceLoadedChunks() {
@@ -1,60 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Wed, 18 Jun 2025 14:24:54 +0800
Subject: [PATCH] Old replaceable by mushrooms
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
index ba1ccd6eab77e27a38d22c12dba0797d65cc4c4e..373f91df79fc7a7bd6fd137bf53b8a55dd745e31 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -11,6 +11,8 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
+
+import fun.bm.lophine.config.modules.misc.OldFeatureConfig;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
@@ -1111,7 +1113,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, AutoCl
if (worldData.captureTreeGeneration) { // Folia - region threading
// Paper start - Protect Bedrock and End Portal/Frames from being destroyed
BlockState type = getBlockState(pos);
- if (!type.isDestroyable()) return false;
+ if (!type.isDestroyable() && !(OldFeatureConfig.oldReplaceableByMushrooms && flags == 3)) return false;
// Paper end - Protect Bedrock and End Portal/Frames from being destroyed
CraftBlockState blockstate = worldData.capturedBlockStates.get(pos); // Folia - region threading
if (blockstate == null) {
diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java
index 834e27ef2f7b342b074ff9e1e390e02f3ca1c399..f5765e3bbf9e735d6f959e1bf6ee1846ee11b4e9 100644
--- a/net/minecraft/world/level/block/state/BlockBehaviour.java
+++ b/net/minecraft/world/level/block/state/BlockBehaviour.java
@@ -770,6 +770,14 @@ public abstract class BlockBehaviour implements FeatureElement {
return this.solidRender;
}
+ public boolean mushroomCheck(BlockState state) {
+ BlockState blockState = state.asState();
+ if (blockState.canOcclude()) {
+ return !Block.isShapeFullBlock(blockState.getOcclusionShape());
+ }
+ return true;
+ }
+
public final boolean canOcclude() { // Paper - Perf: Final for inlining
return this.canOcclude;
}
diff --git a/net/minecraft/world/level/levelgen/feature/AbstractHugeMushroomFeature.java b/net/minecraft/world/level/levelgen/feature/AbstractHugeMushroomFeature.java
index 3a37d66ab3c27c9abd60f35ef3bf3a93f8d7c3cd..2aad65d76cdd0800ac593a5bb3ce84ad1925eab0 100644
--- a/net/minecraft/world/level/levelgen/feature/AbstractHugeMushroomFeature.java
+++ b/net/minecraft/world/level/levelgen/feature/AbstractHugeMushroomFeature.java
@@ -26,7 +26,7 @@ public abstract class AbstractHugeMushroomFeature extends Feature<HugeMushroomFe
protected void placeMushroomBlock(LevelAccessor level, BlockPos.MutableBlockPos mutablePos, BlockState state) {
BlockState blockState = level.getBlockState(mutablePos);
- if (blockState.isAir() || blockState.is(BlockTags.REPLACEABLE_BY_MUSHROOMS)) {
+ if (blockState.isAir() || blockState.is(BlockTags.REPLACEABLE_BY_MUSHROOMS) || (fun.bm.lophine.config.modules.misc.OldFeatureConfig.oldReplaceableByMushrooms && state.mushroomCheck(blockState))) { // Lophine - old replaceable by mushrooms
this.setBlock(level, mutablePos, state);
}
}
@@ -8,10 +8,10 @@ As part of: Purpur (https://github.com/PurpurMC/Purpur/blob/09f547de09fc5d886f18
Licensed under: MIT (https://github.com/PurpurMC/Purpur/blob/09f547de09fc5d886f18f6d99ff389289766ec9d/LICENSE)
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index 001483b7098523f3645a95c81fcf0e38b551705e..70be6c3ef009f1746353cc330a188bc693174ed7 100644
index 593dbcda4b497291eb1ecdc64b947a8d279f8be6..661f797b39853104b16d75ca7ba0eda156a74af3 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -1116,6 +1116,10 @@ public abstract class PlayerList {
@@ -1087,6 +1087,10 @@ public abstract class PlayerList {
player.getBukkitEntity().recalculatePermissions(); // CraftBukkit
this.server.getCommands().sendCommands(player);
} // Paper - Add sendOpLevel API
@@ -21,12 +21,12 @@ index 001483b7098523f3645a95c81fcf0e38b551705e..70be6c3ef009f1746353cc330a188bc6
+ // Purpur end - Barrels and enderchests 6 rows
}
public boolean isWhiteListed(GameProfile profile) {
// Paper start - whitelist verify event / login event
diff --git a/net/minecraft/world/entity/player/Player.java b/net/minecraft/world/entity/player/Player.java
index a26ec6f1a1553bf0b415f382177c75f481e21180..1f7a97b0a52461108fe5a17d2ecc224b1a9f26a1 100644
index 09b549fa9568aa1ae13b1b975db14a6268fdaa46..6c933c27ce155eb516a9ede96fb2c3bd19251bfa 100644
--- a/net/minecraft/world/entity/player/Player.java
+++ b/net/minecraft/world/entity/player/Player.java
@@ -210,6 +210,7 @@ public abstract class Player extends LivingEntity {
@@ -219,6 +219,7 @@ public abstract class Player extends LivingEntity {
private int currentImpulseContextResetGraceTime = 0;
public boolean affectsSpawning = true; // Paper - Affects Spawning API
public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper - flying fall damage
@@ -70,7 +70,7 @@ index 0fffa384f928ab84451331380968fb4650eafe26..d84a10396395939149da88bcb01be59e
return new ChestMenu(MenuType.GENERIC_9x6, containerId, playerInventory, container, 6);
}
diff --git a/net/minecraft/world/inventory/PlayerEnderChestContainer.java b/net/minecraft/world/inventory/PlayerEnderChestContainer.java
index bc2b95973192069fc64581b59583b19df876f55d..89a2f0286b9ab036f4412a9d657c0105382ed64f 100644
index beb74bc8398de8b48f41af7daef14d624826310e..5dd6e89931d397b8afdb84024ed46b0d06dcf347 100644
--- a/net/minecraft/world/inventory/PlayerEnderChestContainer.java
+++ b/net/minecraft/world/inventory/PlayerEnderChestContainer.java
@@ -25,11 +25,18 @@ public class PlayerEnderChestContainer extends SimpleContainer {
@@ -115,10 +115,10 @@ index 5077a9ff7b78801bdc53536a37aee07b8d86ee4d..fe374f08b7829b846b2a1c2ac3ca500c
// Paper end - Fix InventoryOpenEvent cancellation - moved up;
player.awardStat(Stats.OPEN_ENDERCHEST);
diff --git a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
index 027502d0af5512c31878978c4d05c52fa3029cca..e05239582477157bd530222704bf43b1254c63ca 100644
index de99652629c7301a4bc0e9ec48bed9a26a338f0d..4383e87560bb05acf9c50758f38a3cb69ee5c5c7 100644
--- a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
@@ -56,7 +56,17 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
@@ -56,7 +56,17 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity implemen
}
// CraftBukkit end
@@ -137,7 +137,7 @@ index 027502d0af5512c31878978c4d05c52fa3029cca..e05239582477157bd530222704bf43b1
public final ContainerOpenersCounter openersCounter = new ContainerOpenersCounter() {
@Override
protected void onOpen(Level level, BlockPos pos, BlockState state) {
@@ -108,7 +118,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
@@ -108,7 +118,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity implemen
@Override
public int getContainerSize() {
@@ -155,7 +155,7 @@ index 027502d0af5512c31878978c4d05c52fa3029cca..e05239582477157bd530222704bf43b1
}
@Override
@@ -128,7 +147,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
@@ -129,7 +148,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity implemen
@Override
protected AbstractContainerMenu createMenu(int id, Inventory player) {
@@ -0,0 +1,54 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Fri, 15 Aug 2025 02:29:30 +0800
Subject: [PATCH] Compatibility fix for Raid Revert and Cross Region Damage
Trace
diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
index 3f4b1c54f9207ba1854a8c8da965cbaca5ea517d..a8348a073682eec31990e9ab963b11ad32cf8bf8 100644
--- a/net/minecraft/world/entity/LivingEntity.java
+++ b/net/minecraft/world/entity/LivingEntity.java
@@ -1216,6 +1216,29 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
}
}
+ // Lophine Start - raid revert adapt Cross Region Damage trace
+ public boolean addEffect(MobEffectInstance effectInstance, EntityPotionEffectEvent.Cause cause, boolean fireEvent, boolean async) {
+ if (!async || ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this)) {
+ return addEffect(effectInstance, this, cause, fireEvent);
+ } else if (fun.bm.lophine.config.modules.experiment.EntityDamageSourceTraceConfig.enabled) {
+ postToAddEffect(effectInstance, this, cause, fireEvent);
+ return true;
+ }
+ return false;
+ }
+
+ private void postToAddEffect(MobEffectInstance effectInstance, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause, boolean fireEvent) {
+ if (entity != null)
+ entity.getBukkitEntity().taskScheduler.schedule((Entity nmsEntity) -> {
+ try {
+ addEffect(effectInstance, nmsEntity, cause, fireEvent);
+ } catch (Throwable ex) {
+ LOGGER.error(ex.getMessage(), ex);
+ }
+ }, null, 1L );
+ }
+ // Lophine End - raid revert adapt Cross Region Damage trace
+
public boolean canBeAffected(MobEffectInstance effectInstance) {
if (this.getType().is(EntityTypeTags.IMMUNE_TO_INFESTED)) {
return !effectInstance.is(MobEffects.INFESTED);
diff --git a/net/minecraft/world/entity/raid/Raider.java b/net/minecraft/world/entity/raid/Raider.java
index b1f1bcf7f5ac002550077d5e1d4efd5c67d18063..6120f7b970ff05867524ee9f1433d46843d57483 100644
--- a/net/minecraft/world/entity/raid/Raider.java
+++ b/net/minecraft/world/entity/raid/Raider.java
@@ -158,7 +158,7 @@ public abstract class Raider extends PatrollingMonster {
net.minecraft.world.effect.MobEffectInstance mobeffect1 = new net.minecraft.world.effect.MobEffectInstance(net.minecraft.world.effect.MobEffects.BAD_OMEN, 120000, i, false, false, true);
if (!serverLevel.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_DISABLE_RAIDS)) {
- entityhuman.addEffect(mobeffect1, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN); // CraftBukkit
+ entityhuman.addEffect(mobeffect1, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN, true, fun.bm.lophine.config.modules.experiment.EntityDamageSourceTraceConfig.enabled); // Lophine - raid revert adapt Cross Region Damage trace
}
this.setPatrolLeader(false);
}
@@ -1,18 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Thu, 12 Jun 2025 04:44:24 +0800
Subject: [PATCH] I18n support
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
index 1b60378cdbf5bc88f996010a61cceec7c2b9e2dc..dd701b07c4d5894516f613aca687a969f1bbee90 100644
--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -172,6 +172,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
// Paper end - initialize global and world-defaults configuration
fun.bm.lophine.config.LophineConfig.loadConfigFiles(); //Luminol - load config file // Lophine - load config file
+ fun.bm.lophine.utils.ServerI18nUtil.init(); // Lophine - I18n support
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()) {
@@ -0,0 +1,55 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 3 Aug 2025 14:26:37 +0800
Subject: [PATCH] Disable some check for operators
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index d63d8f7523938088bf9efc1c1b8ad9f0583338bb..b40059553089207ab1c6628beefadc674ec43d4e 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -367,7 +367,7 @@ public class ServerGamePacketListenerImpl
this.tickCount++;
this.knownMovePacketCount = this.receivedMovePacketCount;
if (this.clientIsFloating && !this.player.isSleeping() && !this.player.isPassenger() && !this.player.isDeadOrDying()) {
- if (++this.aboveGroundTickCount > this.getMaximumFlyingTicks(this.player)) {
+ if (++this.aboveGroundTickCount > this.getMaximumFlyingTicks(this.player) && !(fun.bm.lophine.config.modules.misc.DisableCheckConfig.disableOpFlyCheck && this.player.hasPermissions(2))) { // Lophine - disable fly check for operator
// LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString()); // Paper - Logging moved to net.minecraft.server.network.ServerCommonPacketListenerImpl.disconnect()
this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_PLAYER); // Paper - use configurable kick message & kick event cause
return;
@@ -386,7 +386,7 @@ public class ServerGamePacketListenerImpl
this.vehicleLastGoodY = this.lastVehicle.getY();
this.vehicleLastGoodZ = this.lastVehicle.getZ();
if (this.clientVehicleIsFloating && this.lastVehicle.getControllingPassenger() == this.player) {
- if (++this.aboveGroundVehicleTickCount > this.getMaximumFlyingTicks(this.lastVehicle)) {
+ if (++this.aboveGroundVehicleTickCount > this.getMaximumFlyingTicks(this.lastVehicle) && !(fun.bm.lophine.config.modules.misc.DisableCheckConfig.disableOpFlyCheck && this.player.hasPermissions(2))) { // Lophine - disable fly check for operator
// LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString()); // Paper - Logging moved to net.minecraft.server.network.ServerCommonPacketListenerImpl.disconnect()
this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_VEHICLE); // Paper - use configurable kick message & kick event cause
return;
@@ -644,7 +644,7 @@ public class ServerGamePacketListenerImpl
d5 = d2 - rootVehicle.getZ();
d7 = d3 * d3 + d4 * d4 + d5 * d5;
boolean flag1 = false;
- if (!me.earthme.luminol.config.modules.misc.DisableMovedWronglyThreshold.enabled && d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot
+ if (!me.earthme.luminol.config.modules.misc.DisableMovedWronglyThreshold.enabled && d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !(fun.bm.lophine.config.modules.misc.DisableCheckConfig.disableOpMoveCheck && this.player.hasPermissions(2))) { // Spigot // Lophine - disable move check for operator
flag1 = true; // Paper - diff on change, this should be moved wrongly
LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", rootVehicle.getName().getString(), this.player.getName().getString(), Math.sqrt(d7));
}
@@ -1540,7 +1540,7 @@ public class ServerGamePacketListenerImpl
// Paper start - Add fail move event
io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY,
toX, toY, toZ, toYaw, toPitch, true);
- if (!event.isAllowed()) {
+ if (!event.isAllowed() && !(fun.bm.lophine.config.modules.misc.DisableCheckConfig.disableOpMoveCheck && this.player.hasPermissions(2))) { // Lophine - disable move check for operator
if (event.getLogWarning()) {
LOGGER.warn("{} moved too quickly! {},{},{}", this.player.getName().getString(), d3, d4, d5);
}
@@ -1618,7 +1618,7 @@ public class ServerGamePacketListenerImpl
// Paper start - Add fail move event
io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_WRONGLY,
toX, toY, toZ, toYaw, toPitch, true);
- if (!event.isAllowed()) {
+ if (!event.isAllowed() && !(fun.bm.lophine.config.modules.misc.DisableCheckConfig.disableOpMoveCheck && this.player.hasPermissions(2))) { // Lophine - disable move check for operator
movedWrongly = true;
if (event.getLogWarning())
// Paper end
@@ -0,0 +1,44 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Thu, 12 Jun 2025 04:44:24 +0800
Subject: [PATCH] I18n support
diff --git a/net/minecraft/locale/Language.java b/net/minecraft/locale/Language.java
index 7b9e2a1a208b46a69c16e6afd8b502259893574f..db46a31ea8a36562e548eb07f0c4cf9b9e8c3360 100644
--- a/net/minecraft/locale/Language.java
+++ b/net/minecraft/locale/Language.java
@@ -65,7 +65,7 @@ public abstract class Language {
};
}
- private static void parseTranslations(BiConsumer<String, String> output, String languagePath) {
+ public static void parseTranslations(BiConsumer<String, String> output, String languagePath) { // Lophine - private -> public
try (InputStream resourceAsStream = Language.class.getResourceAsStream(languagePath)) {
loadFromJson(resourceAsStream, output);
} catch (JsonParseException | IOException var7) {
diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
index cf72cb8b418239b56c755486b03a321672e16bc3..b69c06607ca900beeebf4892a978442dad2db19b 100644
--- a/net/minecraft/server/Main.java
+++ b/net/minecraft/server/Main.java
@@ -155,6 +155,8 @@ public class Main {
return;
}
+ fun.bm.lophine.utils.ServerI18nUtil.preInit(); // Lophine - I18n support
+
// Paper start - Detect headless JRE
String awtException = io.papermc.paper.util.ServerEnvironment.awtDependencyCheck();
if (awtException != null) {
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
index c6c6432c2efeb7b95c2894b188cd966321ae3186..1cd9af71ee66df5f277748c0a5a59a30bd57accc 100644
--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -172,6 +172,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
+ fun.bm.lophine.utils.ServerI18nUtil.init(); // Lophine - I18n support
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()) {
@@ -5,10 +5,10 @@ Subject: [PATCH] Old zombie reinforcement
diff --git a/net/minecraft/world/entity/monster/Zombie.java b/net/minecraft/world/entity/monster/Zombie.java
index 4395947fc8c719864ac2afde5e6bbb53da5129c2..f2b2859084bcff3a004d9b4bfac1f51e75970d39 100644
index 14d0758aca54d9a25e369dfa8c89c930f27946c9..6f1f26b7f759e56f843e674167470da943785728 100644
--- a/net/minecraft/world/entity/monster/Zombie.java
+++ b/net/minecraft/world/entity/monster/Zombie.java
@@ -342,7 +342,7 @@ public class Zombie extends Monster {
@@ -341,7 +341,7 @@ public class Zombie extends Monster {
int floor = Mth.floor(this.getX());
int floor1 = Mth.floor(this.getY());
int floor2 = Mth.floor(this.getZ());
@@ -5,10 +5,10 @@ Subject: [PATCH] Spawn invulnerable time
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 5596214edb14d30c9ed5af4ffdcdd2041d135c6d..bf76bdf5f4c5a490f8de8d924b371cd98b63bfa4 100644
index 6b7c92fdf562dfa46eee8e8acbc82b02fc3226c5..144b2c9396b8162f9cce8b343d998907bf7fb5aa 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -221,6 +221,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
@@ -232,6 +232,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
private int lastSentFood = -99999999;
private boolean lastFoodSaturationZero = true;
public int lastSentExp = -99999999;
@@ -16,7 +16,7 @@ index 5596214edb14d30c9ed5af4ffdcdd2041d135c6d..bf76bdf5f4c5a490f8de8d924b371cd9
private ChatVisiblity chatVisibility = ChatVisiblity.FULL;
public ParticleStatus particleStatus = ParticleStatus.ALL;
private boolean canChatColor = true;
@@ -923,6 +924,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
@@ -925,6 +926,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
this.tickClientLoadTimeout();
this.gameMode.tick();
this.wardenSpawnTracker.tick();
@@ -24,7 +24,7 @@ index 5596214edb14d30c9ed5af4ffdcdd2041d135c6d..bf76bdf5f4c5a490f8de8d924b371cd9
if (this.invulnerableTime > 0) {
this.invulnerableTime--;
}
@@ -1420,6 +1422,13 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
@@ -1435,6 +1437,13 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
if (this.isInvulnerableTo(level, damageSource)) {
return false;
} else {
File diff suppressed because it is too large Load Diff
@@ -1,14 +1,17 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Suisuroru <qwertyuiop14077@qq.com>
Date: Thu, 20 Feb 2025 01:00:29 +0800
Subject: [PATCH] Purpur-Barrels-and-enderchests-6-rows
Subject: [PATCH] Purpur: Barrels and enderchests 6 rows
Co-authored by: William Blake Galbreath <Blake.Galbreath@GMail.com>
As part of: Purpur (https://github.com/PurpurMC/Purpur/blob/09f547de09fc5d886f18f6d99ff389289766ec9d/purpur-server/paper-patches/features/0003-Barrels-and-enderchests-6-rows.patch)
Licensed under: MIT (https://github.com/PurpurMC/Purpur/blob/09f547de09fc5d886f18f6d99ff389289766ec9d/LICENSE)
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
index c00ddfe41439954fa0fd87c0933f274c8a752eb6..77aa23d252e429e01bc7bd8c47e23d12ca596fdb 100644
index 2f41a92465b9da28e026297cc3528898bb1c8412..fb5300ec1bd19b3dfd6a2b4106440b9f73c48205 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
@@ -150,9 +150,26 @@ public class CraftContainer extends AbstractContainerMenu {
@@ -148,9 +148,26 @@ public class CraftContainer extends AbstractContainerMenu {
case PLAYER:
case CHEST:
case ENDER_CHEST:
@@ -0,0 +1,29 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 3 Aug 2025 15:57:08 +0800
Subject: [PATCH] Leaves: Leaves Protocol Core
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/ea91106ae57fc4cc14e2e0225009cf9919072f7f/leaves-server/paper-patches/features/0004-Leaves-Protocol-Core.patch)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 4f7077b592f84334483f8edf3656fccd5b90a9ca..26d5a96edd283a8f8fd50222e2671cef2c4b257b 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -497,6 +497,7 @@ public final class CraftServer implements Server {
this.potionBrewer = new io.papermc.paper.potion.PaperPotionBrewer(console); // Paper - custom potion mixes
datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper
if (false) this.spark = new io.papermc.paper.SparksFly(this); // Paper - spark // Luminol - Force disable builtin spark
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.init(); // Leaves - protocol
}
public boolean getCommandBlockOverride(String command) {
@@ -1115,6 +1116,7 @@ public final class CraftServer implements Server {
if (false) this.spark.registerCommandBeforePlugins(this); // Paper - spark // Luminol - Force disable builtin spark
this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*");
this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions");
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleServerReload(); // Leaves - protocol
int pollCount = 0;
@@ -96,7 +96,7 @@ public class CullTask implements Runnable {
lastCheckedTime = (System.currentTimeMillis() - start);
}
}
}finally {
} finally {
if (this.scheduleNext) {
this.worker.execute(this);
}
@@ -1,33 +0,0 @@
package fun.bm.lophine.config;
import me.earthme.luminol.config.LuminolConfig;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class LophineConfig {
public static final Map<String, LuminolConfig> configfiles = new HashMap<>();
private static final File luminolConfigFolder = new File("luminol_config");
private static final File lophineConfigFolder = new File("lophine_config");
public static void initConfigs() throws IOException {
configfiles.put("luminol", new LuminolConfig(luminolConfigFolder, "luminol", "me.earthme.luminol.config.modules"));
configfiles.put("lophine", new LuminolConfig(lophineConfigFolder, "lophine", "fun.bm.lophine.config.modules"));
preLoad();
}
public static void preLoad() throws IOException {
for (LuminolConfig config : configfiles.values()) {
config.preLoadConfig();
}
}
public static void loadConfigFiles() {
for (LuminolConfig config : configfiles.values()) {
config.finalizeLoadConfig();
config.setupLatch();
}
}
}
@@ -5,16 +5,17 @@ import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigInfo;
public class CommandConfig implements IConfigModule {
@ConfigInfo(baseName = "command_block_enabled", comments =
"""
Allow to use command block""")
public static boolean block = false;
@ConfigInfo(baseName = "tick_command_enabled", comments =
"""
Allow to use tick command""")
public static boolean tick = false;
@ConfigInfo(baseName = "datapack_command_enabled", comments =
"""
Allow to use datapack command
--- datapack hot-update unsupported, please restart server""")
public static boolean datapack = false;
@Override
public EnumConfigCategory getCategory() {
return EnumConfigCategory.EXPERIMENT;
@@ -0,0 +1,24 @@
package fun.bm.lophine.config.modules.experiment;
import me.earthme.luminol.config.EnumConfigCategory;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.config.flags.TransformedConfig;
public class WaypointConfig implements IConfigModule {
@TransformedConfig(name = "enable-waypoint", category = {"experiment", "waypoint bar"})
@ConfigInfo(baseName = "enabled", comments =
"""
Enable waypoint bar unsafe""")
public static boolean forceEnableWaypointUnsafe = false;
@Override
public EnumConfigCategory getCategory() {
return EnumConfigCategory.EXPERIMENT;
}
@Override
public String getBaseName() {
return "waypoint_bar";
}
}
@@ -1,10 +1,12 @@
package fun.bm.lophine.config.modules.misc;
package fun.bm.lophine.config.modules.fixes;
import me.earthme.luminol.config.EnumConfigCategory;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.config.flags.TransformedConfig;
public class DisableEndCrystalCheckConfig implements IConfigModule {
@TransformedConfig(name = "disable_end_crystal_check", category = {"misc", "end_crystal"})
@ConfigInfo(baseName = "disable_end_crystal_check", comments =
"""
Disable paper's End Crystal position check.
@@ -13,7 +15,7 @@ public class DisableEndCrystalCheckConfig implements IConfigModule {
@Override
public EnumConfigCategory getCategory() {
return EnumConfigCategory.MISC;
return EnumConfigCategory.FIXES;
}
@Override
@@ -20,10 +20,8 @@ public class ContainerExpansionConfig implements IConfigModule {
range: 1~64""")
public static int shulkerCount = 1;
@ConfigInfo(baseName = "better_shulker_box", comments =
"""
Enable sneak + use to open shulker box.""")
public static boolean betterShulker = false;
@ConfigInfo(baseName = "same_nbt_shulker_stackable")
public static boolean nbtShulkerStackable = false;
@Override
public EnumConfigCategory getCategory() {
@@ -0,0 +1,25 @@
package fun.bm.lophine.config.modules.misc;
import me.earthme.luminol.config.EnumConfigCategory;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigInfo;
public class DisableCheckConfig implements IConfigModule {
@ConfigInfo(baseName = "disable-op-move-check", comments = """
Disable the check for the operator's move check""")
public static boolean disableOpMoveCheck = false;
@ConfigInfo(baseName = "disable-op-fly-check", comments = """
Disable the check for the operator's fly check""")
public static boolean disableOpFlyCheck = false;
@Override
public EnumConfigCategory getCategory() {
return EnumConfigCategory.MISC;
}
@Override
public String getBaseName() {
return "disable-check";
}
}
@@ -3,19 +3,21 @@ package fun.bm.lophine.config.modules.misc;
import me.earthme.luminol.config.EnumConfigCategory;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.config.flags.TransformedConfig;
public class OldFeatureConfig implements IConfigModule {
@ConfigInfo(baseName = "old_nether_portal_collision")
public static boolean oldNetherPortalCollision = false;
@ConfigInfo(baseName = "spawn_invulnerable_time")
public static boolean spawnInvulnerableTime = false;
@ConfigInfo(baseName = "old_zombie_reinforcement")
public static boolean oldZombieReinforcement = false;
@ConfigInfo(baseName = "old_replaceable_by_mushrooms")
public static boolean oldReplaceableByMushrooms = false;
@ConfigInfo(baseName = "old_explosion_damage_calculator")
public static boolean oldExplosionDamageCalculator = false;
@TransformedConfig(name = "give_bad_omen_when_kill_raid_captain", category = {"misc", "revert_raid_changes"}, transformComments = false)
@ConfigInfo(baseName = "old_raid_behavior")
public static boolean oldRaidBehavior = false;
@Override
public EnumConfigCategory getCategory() {
@@ -1,54 +0,0 @@
package fun.bm.lophine.config.modules.misc;
import me.earthme.luminol.config.EnumConfigCategory;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigInfo;
public class RaidChangesConfig implements IConfigModule {
@ConfigInfo(baseName = "allow_skip_cooldown", comments =
"""
Allow players with bad omen to skip a
30-second cooldown and trigger attacks directly""")
public static boolean trigger = false;
@ConfigInfo(baseName = "give_bad_omen_when_kill_raid_captain", comments =
"""
Enable players to obtain a bad omen
effect when killing the raid captain""")
public static boolean effect = false;
@ConfigInfo(baseName = "bad_omen_infinite", comments =
"""
Enable bad omen effect infinite time
--- this config is not old version's function""")
public static boolean infinite = false;
@ConfigInfo(baseName = "skip_height_check", comments =
"""
Disable y <= 96 check.
If you enabled use_old_position_find, this config
will useless and always behavior of enabled""")
public static boolean heightCheck = false;
@ConfigInfo(baseName = "skip_self_raid_check", comments =
"""
Disable raid self check
--- this config is not old version's function""")
public static boolean selfCheck = false;
@ConfigInfo(baseName = "use_old_position_find", comments =
"""
Revert Old raid's find spawn position logic
--- This revert MC-274911""")
public static boolean posRevert = false;
@Override
public EnumConfigCategory getCategory() {
return EnumConfigCategory.MISC;
}
@Override
public String getBaseName() {
return "revert_raid_changes";
}
}
@@ -0,0 +1,24 @@
package fun.bm.lophine.config.modules.misc;
import me.earthme.luminol.config.EnumConfigCategory;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.config.flags.TransformedConfig;
public class RedStoneConfig implements IConfigModule {
@TransformedConfig(name = "allow_skip_cooldown", category = {"misc", "redstone"})
@ConfigInfo(baseName = "shears_rotate", comments =
"""
Allows you to use the Shears to right-click to rotate the block.""")
public static boolean shears = false;
@Override
public EnumConfigCategory getCategory() {
return EnumConfigCategory.MISC;
}
@Override
public String getBaseName() {
return "redstone";
}
}
@@ -0,0 +1,46 @@
package fun.bm.lophine.config.modules.misc;
import me.earthme.luminol.config.EnumConfigCategory;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigInfo;
import java.util.List;
public class SurvuxProtocolConfig implements IConfigModule {
@ConfigInfo(baseName = "entity-protocol")
public static boolean entityProtocol = false;
@ConfigInfo(baseName = "hud-logger-protocol")
public static boolean hudLoggerProtocol = false;
@ConfigInfo(baseName = "hud-metadata-protocol")
public static boolean hudMetadataProtocol = false;
@ConfigInfo(baseName = "hud-metadata-share-seed")
public static boolean hudMetadataShareSeed = false;
@ConfigInfo(baseName = "structure-protocol")
public static boolean structureProtocol = false;
@ConfigInfo(baseName = "hud-enabled-loggers")
public static List<String> hudEnabledLoggers = List.of("tps", "mob_caps");
@ConfigInfo(baseName = "hud-update-interval")
public static int hudUpdateInterval = 1;
@ConfigInfo(baseName = "litematics-enabled")
public static boolean litematicsEnabled = false;
@ConfigInfo(baseName = "litematics-max-nbt-size")
public static int litematicsMaxNbtSize = 2097152;
@Override
public EnumConfigCategory getCategory() {
return EnumConfigCategory.MISC;
}
@Override
public String getBaseName() {
return "servux-protocol";
}
}
@@ -0,0 +1,23 @@
package fun.bm.lophine.config.modules.misc;
import me.earthme.luminol.config.EnumConfigCategory;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigInfo;
public class VillagerConfig implements IConfigModule {
@ConfigInfo(baseName = "villager-infinite-trade", comments =
"""
Allow villager infinite trade (limit of 524288 times)
---- we won't edit saved data, only edit in send data to client.""")
public static boolean villagerInfiniteTrade = false;
@Override
public EnumConfigCategory getCategory() {
return EnumConfigCategory.MISC;
}
@Override
public String getBaseName() {
return "villager-config";
}
}
@@ -6,7 +6,15 @@ import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.config.flags.TransformedConfig;
public class RemovedConfig implements IConfigModule {
@TransformedConfig(name = "example", category = {"removed", "example"}, transform = false)
@TransformedConfig(name = "allow_skip_cooldown", category = {"misc", "revert_raid_changes"}, transform = false)
@TransformedConfig(name = "bad_omen_infinite", category = {"misc", "revert_raid_changes"}, transform = false)
@TransformedConfig(name = "skip_height_check", category = {"misc", "revert_raid_changes"}, transform = false)
@TransformedConfig(name = "skip_self_raid_check", category = {"misc", "revert_raid_changes"}, transform = false)
@TransformedConfig(name = "use_old_position_find", category = {"misc", "revert_raid_changes"}, transform = false)
@TransformedConfig(name = "vanilla_hopper", category = {"misc", "redstone"}, transform = false)
@TransformedConfig(name = "old_replaceable_by_mushrooms", category = {"misc", "old-feature"}, transform = false)
@TransformedConfig(name = "old_nether_portal_collision", category = {"misc", "old-feature"}, transform = false)
@TransformedConfig(name = "better_shulker_box", category = {"misc", "container_expansion"}, transform = false)
@ConfigInfo(baseName = "removed", comments =
"""
RemovedConfig redirect to here, no any function.""")
@@ -4,7 +4,10 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.mojang.logging.LogUtils;
import fun.bm.lophine.config.modules.optimizations.LanguageConfig;
import me.earthme.luminol.config.ConfigManager;
import me.earthme.luminol.config.ConfigsInstance;
import net.minecraft.DetectedVersion;
import net.minecraft.locale.DeprecatedTranslationsInfo;
import net.minecraft.locale.Language;
@@ -12,8 +15,8 @@ import net.minecraft.network.chat.FormattedText;
import net.minecraft.network.chat.Style;
import net.minecraft.util.FormattedCharSequence;
import net.minecraft.util.StringDecomposer;
import org.apache.commons.io.FileUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import java.io.IOException;
import java.io.InputStream;
@@ -23,44 +26,76 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.*;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.logging.Logger;
/*
* authored by: Helvetica Volubi <suisuroru@blue-millennium.fun>
* modified by: Lumine1909 <133463833+Lumine1909@users.noreply.github.com>
* Some of diff form Leaves
*/
* authored by: Helvetica Volubi <suisuroru@blue-millennium.fun>
* modified by: Lumine1909 <133463833+Lumine1909@users.noreply.github.com>
* Some of diff form Leaves
*/
public class ServerI18nUtil {
private static final Logger logger = Logger.getLogger("LangLoader");
private static final String VERSION = DetectedVersion.BUILT_IN.getName();
private static final Logger logger = LogUtils.getClassLogger();
private static final String VERSION = DetectedVersion.BUILT_IN.name();
private static final String BASE_PATH = "cache/lophine/" + VERSION + "/";
private static final String defaultLophineLangPath = "/assets/lophine/lang/en_us.json";
private static final String manifestUrl = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
private static final String resourceBaseUrl = "https://resources.download.minecraft.net/";
// pre-load
private static CompletableFuture<Void> preloadTask;
// paths
private static String langPath;
private static String assetsPath;
private static String versionPath;
private static String manifestPath;
private static String langJsonPath;
private static String lophineLangPath;
public static void init() {
if (Objects.equals(LanguageConfig.lang, "en_us")) {
return;
}
langPath = BASE_PATH + "lang/" + LanguageConfig.lang + ".json";
langJsonPath = "minecraft/lang/" + LanguageConfig.lang + ".json";
lophineLangPath = "/assets/Lophine/lang/" + LanguageConfig.lang + ".json";
logger.info("Starting load language: {}", LanguageConfig.lang);
preloadTask.thenAcceptAsync(v -> loadI18n(LanguageConfig.lang, 2));
}
public static void preInit() {
assetsPath = BASE_PATH + "assets.json";
versionPath = BASE_PATH + VERSION + ".json";
manifestPath = BASE_PATH + "manifest.json";
langJsonPath = "minecraft/lang/" + LanguageConfig.lang + ".json";
logger.info("Starting load language: " + LanguageConfig.lang);
CompletableFuture.runAsync(() -> loadI18n(LanguageConfig.lang, 2));
preloadTask = CompletableFuture.runAsync(() -> preLoadI18n(2));
}
private static void preLoadI18n(int retryTime) {
try {
if (!Files.exists(Path.of(assetsPath))) {
downloadAssets(true);
}
} catch (UnsupportedLanguageException e) {
logger.warn("Unsupported language: {}", LanguageConfig.lang);
// Fallback to English
final ConfigsInstance configsInstance = ConfigManager.configfiles.get("lophine");
configsInstance.setConfig(new String[]{"optimizations", "lang"}, "en_us");
configsInstance.reloadAsync();
} catch (Exception e) {
if (e instanceof MalformedJsonException malformedJson) {
malformedJson.clean();
}
logger.warn("Failed to download language list file: ", e);
if (retryTime > 0) {
preLoadI18n(retryTime - 1);
} else {
logger.error("Failed to download language list file for many times, skip pre-load language.");
}
}
}
private static void loadI18n(String lang, int retryTime) {
@@ -69,15 +104,16 @@ public class ServerI18nUtil {
downloadLang(true);
}
Language.inject(createLangInstance());
logger.info("Successfully loaded language: " + lang);
logger.info("Successfully loaded language: {}", lang);
} catch (Exception e) {
logger.warning("Failed to load language file for " + lang + "\n" + e);
if (e instanceof MalformedJsonException malformedJson) {
malformedJson.clean();
}
logger.warn("Failed to load language file for {}", lang, e);
if (retryTime > 0) {
cleanCache();
loadI18n(lang, retryTime - 1);
} else {
logger.severe("Failed to load for many times, use default lang \"en_us\" instead");
cleanCache();
logger.error("Failed to load for many times, use default lang \"en_us\" instead");
}
}
}
@@ -94,6 +130,10 @@ public class ServerI18nUtil {
JsonObject langEntry = json.getAsJsonObject("objects").getAsJsonObject(langJsonPath);
if (langEntry == null) {
throw new UnsupportedLanguageException();
}
String hash = langEntry.get("hash").getAsString();
if (hash == null || hash.length() < 2) {
throw new IllegalArgumentException("Invalid hash value");
@@ -145,37 +185,31 @@ public class ServerI18nUtil {
fetchAndSave(versionUrl, versionPath);
}
private static String createHttpResponse(String path) throws IOException, InterruptedException {
private static byte[] fetch(String urlString) throws IOException, InterruptedException {
try {
HttpResponse<String> response;
try (HttpClient httpClient = HttpClient.newHttpClient()) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(path))
.uri(URI.create(urlString))
.timeout(Duration.ofSeconds(10))
.build();
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
}
int responseCode = response.statusCode();
if (responseCode != 200) {
logger.info("Unexpected response code: " + responseCode);
logger.info("Response body: " + response.body());
logger.info("Unexpected response code: {}", responseCode);
logger.info("Response body: {}", response.body());
throw new UnsupportedEncodingException("Unexpected response code");
} else {
return response.body();
return response.body().getBytes();
}
} catch (Exception e) {
logger.warning("Error in getting info: " + e.getMessage());
logger.warn("Error in getting info!");
throw e;
}
}
private static byte[] fetch(String urlString) throws IOException, InterruptedException {
String ret = createHttpResponse(urlString);
return ret.getBytes();
}
private static void fetchAndSave(String url, String savePath) throws IOException, InterruptedException {
byte[] data = fetch(url);
Path outputPath = Path.of(savePath);
@@ -183,23 +217,15 @@ public class ServerI18nUtil {
Files.write(outputPath, data, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
}
private static void cleanCache() {
try {
FileUtils.deleteDirectory(Path.of(BASE_PATH).toFile());
} catch (IOException e) {
logger.severe("Cache cleanup failed: " + e);
}
}
private static JsonObject loadJson(String path) {
try {
byte[] data = Files.readAllBytes(Paths.get(path));
return JsonParser.parseString(new String(data)).getAsJsonObject();
} catch (JsonSyntaxException e) {
logger.warning("Corrupt json file: " + e);
throw e;
logger.warn("Corrupt json file!");
throw new MalformedJsonException(e, path);
} catch (Exception e) {
logger.warning("Failed to load local JSON: " + e);
logger.warn("Failed to load local JSON!");
return null;
}
}
@@ -208,6 +234,7 @@ public class ServerI18nUtil {
DeprecatedTranslationsInfo deprecatedTranslationsInfo = DeprecatedTranslationsInfo.loadFromDefaultResource();
Map<String, String> map = new HashMap<>();
parseTranslations(map::put);
loadLophineI18n(map::put);
deprecatedTranslationsInfo.applyToMap(map);
final Map<String, String> map1 = Map.copyOf(map);
return new Language() {
@@ -237,16 +264,52 @@ public class ServerI18nUtil {
};
}
private static void loadLophineI18n(BiConsumer<String, String> bi) {
if (Language.class.getResource(lophineLangPath) != null) {
Language.parseTranslations(bi, lophineLangPath);
} else {
loadLophineI18nDefault(bi);
}
}
public static void loadLophineI18nDefault(BiConsumer<String, String> bi) {
if (Language.class.getResource(defaultLophineLangPath) != null) {
Language.parseTranslations(bi, defaultLophineLangPath);
}
}
private static void parseTranslations(BiConsumer<String, String> output) throws IOException {
Path filePath = Path.of(langPath);
try (InputStream fileStream = Files.newInputStream(filePath)) {
Language.loadFromJson(fileStream, output);
} catch (NoSuchFileException noSuchFileException) {
logger.warning("Couldn't find language file: " + langPath);
throw noSuchFileException;
} catch (NoSuchFileException e) {
logger.warn("Couldn't find language file: {}", langPath);
throw e;
} catch (JsonSyntaxException e) {
throw new MalformedJsonException(e, langPath);
} catch (Exception e) {
logger.warning("Failed to load language from filesystem " + filePath + "\n" + e);
logger.warn("Failed to load language from filesystem {}", filePath);
throw e;
}
}
private static class UnsupportedLanguageException extends Exception {
}
private static class MalformedJsonException extends RuntimeException {
private final Path path;
protected MalformedJsonException(Throwable root, String path) {
super(root);
this.path = Path.of(path);
}
public void clean() {
try {
Files.delete(path);
} catch (IOException e) {
logger.info("Failed to delete malformed JSON file: {}", path);
}
}
}
}
@@ -1,163 +0,0 @@
package fun.bm.lophine.utils;
import fun.bm.lophine.config.modules.misc.ContainerExpansionConfig;
import net.minecraft.core.NonNullList;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.stats.Stats;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.SlotAccess;
import net.minecraft.world.entity.player.Player;
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 net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Optional;
public class ShulkerBoxesUtil {
// Better ShulkerBoxes used
public static HashMap<Player, ShulkerBoxBlockEntity> shulkerMap = new HashMap<>();
public static HashMap<ShulkerBoxBlockEntity, Player> playerMap = new HashMap<>();
// Stackable ShulkerBoxes part
public static boolean shouldCheck() {
return ContainerExpansionConfig.shulkerCount > 1 && ContainerExpansionConfig.shulkerCount <= 64;
}
public static boolean checkShulkerBox(ItemStack itemStack) {
return shouldCheck() && checkIsShulkerBox(itemStack);
}
public static boolean checkIsShulkerBox(ItemStack itemStack) {
return itemStack.getItem() instanceof BlockItem b && b.getBlock() instanceof ShulkerBoxBlock;
}
public static int getItemMaxCount(ItemStack itemStack) {
if (checkShulkerBox(itemStack)) {
return Math.clamp(ContainerExpansionConfig.shulkerCount, 1, 64);
}
return itemStack.getMaxStackSize();
}
public static int getShulkerBoxesMaxCountUnsafe() {
return Math.clamp(ContainerExpansionConfig.shulkerCount, 1, 64);
}
public static boolean emptyShulkerBoxCheck(@NotNull ItemStack stack) {
return stack.getComponents().getOrDefault(DataComponents.CONTAINER, ItemContainerContents.EMPTY).stream().findAny().isEmpty();
}
public static boolean isStackable(ItemStack itemStack) {
return getItemMaxCount(itemStack) > 1 && (!itemStack.isDamageableItem() || !itemStack.isDamaged());
}
public static int getItemStackMaxCountReal(ItemStack stack) {
CompoundTag nbt = Optional.ofNullable(stack.get(DataComponents.CUSTOM_DATA)).orElse(CustomData.EMPTY).copyTag();
return nbt.getInt("Lophine.RealStackSize").orElse(stack.getMaxStackSize());
}
public static ItemStack encodeMaxStackSize(ItemStack itemStack) {
int realMaxStackSize = getItemStackMaxCountReal(itemStack);
int modifiedMaxStackSize = getItemMaxCount(itemStack);
if (itemStack.getMaxStackSize() != modifiedMaxStackSize) {
itemStack.set(DataComponents.MAX_STACK_SIZE, modifiedMaxStackSize);
CompoundTag nbt = itemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
nbt.putInt("Lophine.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("Lophine.RealStackSize");
if (nbt.isEmpty()) {
itemStack.remove(DataComponents.CUSTOM_DATA);
} else {
itemStack.set(DataComponents.CUSTOM_DATA, CustomData.of(nbt));
}
}
return itemStack;
}
// Better ShulkerBox part
public static boolean checkIfCanOpen(ItemStack itemStack) {
return checkIsShulkerBox(itemStack) && itemStack.getCount() == 1;
}
public static boolean checkIfValid(Player player) {
return shulkerMap.get(player) != null;
}
public static void openShulkerBox(Player player, ItemStack item, InteractionHand hand) {
ItemStack itemInHand = item.copy();
ShulkerBoxBlockEntity shulkerBoxEntity = new ShulkerBoxBlockEntity(
player.blockPosition(),
((BlockItem) itemInHand.getItem()).getBlock().defaultBlockState()
);
ItemContainerContents container = itemInHand.getOrDefault(
DataComponents.CONTAINER,
ItemContainerContents.EMPTY
);
NonNullList<ItemStack> items = NonNullList.withSize(27, ItemStack.EMPTY);
for (int i = 0; i < container.items.size(); i++) {
items.set(i, container.items.get(i));
}
shulkerBoxEntity.setItems(items);
shulkerBoxEntity.haveRealBlock = false;
shulkerBoxEntity.slot = hand == InteractionHand.MAIN_HAND ? player.getInventory().getSelectedSlot() : 40;
shulkerBoxEntity.setLevel(player.level());
shulkerMap.put(player, shulkerBoxEntity);
playerMap.put(shulkerBoxEntity, player);
if (player.openMenu(shulkerBoxEntity).isPresent()) {
player.awardStat(Stats.OPEN_SHULKER_BOX);
}
}
public static void shulkerBoxEntityCallBack(ShulkerBoxBlockEntity shulkerBoxEntity) {
Player player = playerMap.get(shulkerBoxEntity);
if (player != null) {
SlotAccess slot = player.getSlot(shulkerBoxEntity.slot);
ItemStack currentItem = slot.get();
ItemStack copy = currentItem.copy();
copy.set(DataComponents.CONTAINER, ItemContainerContents.fromItems(shulkerBoxEntity.getItems()));
slot.set(copy);
}
}
public static void inventoryCallBack(Player player, int slotId) {
ShulkerBoxBlockEntity entity = shulkerMap.get(player);
if (entity != null) {
if ((slotId == entity.slot) && player instanceof ServerPlayer serverPlayer)
serverPlayer.closeContainer();
}
}
public static void clearMap(Player p) {
ShulkerBoxBlockEntity e = shulkerMap.get(p);
if (e != null) playerMap.remove(e);
shulkerMap.remove(p);
}
public static void clearMap(ShulkerBoxBlockEntity e) {
Player p = playerMap.get(e);
if (p != null) shulkerMap.remove(p);
playerMap.remove(e);
}
}
@@ -0,0 +1,26 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.core;
import com.mojang.authlib.GameProfile;
import net.minecraft.network.Connection;
import org.jetbrains.annotations.NotNull;
public record Context(@NotNull GameProfile profile, Connection connection) {
}
@@ -0,0 +1,28 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.core;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.Nullable;
public record IdentifierSelector(@Nullable Context context, @Nullable ServerPlayer player) {
public Object select(ProtocolHandler.Stage stage) {
return stage == ProtocolHandler.Stage.CONFIGURATION ? context : player;
}
}
@@ -0,0 +1,48 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.core;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public interface LeavesCustomPayload extends CustomPacketPayload {
Type<? extends CustomPacketPayload> LEAVES_TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath("leaves", "custom_payload"));
@Override
default @NotNull Type<? extends CustomPacketPayload> type() {
return LEAVES_TYPE;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface ID {
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Codec {
}
}
@@ -0,0 +1,39 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.core;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public interface LeavesProtocol {
boolean isActive();
default int tickerInterval(String tickerID) {
return 1;
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Register {
String namespace();
}
}
@@ -0,0 +1,401 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.core;
import com.mojang.logging.LogUtils;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import org.leavesmc.leaves.protocol.core.invoker.*;
import org.slf4j.Logger;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class LeavesProtocolManager {
private static final Logger LOGGER = LogUtils.getClassLogger();
private static final Map<Class<? extends LeavesCustomPayload>, PayloadReceiverInvokerHolder> PAYLOAD_RECEIVERS = new HashMap<>();
private static final Map<Class<? extends LeavesCustomPayload>, ResourceLocation> IDS = new HashMap<>();
private static final Map<Class<? extends LeavesCustomPayload>, StreamCodec<? super RegistryFriendlyByteBuf, LeavesCustomPayload>> CODECS = new HashMap<>();
private static final Map<ResourceLocation, StreamCodec<? super RegistryFriendlyByteBuf, LeavesCustomPayload>> ID2CODEC = new HashMap<>();
private static final Map<String, BytebufReceiverInvokerHolder> STRICT_BYTEBUF_RECEIVERS = new HashMap<>();
private static final Map<String, BytebufReceiverInvokerHolder> NAMESPACED_BYTEBUF_RECEIVERS = new HashMap<>();
private static final List<BytebufReceiverInvokerHolder> GENERIC_BYTEBUF_RECEIVERS = new ArrayList<>();
private static final Map<String, MinecraftRegisterInvokerHolder> STRICT_MINECRAFT_REGISTER = new HashMap<>();
private static final Map<String, MinecraftRegisterInvokerHolder> NAMESPACED_MINECRAFT_REGISTER = new HashMap<>();
private static final List<MinecraftRegisterInvokerHolder> WILD_MINECRAFT_REGISTER = new ArrayList<>();
private static final List<EmptyInvokerHolder<ProtocolHandler.Ticker>> TICKERS = new ArrayList<>();
private static final List<PlayerInvokerHolder<ProtocolHandler.PlayerJoin>> PLAYER_JOIN = new ArrayList<>();
private static final List<PlayerInvokerHolder<ProtocolHandler.PlayerLeave>> PLAYER_LEAVE = new ArrayList<>();
private static final List<EmptyInvokerHolder<ProtocolHandler.ReloadServer>> RELOAD_SERVER = new ArrayList<>();
private static final List<EmptyInvokerHolder<ProtocolHandler.ReloadDataPack>> RELOAD_DATAPACK = new ArrayList<>();
@SuppressWarnings("unchecked")
public static void init() {
for (Class<?> clazz : getClasses("org.leavesmc.leaves.protocol")) {
if (LeavesCustomPayload.class.isAssignableFrom(clazz) && !clazz.equals(LeavesCustomPayload.class)) {
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
if (!Modifier.isStatic(field.getModifiers())) {
continue;
}
try {
final LeavesCustomPayload.ID id = field.getAnnotation(LeavesCustomPayload.ID.class);
if (id != null && field.getType().equals(ResourceLocation.class)) {
IDS.put((Class<? extends LeavesCustomPayload>) clazz, (ResourceLocation) field.get(null));
}
final LeavesCustomPayload.Codec codec = field.getAnnotation(LeavesCustomPayload.Codec.class);
if (codec != null && field.getType().equals(StreamCodec.class)) {
CODECS.put((Class<? extends LeavesCustomPayload>) clazz, (StreamCodec<? super RegistryFriendlyByteBuf, LeavesCustomPayload>) field.get(null));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
continue;
}
final LeavesProtocol.Register register = clazz.getAnnotation(LeavesProtocol.Register.class);
if (register == null) {
continue;
}
LeavesProtocol protocol;
try {
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
protocol = (LeavesProtocol) constructor.newInstance();
} catch (Throwable throwable) {
LOGGER.error("Failed to load class {}. {}", clazz.getName(), throwable);
return;
}
for (final Method method : clazz.getDeclaredMethods()) {
if (method.isBridge() || method.isSynthetic()) {
continue;
}
method.setAccessible(true);
final ProtocolHandler.Init init = method.getAnnotation(ProtocolHandler.Init.class);
if (init != null) {
InitInvokerHolder holder = new InitInvokerHolder(protocol, method, init);
try {
holder.invoke();
} catch (RuntimeException exception) {
LOGGER.error("Failed to invoke init method {} in {}, {}: {}", method.getName(), clazz.getName(), exception.getCause(), exception.getMessage());
}
continue;
}
final ProtocolHandler.PayloadReceiver payloadReceiver = method.getAnnotation(ProtocolHandler.PayloadReceiver.class);
if (payloadReceiver != null) {
PAYLOAD_RECEIVERS.put(payloadReceiver.payload(), new PayloadReceiverInvokerHolder(protocol, method, payloadReceiver));
continue;
}
final ProtocolHandler.BytebufReceiver bytebufReceiver = method.getAnnotation(ProtocolHandler.BytebufReceiver.class);
if (bytebufReceiver != null) {
String key = bytebufReceiver.key();
BytebufReceiverInvokerHolder holder = new BytebufReceiverInvokerHolder(protocol, method, bytebufReceiver);
if (bytebufReceiver.onlyNamespace()) {
NAMESPACED_BYTEBUF_RECEIVERS.put(key.isEmpty() ? register.namespace() : key, holder);
} else {
if (key.isEmpty()) {
GENERIC_BYTEBUF_RECEIVERS.add(holder);
} else {
if (key.contains(":")) {
STRICT_BYTEBUF_RECEIVERS.put(key, holder);
} else {
STRICT_BYTEBUF_RECEIVERS.put(register.namespace() + ":" + key, holder);
}
}
}
continue;
}
final ProtocolHandler.Ticker ticker = method.getAnnotation(ProtocolHandler.Ticker.class);
if (ticker != null) {
TICKERS.add(new EmptyInvokerHolder<>(protocol, method, ticker));
continue;
}
final ProtocolHandler.PlayerJoin playerJoin = method.getAnnotation(ProtocolHandler.PlayerJoin.class);
if (playerJoin != null) {
PLAYER_JOIN.add(new PlayerInvokerHolder<>(protocol, method, playerJoin));
continue;
}
final ProtocolHandler.PlayerLeave playerLeave = method.getAnnotation(ProtocolHandler.PlayerLeave.class);
if (playerLeave != null) {
PLAYER_LEAVE.add(new PlayerInvokerHolder<>(protocol, method, playerLeave));
continue;
}
final ProtocolHandler.ReloadServer reloadServer = method.getAnnotation(ProtocolHandler.ReloadServer.class);
if (reloadServer != null) {
RELOAD_SERVER.add(new EmptyInvokerHolder<>(protocol, method, reloadServer));
continue;
}
final ProtocolHandler.ReloadDataPack reloadDataPack = method.getAnnotation(ProtocolHandler.ReloadDataPack.class);
if (reloadDataPack != null) {
RELOAD_DATAPACK.add(new EmptyInvokerHolder<>(protocol, method, reloadDataPack));
continue;
}
final ProtocolHandler.MinecraftRegister minecraftRegister = method.getAnnotation(ProtocolHandler.MinecraftRegister.class);
if (minecraftRegister != null) {
String key = minecraftRegister.key();
MinecraftRegisterInvokerHolder holder = new MinecraftRegisterInvokerHolder(protocol, method, minecraftRegister);
if (minecraftRegister.onlyNamespace()) {
NAMESPACED_MINECRAFT_REGISTER.put(key.isEmpty() ? register.namespace() : key, holder);
} else {
if (key.isEmpty()) {
WILD_MINECRAFT_REGISTER.add(holder);
} else {
if (key.contains(":")) {
STRICT_MINECRAFT_REGISTER.put(key, holder);
} else {
STRICT_MINECRAFT_REGISTER.put(register.namespace() + ":" + key, holder);
}
}
}
}
}
}
for (var idInfo : IDS.entrySet()) {
var codec = CODECS.get(idInfo.getKey());
if (codec == null) {
throw new IllegalArgumentException("Payload " + idInfo.getKey() + " is not configured correctly");
}
ID2CODEC.put(idInfo.getValue(), codec);
}
}
public static LeavesCustomPayload decode(ResourceLocation location, FriendlyByteBuf buf) {
var codec = ID2CODEC.get(location);
if (codec == null) {
return null;
}
return codec.decode(ProtocolUtils.decorate(buf));
}
public static void encode(FriendlyByteBuf buf, LeavesCustomPayload payload) {
var location = IDS.get(payload.getClass());
var codec = CODECS.get(payload.getClass());
if (location == null || codec == null) {
throw new IllegalArgumentException("Payload " + payload.getClass() + " is not configured correctly " + location + " " + codec);
}
buf.writeResourceLocation(location);
codec.encode(ProtocolUtils.decorate(buf), payload);
}
public static void handlePayload(IdentifierSelector selector, LeavesCustomPayload payload) {
PayloadReceiverInvokerHolder holder;
if ((holder = PAYLOAD_RECEIVERS.get(payload.getClass())) != null) {
holder.invoke(selector, payload);
}
}
public static boolean handleBytebuf(IdentifierSelector selector, ResourceLocation location, ByteBuf buf) {
RegistryFriendlyByteBuf buf1 = ProtocolUtils.decorate(buf);
BytebufReceiverInvokerHolder holder;
if ((holder = STRICT_BYTEBUF_RECEIVERS.get(location.toString())) != null) {
holder.invoke(selector, buf1);
return true;
}
if ((holder = NAMESPACED_BYTEBUF_RECEIVERS.get(location.getNamespace())) != null) {
if (holder.invoke(selector, buf1)) {
return true;
}
}
for (var holder1 : GENERIC_BYTEBUF_RECEIVERS) {
if (holder1.invoke(selector, buf1)) {
return true;
}
}
return false;
}
public static void handleTick(long tickCount) {
for (var tickerInfo : TICKERS) {
if (tickCount % tickerInfo.owner().tickerInterval(tickerInfo.handler().tickerId()) == 0) {
tickerInfo.invoke();
}
}
}
public static void handlePlayerJoin(ServerPlayer player) {
sendKnownId(player);
for (var join : PLAYER_JOIN) {
join.invoke(player);
}
}
public static void handlePlayerLeave(ServerPlayer player) {
for (var leave : PLAYER_LEAVE) {
leave.invoke(player);
}
}
public static void handleServerReload() {
for (var reload : RELOAD_SERVER) {
reload.invoke();
}
}
public static void handleDataPackReload() {
for (var reload : RELOAD_DATAPACK) {
reload.invoke();
}
}
public static void handleMinecraftRegister(String channelId, IdentifierSelector selector) {
ResourceLocation location = ResourceLocation.tryParse(channelId);
if (location == null) {
return;
}
for (var wildHolder : WILD_MINECRAFT_REGISTER) {
wildHolder.invoke(selector, location);
}
MinecraftRegisterInvokerHolder holder;
if ((holder = STRICT_MINECRAFT_REGISTER.get(location.toString())) != null) {
holder.invoke(selector, location);
}
if ((holder = NAMESPACED_MINECRAFT_REGISTER.get(location.getNamespace())) != null) {
holder.invoke(selector, location);
}
}
private static void sendKnownId(ServerPlayer player) {
Set<String> set = new HashSet<>();
PAYLOAD_RECEIVERS.forEach((clazz, holder) -> {
if (holder.owner().isActive()) {
set.add(IDS.get(clazz).toString());
}
});
STRICT_BYTEBUF_RECEIVERS.forEach((key, holder) -> {
if (holder.owner().isActive()) {
set.add(key);
}
});
ProtocolUtils.sendBytebufPacket(player, ResourceLocation.fromNamespaceAndPath("minecraft", "register"), buf -> {
for (String channel : set) {
buf.writeBytes(channel.getBytes(StandardCharsets.US_ASCII));
buf.writeByte(0);
}
buf.writerIndex(Math.max(buf.writerIndex() - 1, 0));
});
}
public static Set<Class<?>> getClasses(String pack) {
Set<Class<?>> classes = new LinkedHashSet<>();
String packageDirName = pack.replace('.', '/');
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
while (dirs.hasMoreElements()) {
URL url = dirs.nextElement();
String protocol = url.getProtocol();
if ("file".equals(protocol)) {
String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
findClassesInPackageByFile(pack, filePath, classes);
} else if ("jar".equals(protocol)) {
JarFile jar;
try {
jar = ((JarURLConnection) url.openConnection()).getJarFile();
Enumeration<JarEntry> entries = jar.entries();
findClassesInPackageByJar(pack, entries, packageDirName, classes);
} catch (IOException exception) {
LOGGER.warn("Failed to load jar file, {}: {}", exception.getCause(), exception.getMessage());
}
}
}
} catch (IOException exception) {
LOGGER.warn("Failed to load classes, {}: {}", exception.getCause(), exception.getMessage());
}
return classes;
}
private static void findClassesInPackageByFile(String packageName, String packagePath, Set<Class<?>> classes) {
File dir = new File(packagePath);
if (!dir.exists() || !dir.isDirectory()) {
return;
}
File[] dirfiles = dir.listFiles((file) -> file.isDirectory() || file.getName().endsWith(".class"));
if (dirfiles != null) {
for (File file : dirfiles) {
if (file.isDirectory()) {
findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classes);
} else {
String className = file.getName().substring(0, file.getName().length() - 6);
try {
classes.add(Class.forName(packageName + '.' + className));
} catch (ClassNotFoundException exception) {
LOGGER.warn("Failed to load class {}, {}: {}", className, exception.getCause(), exception.getMessage());
}
}
}
}
}
private static void findClassesInPackageByJar(String packageName, Enumeration<JarEntry> entries, String packageDirName, Set<Class<?>> classes) {
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
if (name.charAt(0) == '/') {
name = name.substring(1);
}
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf('/');
if (idx != -1) {
packageName = name.substring(0, idx).replace('/', '.');
}
if (name.endsWith(".class") && !entry.isDirectory()) {
String className = name.substring(packageName.length() + 1, name.length() - 6);
try {
classes.add(Class.forName(packageName + '.' + className));
} catch (ClassNotFoundException exception) {
LOGGER.warn("Failed to load class {}, {}: {}", className, exception.getCause(), exception.getMessage());
}
}
}
}
}
}
@@ -0,0 +1,103 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.core;
import net.minecraft.server.level.ServerPlayer;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class ProtocolHandler {
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Init {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PayloadReceiver {
Class<? extends LeavesCustomPayload> payload();
Stage stage() default Stage.GAME;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BytebufReceiver {
String key() default "";
boolean onlyNamespace() default false;
Stage stage() default Stage.GAME;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Ticker {
String tickerId() default "";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PlayerJoin {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PlayerLeave {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReloadServer {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MinecraftRegister {
String key() default "";
boolean onlyNamespace() default false;
Stage stage() default Stage.CONFIGURATION;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReloadDataPack {
}
public enum Stage {
CONFIGURATION(Context.class),
GAME(ServerPlayer.class);
private final Class<?> identifier;
Stage(Class<?> identifier) {
this.identifier = identifier;
}
public Class<?> identifier() {
return identifier;
}
}
}
@@ -0,0 +1,89 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.core;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.papermc.paper.ServerBuildInfo;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.network.protocol.common.custom.DiscardedPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerCommonPacketListenerImpl;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Consumer;
import java.util.function.Function;
public class ProtocolUtils {
private static final Function<ByteBuf, RegistryFriendlyByteBuf> bufDecorator = buf -> buf instanceof RegistryFriendlyByteBuf registry ? registry : new RegistryFriendlyByteBuf(buf, MinecraftServer.getServer().registryAccess());
private static final byte[] EMPTY = new byte[0];
public static String buildProtocolVersion(String protocol) {
return protocol + "-lophine-" + ServerBuildInfo.buildInfo().asString(ServerBuildInfo.StringRepresentation.VERSION_SIMPLE);
}
public static void sendEmptyPacket(ServerPlayer player, ResourceLocation id) {
player.connection.send(new ClientboundCustomPayloadPacket(new DiscardedPayload(id, EMPTY)));
}
public static void sendBytebufPacket(@NotNull ServerPlayer player, ResourceLocation id, Consumer<? super RegistryFriendlyByteBuf> consumer) {
RegistryFriendlyByteBuf buf = decorate(Unpooled.buffer());
consumer.accept(buf);
player.connection.send(new ClientboundCustomPayloadPacket(new DiscardedPayload(id, ByteBufUtil.getBytes(buf))));
}
public static void sendPayloadPacket(ServerPlayer player, CustomPacketPayload payload) {
player.connection.send(new ClientboundCustomPayloadPacket(payload));
}
public static void sendEmptyPacket(Context context, ResourceLocation id) {
context.connection().send(new ClientboundCustomPayloadPacket(new DiscardedPayload(id, EMPTY)));
}
public static void sendBytebufPacket(@NotNull Context context, ResourceLocation id, Consumer<? super RegistryFriendlyByteBuf> consumer) {
RegistryFriendlyByteBuf buf = decorate(Unpooled.buffer());
consumer.accept(buf);
context.connection().send(new ClientboundCustomPayloadPacket(new DiscardedPayload(id, ByteBufUtil.getBytes(buf))));
}
public static void sendPayloadPacket(Context context, CustomPacketPayload payload) {
context.connection().send(new ClientboundCustomPayloadPacket(payload));
}
public static RegistryFriendlyByteBuf decorate(ByteBuf buf) {
return bufDecorator.apply(buf);
}
public static IdentifierSelector createSelector(ServerCommonPacketListenerImpl common) {
ServerPlayer player = common instanceof ServerGamePacketListenerImpl game ? game.getPlayer() : null;
return new IdentifierSelector(new Context(common.profile, common.connection), player);
}
public static ByteBuf wrapNullable(byte @Nullable [] data) {
return data == null ? Unpooled.wrappedBuffer(EMPTY) : Unpooled.wrappedBuffer(data);
}
}
@@ -0,0 +1,92 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.core.invoker;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public abstract class AbstractInvokerHolder<T> {
protected final LeavesProtocol owner;
protected final Method invoker;
protected final T handler;
protected final Class<?> returnType;
protected final Class<?>[] parameterTypes;
protected final boolean isStatic;
protected AbstractInvokerHolder(LeavesProtocol owner, Method invoker, T handler, @Nullable Class<?> returnType, @NotNull Class<?>... parameterTypes) {
this.owner = owner;
this.invoker = invoker;
this.handler = handler;
this.returnType = returnType;
this.parameterTypes = parameterTypes;
this.isStatic = Modifier.isStatic(invoker.getModifiers());
validateMethodSignature();
}
protected void validateMethodSignature() {
if (returnType != null && !returnType.isAssignableFrom(invoker.getReturnType())) {
throw new IllegalArgumentException("Return type mismatch in " + owner.getClass().getName() + "#" + invoker.getName() +
": expected " + returnType.getName() + " but found " + invoker.getReturnType().getName());
}
Class<?>[] methodParamTypes = invoker.getParameterTypes();
if (methodParamTypes.length != parameterTypes.length) {
throw new IllegalArgumentException("Parameter count mismatch in " + owner.getClass().getName() + "#" + invoker.getName() +
": expected " + parameterTypes.length + " but found " + methodParamTypes.length);
}
for (int i = 0; i < parameterTypes.length; i++) {
if (!parameterTypes[i].isAssignableFrom(methodParamTypes[i])) {
throw new IllegalArgumentException("Parameter type mismatch in " + owner.getClass().getName() + "#" + invoker.getName() +
" at index " + i + ": expected " + parameterTypes[i].getName() + " but found " + methodParamTypes[i].getName());
}
}
}
public LeavesProtocol owner() {
return owner;
}
public T handler() {
return handler;
}
protected Object invoke0(boolean force, Object... args) {
if (!force && !owner.isActive()) {
return null;
}
try {
if (isStatic) {
return invoker.invoke(null, args);
} else {
return invoker.invoke(owner, args);
}
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,35 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.core.invoker;
import net.minecraft.network.FriendlyByteBuf;
import org.leavesmc.leaves.protocol.core.IdentifierSelector;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import java.lang.reflect.Method;
public class BytebufReceiverInvokerHolder extends AbstractInvokerHolder<ProtocolHandler.BytebufReceiver> {
public BytebufReceiverInvokerHolder(LeavesProtocol owner, Method invoker, ProtocolHandler.BytebufReceiver handler) {
super(owner, invoker, handler, null, handler.stage().identifier(), FriendlyByteBuf.class);
}
public boolean invoke(IdentifierSelector selector, FriendlyByteBuf buf) {
return invoke0(false, selector.select(handler.stage()), buf) instanceof Boolean b && b;
}
}
@@ -0,0 +1,32 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.core.invoker;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import java.lang.reflect.Method;
public class EmptyInvokerHolder<T> extends AbstractInvokerHolder<T> {
public EmptyInvokerHolder(LeavesProtocol owner, Method invoker, T handler) {
super(owner, invoker, handler, null);
}
public void invoke() {
invoke0(false);
}
}
@@ -0,0 +1,33 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.core.invoker;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import java.lang.reflect.Method;
public class InitInvokerHolder extends AbstractInvokerHolder<ProtocolHandler.Init> {
public InitInvokerHolder(LeavesProtocol owner, Method invoker, ProtocolHandler.Init handler) {
super(owner, invoker, handler, null);
}
public void invoke() {
invoke0(true);
}
}
@@ -0,0 +1,35 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.core.invoker;
import net.minecraft.resources.ResourceLocation;
import org.leavesmc.leaves.protocol.core.IdentifierSelector;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import java.lang.reflect.Method;
public class MinecraftRegisterInvokerHolder extends AbstractInvokerHolder<ProtocolHandler.MinecraftRegister> {
public MinecraftRegisterInvokerHolder(LeavesProtocol owner, Method invoker, ProtocolHandler.MinecraftRegister handler) {
super(owner, invoker, handler, null, handler.stage().identifier(), ResourceLocation.class);
}
public void invoke(IdentifierSelector selector, ResourceLocation id) {
invoke0(false, selector.select(handler.stage()), id);
}
}
@@ -0,0 +1,35 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.core.invoker;
import org.leavesmc.leaves.protocol.core.IdentifierSelector;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import java.lang.reflect.Method;
public class PayloadReceiverInvokerHolder extends AbstractInvokerHolder<ProtocolHandler.PayloadReceiver> {
public PayloadReceiverInvokerHolder(LeavesProtocol owner, Method invoker, ProtocolHandler.PayloadReceiver handler) {
super(owner, invoker, handler, null, handler.stage().identifier(), handler.payload());
}
public void invoke(IdentifierSelector selector, LeavesCustomPayload payload) {
invoke0(false, selector.select(handler.stage()), payload);
}
}
@@ -0,0 +1,33 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.core.invoker;
import net.minecraft.server.level.ServerPlayer;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import java.lang.reflect.Method;
public class PlayerInvokerHolder<T> extends AbstractInvokerHolder<T> {
public PlayerInvokerHolder(LeavesProtocol owner, Method invoker, T handler) {
super(owner, invoker, handler, null, ServerPlayer.class);
}
public void invoke(ServerPlayer player) {
invoke0(false, player);
}
}
@@ -0,0 +1,134 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux;
import io.netty.buffer.Unpooled;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
// Powered by Servux(https://github.com/sakura-ryoko/servux)
/**
* Network packet splitter code from QuickCarpet by skyrising
*
* @author skyrising
* <p>
* Updated by Sakura to work with newer versions by changing the Reading Session keys,
* and using the HANDLER interface to send packets via the Payload system
* <p>
* Move to Leaves by violetc
*/
public class PacketSplitter {
public static final int MAX_TOTAL_PER_PACKET_S2C = 1048576;
public static final int MAX_PAYLOAD_PER_PACKET_S2C = MAX_TOTAL_PER_PACKET_S2C - 5;
public static final int MAX_TOTAL_PER_PACKET_C2S = 32767;
public static final int MAX_PAYLOAD_PER_PACKET_C2S = MAX_TOTAL_PER_PACKET_C2S - 5;
public static final int DEFAULT_MAX_RECEIVE_SIZE_C2S = 1048576;
public static final int DEFAULT_MAX_RECEIVE_SIZE_S2C = 67108864;
private static final Map<Long, ReadingSession> READING_SESSIONS = new HashMap<>();
public static boolean send(IPacketSplitterHandler handler, FriendlyByteBuf packet, ServerPlayer player) {
return send(handler, packet, player, MAX_PAYLOAD_PER_PACKET_S2C);
}
private static boolean send(IPacketSplitterHandler handler, FriendlyByteBuf packet, ServerPlayer player, int payloadLimit) {
int len = packet.writerIndex();
packet.resetReaderIndex();
for (int offset = 0; offset < len; offset += payloadLimit) {
int thisLen = Math.min(len - offset, payloadLimit);
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(thisLen));
buf.resetWriterIndex();
if (offset == 0) {
buf.writeVarInt(len);
}
buf.writeBytes(packet, thisLen);
handler.encode(player, buf);
}
packet.release();
return true;
}
public static FriendlyByteBuf receive(long key, FriendlyByteBuf buf) {
return receive(key, buf, DEFAULT_MAX_RECEIVE_SIZE_S2C);
}
@Nullable
private static FriendlyByteBuf receive(long key, FriendlyByteBuf buf, int maxLength) {
return READING_SESSIONS.computeIfAbsent(key, ReadingSession::new).receive(buf, maxLength);
}
public interface IPacketSplitterHandler {
void encode(ServerPlayer player, FriendlyByteBuf buf);
}
/**
* I had to fix the `Pair.of` key mappings, because they were removed from MC;
* So I made it into a pre-shared random session 'key' between client and server.
* Generated using 'long key = Random.create(Util.getMeasuringTimeMs()).nextLong();'
* -
* It can be shared to the receiving end via a separate packet; or it can just be
* generated randomly on the receiving end per an expected Reading Session.
* It needs to be stored and changed for every unique session.
*/
private static class ReadingSession {
private final long key;
private int expectedSize = -1;
private FriendlyByteBuf received;
private ReadingSession(long key) {
this.key = key;
}
@Nullable
private FriendlyByteBuf receive(FriendlyByteBuf data, int maxLength) {
data.readerIndex(0);
// data = PacketUtils.slice(data);
if (this.expectedSize < 0) {
this.expectedSize = data.readVarInt();
if (this.expectedSize > maxLength) {
throw new IllegalArgumentException("Payload too large");
}
this.received = new FriendlyByteBuf(Unpooled.buffer(this.expectedSize));
}
this.received.writeBytes(data.readBytes(data.readableBytes()));
if (this.received.writerIndex() >= this.expectedSize) {
READING_SESSIONS.remove(this.key);
return this.received;
}
return null;
}
}
}
@@ -0,0 +1,286 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux;
import com.mojang.logging.LogUtils;
import io.netty.buffer.Unpooled;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
import org.leavesmc.leaves.util.TagUtil;
import org.slf4j.Logger;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
// Powered by Servux(https://github.com/sakura-ryoko/servux)
@LeavesProtocol.Register(namespace = "servux")
public class ServuxEntityDataProtocol implements LeavesProtocol {
private static final Logger LOGGER = LogUtils.getClassLogger();
public static final int PROTOCOL_VERSION = 1;
private static final Map<UUID, Long> readingSessionKeys = new HashMap<>();
@ProtocolHandler.PlayerJoin
public static void onPlayerJoin(ServerPlayer player) {
sendMetadata(player);
}
@ProtocolHandler.PlayerLeave
public static void onPlayerLeave(ServerPlayer player) {
readingSessionKeys.remove(player.getUUID());
}
@ProtocolHandler.PayloadReceiver(payload = EntityDataPayload.class)
public static void onPacketReceive(ServerPlayer player, EntityDataPayload payload) {
switch (payload.packetType) {
case PACKET_C2S_METADATA_REQUEST -> sendMetadata(player);
case PACKET_C2S_BLOCK_ENTITY_REQUEST -> onBlockEntityRequest(player, payload.pos);
case PACKET_C2S_ENTITY_REQUEST -> onEntityRequest(player, payload.entityId);
case PACKET_C2S_NBT_RESPONSE_DATA -> {
UUID uuid = player.getUUID();
long readingSessionKey;
if (!readingSessionKeys.containsKey(uuid)) {
readingSessionKey = RandomSource.create(Util.getMillis()).nextLong();
readingSessionKeys.put(uuid, readingSessionKey);
} else {
readingSessionKey = readingSessionKeys.get(uuid);
}
FriendlyByteBuf fullPacket = PacketSplitter.receive(readingSessionKey, payload.buffer);
if (fullPacket != null) {
readingSessionKeys.remove(uuid);
LOGGER.warn("ServuxEntityDataProtocol,PACKET_C2S_NBT_RESPONSE_DATA NOT Implemented!");
}
}
}
}
public static void sendMetadata(ServerPlayer player) {
CompoundTag metadata = new CompoundTag();
metadata.putString("name", "entity_data");
metadata.putString("id", EntityDataPayload.CHANNEL.toString());
metadata.putInt("version", PROTOCOL_VERSION);
metadata.putString("servux", ServuxProtocol.SERVUX_STRING);
EntityDataPayload payload = new EntityDataPayload(EntityDataPayloadType.PACKET_S2C_METADATA);
payload.nbt.merge(metadata);
sendPacket(player, payload);
}
public static void onBlockEntityRequest(ServerPlayer player, BlockPos pos) {
io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
player.level(),
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(player.position()),
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(player.position()),
() -> {
BlockEntity be = player.level().getBlockEntity(pos);
CompoundTag nbt = be != null ? be.saveWithFullMetadata(player.registryAccess()) : new CompoundTag();
EntityDataPayload payload = new EntityDataPayload(EntityDataPayloadType.PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE);
payload.pos = pos.immutable();
payload.nbt.merge(nbt);
sendPacket(player, payload);
}
);
}
public static void onEntityRequest(ServerPlayer player, int entityId) {
io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
player.level(),
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(player.position()),
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(player.position()),
() -> {
Entity entity = player.level().getEntity(entityId);
CompoundTag nbt = TagUtil.saveEntityWithoutId(entity);
EntityDataPayload payload = new EntityDataPayload(EntityDataPayloadType.PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE);
payload.entityId = entityId;
payload.nbt.merge(nbt);
sendPacket(player, payload);
}
);
}
public static void sendPacket(ServerPlayer player, EntityDataPayload payload) {
if (payload.packetType == EntityDataPayloadType.PACKET_S2C_NBT_RESPONSE_START) {
FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.buffer());
buffer.writeNbt(payload.nbt);
PacketSplitter.send(ServuxEntityDataProtocol::sendWithSplitter, buffer, player);
} else {
ProtocolUtils.sendPayloadPacket(player, payload);
}
}
private static void sendWithSplitter(ServerPlayer player, FriendlyByteBuf buf) {
EntityDataPayload payload = new EntityDataPayload(EntityDataPayloadType.PACKET_S2C_NBT_RESPONSE_DATA);
payload.buffer = buf;
payload.nbt = new CompoundTag();
sendPacket(player, payload);
}
@Override
public boolean isActive() {
return fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.entityProtocol;
}
public enum EntityDataPayloadType {
PACKET_S2C_METADATA(1),
PACKET_C2S_METADATA_REQUEST(2),
PACKET_C2S_BLOCK_ENTITY_REQUEST(3),
PACKET_C2S_ENTITY_REQUEST(4),
PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE(5),
PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE(6),
// For Packet Splitter (Oversize Packets, S2C)
PACKET_S2C_NBT_RESPONSE_START(10),
PACKET_S2C_NBT_RESPONSE_DATA(11),
// For Packet Splitter (Oversize Packets, C2S)
PACKET_C2S_NBT_RESPONSE_START(12),
PACKET_C2S_NBT_RESPONSE_DATA(13);
public final int type;
EntityDataPayloadType(int type) {
this.type = type;
Helper.ID_TO_TYPE.put(type, this);
}
public static EntityDataPayloadType fromId(int id) {
return Helper.ID_TO_TYPE.get(id);
}
private static final class Helper {
static Map<Integer, EntityDataPayloadType> ID_TO_TYPE = new HashMap<>();
}
}
public static class EntityDataPayload implements LeavesCustomPayload {
@ID
public static final ResourceLocation CHANNEL = ServuxProtocol.id("entity_data");
@Codec
public static final StreamCodec<FriendlyByteBuf, EntityDataPayload> CODEC = StreamCodec.of(
(buf, payload) -> {
buf.writeVarInt(payload.packetType.type);
switch (payload.packetType) {
case PACKET_C2S_BLOCK_ENTITY_REQUEST -> {
buf.writeVarInt(payload.transactionId);
buf.writeBlockPos(payload.pos);
}
case PACKET_C2S_ENTITY_REQUEST -> {
buf.writeVarInt(payload.transactionId);
buf.writeVarInt(payload.entityId);
}
case PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE -> {
buf.writeBlockPos(payload.pos);
buf.writeNbt(payload.nbt);
}
case PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE -> {
buf.writeVarInt(payload.entityId);
buf.writeNbt(payload.nbt);
}
case PACKET_S2C_NBT_RESPONSE_DATA, PACKET_C2S_NBT_RESPONSE_DATA ->
buf.writeBytes(payload.buffer.copy());
case PACKET_C2S_METADATA_REQUEST, PACKET_S2C_METADATA -> buf.writeNbt(payload.nbt);
}
},
buf -> {
EntityDataPayloadType type = EntityDataPayloadType.fromId(buf.readVarInt());
if (type == null) {
throw new IllegalStateException("invalid packet type received");
}
EntityDataPayload payload = new EntityDataPayload(type);
switch (type) {
case PACKET_C2S_BLOCK_ENTITY_REQUEST -> {
buf.readVarInt();
payload.pos = buf.readBlockPos().immutable();
}
case PACKET_C2S_ENTITY_REQUEST -> {
buf.readVarInt();
payload.entityId = buf.readVarInt();
}
case PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE -> {
payload.pos = buf.readBlockPos().immutable();
CompoundTag nbt = buf.readNbt();
if (nbt != null) {
payload.nbt.merge(nbt);
}
}
case PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE -> {
payload.entityId = buf.readVarInt();
CompoundTag nbt = buf.readNbt();
if (nbt != null) {
payload.nbt.merge(nbt);
}
}
case PACKET_S2C_NBT_RESPONSE_DATA, PACKET_C2S_NBT_RESPONSE_DATA -> {
payload.buffer = new FriendlyByteBuf(buf.readBytes(buf.readableBytes()));
payload.nbt = new CompoundTag();
}
case PACKET_C2S_METADATA_REQUEST, PACKET_S2C_METADATA -> {
CompoundTag nbt = buf.readNbt();
if (nbt != null) {
payload.nbt.merge(nbt);
}
}
}
return payload;
}
);
private final EntityDataPayloadType packetType;
private final int transactionId;
private int entityId;
private BlockPos pos;
private CompoundTag nbt;
private FriendlyByteBuf buffer;
private EntityDataPayload(EntityDataPayloadType type) {
this.packetType = type;
this.transactionId = -1;
this.entityId = -1;
this.pos = BlockPos.ZERO;
this.nbt = new CompoundTag();
this.clearPacket();
}
private void clearPacket() {
if (this.buffer != null) {
this.buffer.clear();
this.buffer = new FriendlyByteBuf(Unpooled.buffer());
}
}
}
}
@@ -0,0 +1,375 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import com.mojang.serialization.DataResult;
import io.netty.buffer.Unpooled;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.Tag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.level.GameRules;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
import org.leavesmc.leaves.protocol.servux.logger.DataLogger;
import javax.annotation.Nonnull;
import java.util.*;
@LeavesProtocol.Register(namespace = "servux")
public class ServuxHudDataProtocol implements LeavesProtocol {
public static final int PROTOCOL_VERSION = 2;
private static final List<ServerPlayer> players = new ArrayList<>();
private static final int updateInterval = 80;
private static final HashMap<ServerPlayer, List<DataLogger.Type>> loggerPlayers = new HashMap<>();
private static final HashMap<DataLogger.Type, DataLogger<?>> LOGGERS = new HashMap<>();
private static final Table<DataLogger.Type, ServerPlayer, Tag> DATA = HashBasedTable.create();
public static boolean refreshSpawnMetadata = false;
@ProtocolHandler.Init
private static void initializeLoggers() {
if (!LOGGERS.isEmpty()) {
return;
}
for (DataLogger.Type type : DataLogger.Type.VALUES) {
DataLogger<?> entry = type.init();
if (entry != null) {
LOGGERS.put(type, entry);
}
}
}
@ProtocolHandler.PlayerJoin
private static void onPlayerJoin(ServerPlayer player) {
sendHudMetadata(player);
}
@ProtocolHandler.PlayerLeave
private static void onPlayerLeave(ServerPlayer player) {
players.remove(player);
loggerPlayers.remove(player);
}
@ProtocolHandler.PayloadReceiver(payload = HudDataPayload.class)
public static void onPacketReceive(ServerPlayer player, HudDataPayload payload) {
switch (payload.packetType) {
case PACKET_C2S_METADATA_REQUEST -> {
players.add(player);
sendHudMetadata(player);
}
case PACKET_C2S_SPAWN_DATA_REQUEST -> refreshSpawnMetadata(player);
case PACKET_C2S_RECIPE_MANAGER_REQUEST -> refreshRecipeManager(player);
case PACKET_C2S_DATA_LOGGER_REQUEST -> refreshLoggers(player, payload.nbt);
}
}
public static void sendHudMetadata(ServerPlayer player) {
CompoundTag metadata = new CompoundTag();
metadata.putString("name", "hud_metadata");
metadata.putString("id", HudDataPayload.CHANNEL.toString());
metadata.putInt("version", PROTOCOL_VERSION);
metadata.putString("servux", ServuxProtocol.SERVUX_STRING);
if (fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.hudLoggerProtocol) {
CompoundTag nbt = new CompoundTag();
for (DataLogger.Type type : DataLogger.Type.VALUES) {
nbt.putBoolean(type.getSerializedName(), isLoggerTypeEnabled(type));
}
metadata.put("Loggers", nbt);
}
putWorldData(metadata);
sendPacket(player, new HudDataPayload(HudDataPayloadType.PACKET_S2C_METADATA, metadata));
}
public static void refreshSpawnMetadata(ServerPlayer player) {
CompoundTag metadata = new CompoundTag();
metadata.putString("id", HudDataPayload.CHANNEL.toString());
metadata.putString("servux", ServuxProtocol.SERVUX_STRING);
putWorldData(metadata);
sendPacket(player, new HudDataPayload(HudDataPayloadType.PACKET_S2C_SPAWN_DATA, metadata));
}
public static void refreshRecipeManager(ServerPlayer player) {
Collection<RecipeHolder<?>> recipes = MinecraftServer.getServer().getRecipeManager().getRecipes();
CompoundTag nbt = new CompoundTag();
ListTag list = new ListTag();
recipes.forEach((recipeEntry -> {
DataResult<Tag> dr = Recipe.CODEC.encodeStart(NbtOps.INSTANCE, recipeEntry.value());
if (dr.result().isPresent()) {
CompoundTag entry = new CompoundTag();
entry.putString("id_reg", recipeEntry.id().registry().toString());
entry.putString("id_value", recipeEntry.id().location().toString());
entry.put("recipe", dr.result().get());
list.add(entry);
}
}));
nbt.put("RecipeManager", list);
sendPacket(player, new HudDataPayload(HudDataPayloadType.PACKET_S2C_NBT_RESPONSE_START, nbt));
}
public static void refreshWeatherData(ServerPlayer player) {
ServerLevel level = MinecraftServer.getServer().overworld();
if (!level.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE)) {
return;
}
CompoundTag nbt = new CompoundTag();
nbt.putString("id", HudDataPayload.CHANNEL.toString());
nbt.putString("servux", ServuxProtocol.SERVUX_STRING);
if (level.serverLevelData.isRaining() && level.serverLevelData.getRainTime() > -1) {
nbt.putInt("SetRaining", level.serverLevelData.getRainTime());
nbt.putBoolean("isRaining", true);
} else {
nbt.putBoolean("isRaining", false);
}
if (level.serverLevelData.isThundering() && level.serverLevelData.getThunderTime() > -1) {
nbt.putInt("SetThundering", level.serverLevelData.getThunderTime());
nbt.putBoolean("isThundering", true);
} else {
nbt.putBoolean("isThundering", false);
}
if (level.serverLevelData.getClearWeatherTime() > -1) {
nbt.putInt("SetClear", level.serverLevelData.getClearWeatherTime());
}
sendPacket(player, new HudDataPayload(HudDataPayloadType.PACKET_S2C_WEATHER_TICK, nbt));
}
private static void putWorldData(@NotNull CompoundTag metadata) {
ServerLevel level = MinecraftServer.getServer().overworld();
BlockPos spawnPos = level.levelData.getSpawnPos();
metadata.putInt("spawnPosX", spawnPos.getX());
metadata.putInt("spawnPosY", spawnPos.getY());
metadata.putInt("spawnPosZ", spawnPos.getZ());
metadata.putInt("spawnChunkRadius", level.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
if (fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.hudMetadataShareSeed) {
metadata.putLong("worldSeed", level.getSeed());
}
}
public static void refreshLoggers(ServerPlayer player, @Nonnull CompoundTag nbt) {
if (!player.getBukkitEntity().hasPermission("servux.provider.hud_data.logger")) {
player.sendSystemMessage(Component.translatable("servux.hud_data.error.insufficient_for_loggers", "any"));
return;
}
if (nbt.isEmpty()) {
return;
}
List<DataLogger.Type> list = new ArrayList<>();
for (String key : nbt.keySet()) {
DataLogger.Type type = DataLogger.Type.fromStringStatic(key);
if (type != null && nbt.getBooleanOr(key, false)) {
list.add(type);
}
}
if (!list.isEmpty()) {
loggerPlayers.put(player, list);
} else {
loggerPlayers.remove(player);
}
}
private static boolean isLoggerTypeEnabled(DataLogger.Type type) {
return fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.hudEnabledLoggers.contains(type.getSerializedName());
}
@ProtocolHandler.Ticker
public void protocolTick() {
for (ServerPlayer player : players) {
if (refreshSpawnMetadata) {
refreshSpawnMetadata(player);
}
refreshWeatherData(player);
}
refreshSpawnMetadata = false;
}
@ProtocolHandler.Ticker(tickerId = "logger")
public void loggerTick() {
if (!fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.hudLoggerProtocol) {
return;
}
MinecraftServer server = MinecraftServer.getServer();
if (server.getTickCount() % fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.hudUpdateInterval == 0) {
LOGGERS.forEach((type, logger) -> {
if (!isLoggerTypeEnabled(type)) {
return;
}
for (ServerPlayer player : players) {
Tag ret = logger.getResult(server, this, player);
if (ret != null) {
DATA.put(type, player, ret);
}
}
});
}
for (ServerPlayer player : loggerPlayers.keySet()) {
List<DataLogger.Type> list = loggerPlayers.get(player);
if (list.isEmpty()) {
return;
}
CompoundTag nbt = new CompoundTag();
for (DataLogger.Type type : list) {
Tag data = DATA.get(type, player);
if (data != null) {
nbt.put(type.getSerializedName(), data);
DATA.remove(type, player);
}
}
sendPacket(player, new HudDataPayload(HudDataPayloadType.PACKET_S2C_DATA_LOGGER_TICK, nbt));
}
}
public void applyData(DataLogger.Type type, ServerPlayer player, Tag tag) {
DATA.put(type, player, tag);
}
@Override
public boolean isActive() {
return fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.hudMetadataProtocol;
}
@Override
public int tickerInterval(String tickerID) {
return tickerID.equals("logger") ? 1 : updateInterval;
}
public static void sendPacket(ServerPlayer player, HudDataPayload payload) {
if (payload.packetType == HudDataPayloadType.PACKET_S2C_NBT_RESPONSE_START) {
FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.buffer());
buffer.writeNbt(payload.nbt);
PacketSplitter.send(ServuxHudDataProtocol::sendWithSplitter, buffer, player);
} else {
ProtocolUtils.sendPayloadPacket(player, payload);
}
}
private static void sendWithSplitter(ServerPlayer player, FriendlyByteBuf buf) {
sendPacket(player, new HudDataPayload(HudDataPayloadType.PACKET_S2C_NBT_RESPONSE_DATA, buf));
}
public enum HudDataPayloadType {
PACKET_S2C_METADATA(1),
PACKET_C2S_METADATA_REQUEST(2),
PACKET_S2C_SPAWN_DATA(3),
PACKET_C2S_SPAWN_DATA_REQUEST(4),
PACKET_S2C_WEATHER_TICK(5),
PACKET_C2S_RECIPE_MANAGER_REQUEST(6),
PACKET_S2C_DATA_LOGGER_TICK(7),
PACKET_C2S_DATA_LOGGER_REQUEST(8),
// For Packet Splitter (Oversize Packets, S2C)
PACKET_S2C_NBT_RESPONSE_START(10),
PACKET_S2C_NBT_RESPONSE_DATA(11);
public final int type;
HudDataPayloadType(int type) {
this.type = type;
Helper.ID_TO_TYPE.put(type, this);
}
public static HudDataPayloadType fromId(int id) {
return Helper.ID_TO_TYPE.get(id);
}
private static final class Helper {
static Map<Integer, HudDataPayloadType> ID_TO_TYPE = new HashMap<>();
}
}
public record HudDataPayload(HudDataPayloadType packetType, CompoundTag nbt,
FriendlyByteBuf buffer) implements LeavesCustomPayload {
@ID
public static final ResourceLocation CHANNEL = ServuxProtocol.id("hud_metadata");
@Codec
public static final StreamCodec<FriendlyByteBuf, HudDataPayload> CODEC = StreamCodec.of(
(buf, payload) -> {
buf.writeVarInt(payload.packetType().type);
switch (payload.packetType()) {
case PACKET_S2C_NBT_RESPONSE_DATA -> buf.writeBytes(payload.buffer().copy());
case PACKET_C2S_METADATA_REQUEST, PACKET_S2C_METADATA, PACKET_C2S_SPAWN_DATA_REQUEST,
PACKET_S2C_SPAWN_DATA, PACKET_S2C_WEATHER_TICK, PACKET_C2S_RECIPE_MANAGER_REQUEST,
PACKET_C2S_DATA_LOGGER_REQUEST, PACKET_S2C_DATA_LOGGER_TICK -> buf.writeNbt(payload.nbt());
}
},
buf -> {
HudDataPayloadType type = HudDataPayloadType.fromId(buf.readVarInt());
if (type == null) {
throw new IllegalStateException("invalid packet type received");
}
switch (type) {
case PACKET_S2C_NBT_RESPONSE_DATA -> {
return new HudDataPayload(type, new FriendlyByteBuf(buf.readBytes(buf.readableBytes())));
}
case PACKET_C2S_METADATA_REQUEST, PACKET_S2C_METADATA, PACKET_C2S_SPAWN_DATA_REQUEST,
PACKET_S2C_SPAWN_DATA, PACKET_S2C_WEATHER_TICK,
PACKET_C2S_RECIPE_MANAGER_REQUEST, PACKET_C2S_DATA_LOGGER_REQUEST,
PACKET_S2C_DATA_LOGGER_TICK -> {
return new HudDataPayload(type, buf.readNbt());
}
}
throw new IllegalStateException("invalid packet type received");
}
);
public HudDataPayload(HudDataPayloadType type, CompoundTag nbt) {
this(type, nbt, null);
}
public HudDataPayload(HudDataPayloadType type, FriendlyByteBuf buffer) {
this(type, new CompoundTag(), buffer);
}
}
}
@@ -0,0 +1,36 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Contract;
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ServuxProtocol {
public static final String PROTOCOL_ID = "servux";
public static final Logger LOGGER = LoggerFactory.getLogger(PROTOCOL_ID.toUpperCase());
public static final String SERVUX_STRING = ProtocolUtils.buildProtocolVersion(PROTOCOL_ID);
@Contract("_ -> new")
public static ResourceLocation id(String path) {
return ResourceLocation.tryBuild(PROTOCOL_ID, path);
}
}
@@ -0,0 +1,419 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux;
import com.mojang.logging.LogUtils;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceSerializationContext;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
import org.slf4j.Logger;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
// Powered by Servux(https://github.com/sakura-ryoko/servux)
@LeavesProtocol.Register(namespace = "servux")
public class ServuxStructuresProtocol implements LeavesProtocol {
private static final Logger LOGGER = LogUtils.getClassLogger();
public static final int PROTOCOL_VERSION = 2;
private static final int updateInterval = 40;
private static final int timeout = 30 * 20;
private static final Map<Integer, ServerPlayer> players = new ConcurrentHashMap<>();
private static final Map<UUID, Map<ChunkPos, Timeout>> timeouts = new HashMap<>();
private static int retainDistance;
@ProtocolHandler.PlayerJoin
public static void onPlayerJoin(ServerPlayer player) {
sendMetaData(player);
}
@ProtocolHandler.PayloadReceiver(payload = StructuresPayload.class)
public static void onPacketReceive(ServerPlayer player, StructuresPayload payload) {
switch (payload.packetType()) {
case PACKET_C2S_STRUCTURES_REGISTER -> onPlayerSubscribed(player);
case PACKET_C2S_REQUEST_SPAWN_METADATA -> ServuxHudDataProtocol.refreshSpawnMetadata(player); // move to
case PACKET_C2S_STRUCTURES_UNREGISTER -> onPlayerLoggedOut(player);
}
}
@ProtocolHandler.PlayerLeave
public static void onPlayerLoggedOut(@NotNull ServerPlayer player) {
players.remove(player.getId());
timeouts.remove(player.getUUID());
}
@ProtocolHandler.Ticker
public static void tick() {
MinecraftServer server = MinecraftServer.getServer();
int tickCounter = server.getTickCount();
retainDistance = server.getPlayerList().getViewDistance() + 2;
for (ServerPlayer player : players.values()) {
// TODO DimensionChange
refreshTrackedChunks(player, tickCounter);
}
}
public static void onStartedWatchingChunk(ServerPlayer player, LevelChunk chunk) {
MinecraftServer server = player.getServer();
if (players.containsKey(player.getId()) && server != null) {
addChunkTimeoutIfHasReferences(player.getUUID(), chunk, server.getTickCount());
}
}
private static void addChunkTimeoutIfHasReferences(final UUID uuid, LevelChunk chunk, final int tickCounter) {
final ChunkPos pos = chunk.getPos();
if (chunkHasStructureReferences(pos.x, pos.z, chunk.getLevel())) {
final Map<ChunkPos, Timeout> map = timeouts.computeIfAbsent(uuid, (u) -> new HashMap<>());
map.computeIfAbsent(pos, (p) -> new Timeout(tickCounter - timeout));
}
}
private static boolean chunkHasStructureReferences(int chunkX, int chunkZ, @NotNull Level world) {
if (!world.hasChunk(chunkX, chunkZ)) {
return false;
}
ChunkAccess chunk = world.getChunk(chunkX, chunkZ, ChunkStatus.STRUCTURE_STARTS, false);
if (chunk != null) {
for (Map.Entry<Structure, LongSet> entry : chunk.getAllReferences().entrySet()) {
if (!entry.getValue().isEmpty()) {
return true;
}
}
}
return false;
}
public static void onPlayerSubscribed(@NotNull ServerPlayer player) {
if (!players.containsKey(player.getId())) {
players.put(player.getId(), player);
} else {
LOGGER.warn("{} re-register servux:structures", player.getScoreboardName());
}
MinecraftServer server = MinecraftServer.getServer();
sendMetaData(player);
initialSyncStructures(player, player.moonrise$getViewDistanceHolder().getViewDistances().sendViewDistance() + 2, server.getTickCount());
}
private static void sendMetaData(ServerPlayer player) {
CompoundTag tag = new CompoundTag();
tag.putString("name", "structure_bounding_boxes");
tag.putString("id", StructuresPayload.CHANNEL.toString());
tag.putInt("version", PROTOCOL_VERSION);
tag.putString("servux", ServuxProtocol.SERVUX_STRING);
tag.putInt("timeout", timeout);
sendPacket(player, new StructuresPayload(StructuresPayloadType.PACKET_S2C_METADATA, tag));
}
public static void initialSyncStructures(ServerPlayer player, int chunkRadius, int tickCounter) {
UUID uuid = player.getUUID();
ChunkPos center = player.getLastSectionPos().chunk();
Map<Structure, LongSet> references = getStructureReferences(player.level(), center, chunkRadius);
timeouts.remove(uuid);
sendStructures(player, references, tickCounter);
}
public static Map<Structure, LongSet> getStructureReferences(ServerLevel world, ChunkPos center, int chunkRadius) {
Map<Structure, LongSet> references = new HashMap<>();
for (int cx = center.x - chunkRadius; cx <= center.x + chunkRadius; ++cx) {
for (int cz = center.z - chunkRadius; cz <= center.z + chunkRadius; ++cz) {
getReferencesFromChunk(cx, cz, world, references);
}
}
return references;
}
public static void getReferencesFromChunk(int chunkX, int chunkZ, Level world, Map<Structure, LongSet> references) {
if (!world.hasChunk(chunkX, chunkZ)) {
return;
}
ChunkAccess chunk = world.getChunk(chunkX, chunkZ, ChunkStatus.STRUCTURE_STARTS, false);
if (chunk != null) {
for (Map.Entry<Structure, LongSet> entry : chunk.getAllReferences().entrySet()) {
Structure feature = entry.getKey();
LongSet startChunks = entry.getValue();
// TODO add an option && feature != StructureFeature.MINESHAFT (?)
if (!startChunks.isEmpty()) {
references.merge(feature, startChunks, (oldSet, entrySet) -> {
LongOpenHashSet newSet = new LongOpenHashSet(oldSet);
newSet.addAll(entrySet);
return newSet;
});
}
}
}
}
public static void sendStructures(ServerPlayer player, Map<Structure, LongSet> references, int tickCounter) {
ServerLevel world = player.level();
Map<ChunkPos, StructureStart> starts = getStructureStarts(world, references);
if (!starts.isEmpty()) {
addOrRefreshTimeouts(player.getUUID(), references, tickCounter);
ListTag structureList = getStructureList(starts, world);
if (players.containsKey(player.getId())) {
CompoundTag test = new CompoundTag();
test.put("Structures", structureList.copy());
sendPacket(player, new StructuresPayload(StructuresPayloadType.PACKET_S2C_STRUCTURE_DATA_START, test));
}
}
}
public static ListTag getStructureList(Map<ChunkPos, StructureStart> structures, ServerLevel world) {
ListTag list = new ListTag();
StructurePieceSerializationContext ctx = StructurePieceSerializationContext.fromLevel(world);
for (Map.Entry<ChunkPos, StructureStart> entry : structures.entrySet()) {
ChunkPos pos = entry.getKey();
list.add(entry.getValue().createTag(ctx, pos));
}
return list;
}
public static Map<ChunkPos, StructureStart> getStructureStarts(ServerLevel world, Map<Structure, LongSet> references) {
Map<ChunkPos, StructureStart> starts = new HashMap<>();
for (Map.Entry<Structure, LongSet> entry : references.entrySet()) {
Structure structure = entry.getKey();
LongSet startChunks = entry.getValue();
LongIterator iter = startChunks.iterator();
while (iter.hasNext()) {
ChunkPos pos = new ChunkPos(iter.nextLong());
if (!world.hasChunk(pos.x, pos.z)) {
continue;
}
ChunkAccess chunk = world.getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_STARTS, false);
StructureStart start = null;
if (chunk != null) {
start = chunk.getStartForStructure(structure);
}
if (start != null) {
starts.put(pos, start);
}
}
}
return starts;
}
public static void refreshTrackedChunks(ServerPlayer player, int tickCounter) {
UUID uuid = player.getUUID();
Map<ChunkPos, Timeout> map = timeouts.get(uuid);
if (map != null) {
sendAndRefreshExpiredStructures(player, map, tickCounter);
}
}
public static void sendAndRefreshExpiredStructures(ServerPlayer player, Map<ChunkPos, Timeout> map, int tickCounter) {
Set<ChunkPos> positionsToUpdate = new HashSet<>();
for (Map.Entry<ChunkPos, Timeout> entry : map.entrySet()) {
Timeout out = entry.getValue();
if (out.needsUpdate(tickCounter, timeout)) {
positionsToUpdate.add(entry.getKey());
}
}
if (!positionsToUpdate.isEmpty()) {
ServerLevel world = player.level();
ChunkPos center = player.getLastSectionPos().chunk();
Map<Structure, LongSet> references = new HashMap<>();
for (ChunkPos pos : positionsToUpdate) {
if (isOutOfRange(pos, center)) {
map.remove(pos);
} else {
getReferencesFromChunk(pos.x, pos.z, world, references);
Timeout timeout = map.get(pos);
if (timeout != null) {
timeout.setLastSync(tickCounter);
}
}
}
if (!references.isEmpty()) {
sendStructures(player, references, tickCounter);
}
}
}
protected static boolean isOutOfRange(ChunkPos pos, ChunkPos center) {
return Math.abs(pos.x - center.x) > retainDistance || Math.abs(pos.z - center.z) > retainDistance;
}
public static void addOrRefreshTimeouts(final UUID uuid, final Map<Structure, LongSet> references, final int tickCounter) {
Map<ChunkPos, Timeout> map = timeouts.computeIfAbsent(uuid, (u) -> new HashMap<>());
for (LongSet chunks : references.values()) {
for (Long chunkPosLong : chunks) {
final ChunkPos pos = new ChunkPos(chunkPosLong);
map.computeIfAbsent(pos, (p) -> new Timeout(tickCounter)).setLastSync(tickCounter);
}
}
}
public static void sendPacket(ServerPlayer player, StructuresPayload payload) {
if (payload.packetType() == StructuresPayloadType.PACKET_S2C_STRUCTURE_DATA_START) {
FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.buffer());
buffer.writeNbt(payload.nbt());
PacketSplitter.send(ServuxStructuresProtocol::sendWithSplitter, buffer, player);
} else {
ProtocolUtils.sendPayloadPacket(player, payload);
}
}
private static void sendWithSplitter(ServerPlayer player, FriendlyByteBuf buf) {
sendPacket(player, new StructuresPayload(StructuresPayloadType.PACKET_S2C_STRUCTURE_DATA, buf));
}
@Override
public int tickerInterval(String tickerID) {
return updateInterval;
}
@Override
public boolean isActive() {
return fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.structureProtocol;
}
public enum StructuresPayloadType {
PACKET_S2C_METADATA(1),
PACKET_S2C_STRUCTURE_DATA(2),
PACKET_C2S_STRUCTURES_REGISTER(3),
PACKET_C2S_STRUCTURES_UNREGISTER(4),
PACKET_S2C_STRUCTURE_DATA_START(5),
PACKET_S2C_SPAWN_METADATA(10),
PACKET_C2S_REQUEST_SPAWN_METADATA(11);
public final int type;
StructuresPayloadType(int type) {
this.type = type;
Helper.ID_TO_TYPE.put(type, this);
}
public static StructuresPayloadType fromId(int id) {
return Helper.ID_TO_TYPE.get(id);
}
private static final class Helper {
static Map<Integer, StructuresPayloadType> ID_TO_TYPE = new HashMap<>();
}
}
public record StructuresPayload(StructuresPayloadType packetType, CompoundTag nbt,
FriendlyByteBuf buffer) implements LeavesCustomPayload {
@ID
public static final ResourceLocation CHANNEL = ServuxProtocol.id("structures");
@Codec
public static final StreamCodec<FriendlyByteBuf, StructuresPayload> CODEC = StreamCodec.of(
(buf, payload) -> {
buf.writeVarInt(payload.packetType().type);
if (payload.packetType().equals(StructuresPayloadType.PACKET_S2C_STRUCTURE_DATA)) {
buf.writeBytes(payload.buffer().readBytes(payload.buffer().readableBytes()));
} else {
buf.writeNbt(payload.nbt());
}
},
buf -> {
int i = buf.readVarInt();
StructuresPayloadType type = StructuresPayloadType.fromId(i);
if (type == null) {
throw new IllegalStateException("invalid packet type received");
} else if (type.equals(StructuresPayloadType.PACKET_S2C_STRUCTURE_DATA)) {
return new StructuresPayload(type, new FriendlyByteBuf(buf.readBytes(buf.readableBytes())));
} else {
return new StructuresPayload(type, buf.readNbt());
}
}
);
public StructuresPayload(StructuresPayloadType packetType, CompoundTag nbt) {
this(packetType, nbt, null);
}
public StructuresPayload(StructuresPayloadType packetType, FriendlyByteBuf buffer) {
this(packetType, new CompoundTag(), buffer);
}
}
public static class Timeout {
private int lastSync;
public Timeout(int currentTick) {
this.lastSync = currentTick;
}
public boolean needsUpdate(int currentTick, int timeout) {
return currentTick - this.lastSync >= timeout;
}
public void setLastSync(int tickCounter) {
this.lastSync = tickCounter;
}
}
}
@@ -0,0 +1,281 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics;
import com.google.common.collect.ImmutableMap;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.LongArrayTag;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.ticks.ScheduledTick;
import net.minecraft.world.ticks.TickPriority;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Unmodifiable;
import org.leavesmc.leaves.protocol.servux.ServuxProtocol;
import org.leavesmc.leaves.protocol.servux.litematics.container.LitematicaBlockStateContainer;
import org.leavesmc.leaves.protocol.servux.litematics.utils.FileType;
import org.leavesmc.leaves.protocol.servux.litematics.utils.NbtUtils;
import org.leavesmc.leaves.protocol.servux.litematics.utils.PositionUtils;
import java.util.*;
public record LitematicaSchematic(Map<String, SubRegion> subRegions, SchematicMetadata metadata) {
public static final int MINECRAFT_DATA_VERSION = SharedConstants.getProtocolVersion();
public static final int SCHEMATIC_VERSION = 7;
@NotNull
@Contract("_ -> new")
public static LitematicaSchematic readFromNBT(@NotNull CompoundTag nbt) {
if (nbt.contains("Version")) {
final int version = nbt.getIntOr("Version", -1);
final int minecraftDataVersion = nbt.contains("MinecraftDataVersion") ? nbt.getInt("MinecraftDataVersion").orElseThrow() : SharedConstants.getProtocolVersion();
if (version >= 1 && version <= SCHEMATIC_VERSION) {
SchematicMetadata metadata = SchematicMetadata.readFromNBT(nbt.getCompoundOrEmpty("Metadata"), version, minecraftDataVersion, FileType.LITEMATICA_SCHEMATIC);
Map<String, SubRegion> subRegions = readSubRegionsFromNBT(nbt.getCompoundOrEmpty("Regions"), version, minecraftDataVersion);
return new LitematicaSchematic(subRegions, metadata);
} else {
throw new RuntimeException("Unsupported or future schematic version");
}
} else {
throw new RuntimeException("The schematic doesn't have version information, and can't be safely loaded!");
}
}
@NotNull
private static Map<String, SubRegion> readSubRegionsFromNBT(@NotNull CompoundTag tag, int version, int minecraftDataVersion) {
Map<String, SubRegion> subRegions = new HashMap<>();
for (String regionName : tag.keySet()) {
Tag region = tag.get(regionName);
if (region == null || region.getId() != Tag.TAG_COMPOUND) {
throw new RuntimeException("Unknown region: " + regionName);
}
subRegions.put(regionName, SubRegion.readFromNBT(tag.getCompoundOrEmpty(regionName), version, minecraftDataVersion));
}
return subRegions;
}
@NotNull
@Unmodifiable
public Map<String, BlockPos> getAreaSizes() {
ImmutableMap.Builder<String, BlockPos> builder = ImmutableMap.builderWithExpectedSize(subRegions.size());
for (Map.Entry<String, SubRegion> entry : subRegions.entrySet()) {
builder.put(entry.getKey(), entry.getValue().size());
}
return builder.build();
}
@NotNull
public SubRegion getSubRegion(String name) {
return Objects.requireNonNull(subRegions.get(name));
}
public record SubRegion(
LitematicaBlockStateContainer blockContainers,
Map<BlockPos, CompoundTag> tileEntities,
Map<BlockPos, ScheduledTick<Block>> pendingBlockTicks,
Map<BlockPos, ScheduledTick<Fluid>> pendingFluidTicks,
List<EntityInfo> entities,
BlockPos position,
BlockPos size
) {
@NotNull
@Contract("_, _, _ -> new")
public static SubRegion readFromNBT(@NotNull CompoundTag regionTag, int version, int minecraftDataVersion) {
BlockPos position = NbtUtils.readBlockPos(regionTag.getCompoundOrEmpty("Position"));
BlockPos size = NbtUtils.readBlockPos(regionTag.getCompoundOrEmpty("Size"));
if (position == null || size == null) {
throw new IllegalArgumentException("Invalid region");
}
Map<BlockPos, CompoundTag> tileEntities;
List<EntityInfo> entities;
if (version >= 2) {
tileEntities = readTileEntitiesFromNBT(regionTag.getListOrEmpty("TileEntities"));
entities = readEntitiesFromNBT(regionTag.getListOrEmpty("Entities"));
} else {
tileEntities = readTileEntitiesFromNBT_v1(regionTag.getListOrEmpty("TileEntities"));
entities = readEntitiesFromNBT_v1(regionTag.getListOrEmpty("Entities"));
}
Map<BlockPos, ScheduledTick<Block>> pendingBlockTicks = null;
if (version >= 3) {
pendingBlockTicks = readPendingTicksFromNBT(regionTag.getListOrEmpty("PendingBlockTicks"), BuiltInRegistries.BLOCK, "Block", Blocks.AIR);
}
Map<BlockPos, ScheduledTick<Fluid>> pendingFluidTicks = null;
if (version >= 5) {
pendingFluidTicks = readPendingTicksFromNBT(regionTag.getListOrEmpty("PendingFluidTicks"), BuiltInRegistries.FLUID, "Fluid", Fluids.EMPTY);
}
LitematicaBlockStateContainer blockContainers = null;
Tag blockState = regionTag.get("BlockStates");
if (blockState != null && blockState.getId() == Tag.TAG_LONG_ARRAY) {
ListTag palette = regionTag.getListOrEmpty("BlockStatePalette");
long[] blockStateArr = ((LongArrayTag) blockState).getAsLongArray();
BlockPos posEndRel = PositionUtils.getRelativeEndPositionFromAreaSize(size).offset(position);
BlockPos posMin = PositionUtils.getMinCorner(position, posEndRel);
BlockPos posMax = PositionUtils.getMaxCorner(position, posEndRel);
BlockPos containerSize = posMax.subtract(posMin).offset(1, 1, 1);
blockContainers = LitematicaBlockStateContainer.createFrom(palette, blockStateArr, containerSize);
if (minecraftDataVersion < MINECRAFT_DATA_VERSION) {
ServuxProtocol.LOGGER.warn("Cannot process minecraft data version: {}", minecraftDataVersion);
}
}
return new SubRegion(blockContainers, tileEntities, pendingBlockTicks, pendingFluidTicks, entities, position, size);
}
private static List<EntityInfo> readEntitiesFromNBT(ListTag tagList) {
List<EntityInfo> entityList = new ArrayList<>();
final int size = tagList.size();
for (int i = 0; i < size; ++i) {
CompoundTag entityData = tagList.getCompoundOrEmpty(i);
Vec3 posVec = NbtUtils.readEntityPositionFromTag(entityData);
if (posVec != null && !entityData.isEmpty()) {
entityList.add(new EntityInfo(posVec, entityData));
}
}
return entityList;
}
private static Map<BlockPos, CompoundTag> readTileEntitiesFromNBT(ListTag tagList) {
Map<BlockPos, CompoundTag> tileMap = new HashMap<>();
final int size = tagList.size();
for (int i = 0; i < size; ++i) {
CompoundTag tag = tagList.getCompoundOrEmpty(i);
BlockPos pos = NbtUtils.readBlockPos(tag);
if (pos != null && !tag.isEmpty()) {
tileMap.put(pos, tag);
}
}
return tileMap;
}
private static <T> Map<BlockPos, ScheduledTick<T>> readPendingTicksFromNBT(ListTag tagList, Registry<T> registry, String tagName, T emptyValue) {
Map<BlockPos, ScheduledTick<T>> tickMap = new HashMap<>();
final int size = tagList.size();
for (int i = 0; i < size; ++i) {
CompoundTag tag = tagList.getCompoundOrEmpty(i);
// XXX these were accidentally saved as longs in version 3
if (!tag.contains("Time")) {
continue;
}
T target;
ResourceLocation resourceLocation = ResourceLocation.tryParse(tag.getStringOr(tagName, ""));
if (resourceLocation == null) {
continue;
}
Optional<Holder.Reference<T>> tReference = registry.get(resourceLocation);
if (tReference.isEmpty()) {
continue;
}
target = tReference.get().value();
if (target == emptyValue) {
continue;
}
BlockPos pos = new BlockPos(tag.getIntOr("x", 0), tag.getIntOr("y", 0), tag.getIntOr("z", 0));
TickPriority priority = TickPriority.byValue(tag.getIntOr("Priority", 0));
int scheduledTime = tag.getIntOr("Time", 0);
long subTick = tag.getLongOr("SubTick", 0L);
tickMap.put(pos, new ScheduledTick<>(target, pos, scheduledTime, priority, subTick));
}
return tickMap;
}
private static List<EntityInfo> readEntitiesFromNBT_v1(ListTag tagList) {
List<EntityInfo> entityList = new ArrayList<>();
final int size = tagList.size();
for (int i = 0; i < size; ++i) {
CompoundTag tag = tagList.getCompoundOrEmpty(i);
Vec3 posVec = NbtUtils.readVec3(tag);
CompoundTag entityData = tag.getCompoundOrEmpty("EntityData");
if (posVec != null && !entityData.isEmpty()) {
// Update the correct position to the TileEntity NBT, where it is stored in version 2
NbtUtils.writeEntityPositionToTag(posVec, entityData);
entityList.add(new EntityInfo(posVec, entityData));
}
}
return entityList;
}
private static Map<BlockPos, CompoundTag> readTileEntitiesFromNBT_v1(ListTag tagList) {
Map<BlockPos, CompoundTag> tileMap = new HashMap<>();
final int size = tagList.size();
for (int i = 0; i < size; ++i) {
CompoundTag tag = tagList.getCompoundOrEmpty(i);
CompoundTag tileNbt = tag.getCompoundOrEmpty("TileNBT");
// Note: This within-schematic relative position is not inside the tile tag!
BlockPos pos = NbtUtils.readBlockPos(tag);
if (pos != null && !tileNbt.isEmpty()) {
// Update the correct position to the entity NBT, where it is stored in version 2
NbtUtils.writeBlockPosToTag(pos, tileNbt);
tileMap.put(pos, tileNbt);
}
}
return tileMap;
}
}
public record EntityInfo(Vec3 posVec, CompoundTag nbt) {
public EntityInfo(Vec3 posVec, CompoundTag nbt) {
this.posVec = posVec;
if (nbt.contains("SleepingX")) {
nbt.putInt("SleepingX", Mth.floor(posVec.x));
}
if (nbt.contains("SleepingY")) {
nbt.putInt("SleepingY", Mth.floor(posVec.y));
}
if (nbt.contains("SleepingZ")) {
nbt.putInt("SleepingZ", Mth.floor(posVec.z));
}
this.nbt = nbt;
}
}
}
@@ -0,0 +1,62 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics;
import net.minecraft.core.Vec3i;
import net.minecraft.nbt.CompoundTag;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.servux.litematics.utils.FileType;
import org.leavesmc.leaves.protocol.servux.litematics.utils.NbtUtils;
import org.leavesmc.leaves.protocol.servux.litematics.utils.Schema;
import javax.annotation.Nullable;
import java.util.Objects;
public record SchematicMetadata(
String name,
String author,
String description,
Vec3i enclosingSize,
long timeCreated,
long timeModified,
int minecraftDataVersion,
int schematicVersion,
Schema schema,
FileType type,
int regionCount,
long totalVolume,
long totalBlocks,
@Nullable int[] thumbnailPixelData
) {
@NotNull
@Contract("_, _, _, _ -> new")
public static SchematicMetadata readFromNBT(@NotNull CompoundTag nbt, int version, int minecraftDataVersion, FileType fileType) {
String name = nbt.getStringOr("Name", "?");
String author = nbt.getStringOr("Author", "?");
String description = nbt.getStringOr("Description", "");
int regionCount = nbt.getIntOr("RegionCount", -1);
long timeCreated = nbt.getLongOr("TimeCreated", -1L);
long timeModified = nbt.getLongOr("TimeModified", -1L);
long totalVolume = nbt.getIntOr("TotalVolume", -1);
long totalBlocks = nbt.getIntOr("TotalBlocks", -1);
Vec3i enclosingSize = Objects.requireNonNullElse(NbtUtils.readVec3iFromTag(nbt.getCompoundOrEmpty("EnclosingSize")), Vec3i.ZERO);
int[] thumbnailPixelData = nbt.getIntArray("PreviewImageData").orElse(null);
return new SchematicMetadata(name, author, description, enclosingSize, timeCreated, timeModified, minecraftDataVersion, version, Schema.getSchemaByDataVersion(minecraftDataVersion), fileType, regionCount, totalVolume, totalBlocks, thumbnailPixelData);
}
}
@@ -0,0 +1,456 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics;
import io.netty.buffer.Unpooled;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtAccounter;
import net.minecraft.nbt.Tag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
import org.leavesmc.leaves.protocol.servux.PacketSplitter;
import org.leavesmc.leaves.protocol.servux.ServuxProtocol;
import org.leavesmc.leaves.protocol.servux.litematics.placement.SchematicPlacement;
import org.leavesmc.leaves.protocol.servux.litematics.utils.NbtUtils;
import org.leavesmc.leaves.protocol.servux.litematics.utils.ReplaceBehavior;
import org.leavesmc.leaves.util.TagUtil;
import java.util.*;
@LeavesProtocol.Register(namespace = "servux")
public class ServuxLitematicsProtocol implements LeavesProtocol {
public static final int PROTOCOL_VERSION = 1;
private static final CompoundTag metadata = new CompoundTag();
private static final Map<UUID, Long> playerSession = new HashMap<>();
@ProtocolHandler.Init
public static void init() {
metadata.putString("name", "litematic_data");
metadata.putString("id", ServuxLitematicaPayload.CHANNEL.toString());
metadata.putInt("version", PROTOCOL_VERSION);
metadata.putString("servux", ServuxProtocol.SERVUX_STRING);
}
public static boolean hasPermission(ServerPlayer player) {
return player.getBukkitEntity().hasPermission("leaves.protocol.litematics");
}
public static void sendMetaData(ServerPlayer player) {
ServuxLitematicaPayload send = new ServuxLitematicaPayload(ServuxLitematicaPayloadType.PACKET_S2C_METADATA);
send.nbt.merge(metadata);
encodeServerData(player, send);
}
public static void encodeServerData(ServerPlayer player, @NotNull ServuxLitematicaPayload packet) {
if (packet.packetType.equals(ServuxLitematicaPayloadType.PACKET_S2C_NBT_RESPONSE_START)) {
FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.buffer());
buffer.writeVarInt(packet.getTransactionId());
buffer.writeNbt(packet.getCompound());
PacketSplitter.send(ServuxLitematicsProtocol::sendWithSplitter, buffer, player);
} else {
ProtocolUtils.sendPayloadPacket(player, packet);
}
}
private static void sendWithSplitter(ServerPlayer player, FriendlyByteBuf buf) {
ServuxLitematicaPayload payload = new ServuxLitematicaPayload(ServuxLitematicaPayloadType.PACKET_S2C_NBT_RESPONSE_DATA);
payload.buffer = buf;
payload.nbt = new CompoundTag();
encodeServerData(player, payload);
}
@ProtocolHandler.PlayerJoin
public static void onPlayerJoin(ServerPlayer player) {
sendMetaData(player);
}
@ProtocolHandler.PayloadReceiver(payload = ServuxLitematicaPayload.class)
public static void onPacketReceive(ServerPlayer player, ServuxLitematicaPayload payload) {
if (!hasPermission(player)) {
return;
}
switch (payload.packetType) {
case PACKET_C2S_METADATA_REQUEST -> sendMetaData(player);
case PACKET_C2S_BLOCK_ENTITY_REQUEST -> onBlockEntityRequest(player, payload.getPos());
case PACKET_C2S_ENTITY_REQUEST -> onEntityRequest(player, payload.getEntityId());
case PACKET_C2S_BULK_ENTITY_NBT_REQUEST ->
onBulkEntityRequest(player, payload.getChunkPos(), payload.getCompound());
case PACKET_C2S_NBT_RESPONSE_DATA -> {
ServuxProtocol.LOGGER.debug("nbt response data");
UUID uuid = player.getUUID();
Long session = playerSession.getOrDefault(uuid, new Random().nextLong());
playerSession.put(uuid, session);
FriendlyByteBuf fullPacket = PacketSplitter.receive(session, payload.getBuffer());
if (fullPacket == null) {
ServuxProtocol.LOGGER.debug("packet is none");
return;
}
playerSession.remove(uuid);
fullPacket.readVarInt();
Tag tag = FriendlyByteBuf.readNbt(fullPacket, new NbtAccounter(fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.litematicsMaxNbtSize == -1 ? Long.MAX_VALUE : fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.litematicsMaxNbtSize, 512));
if (!(tag instanceof CompoundTag)) {
ServuxProtocol.LOGGER.error("cannot read nbt tag from packet");
return;
}
handleClientPasteRequest(player, (CompoundTag) tag);
}
}
}
public static void onBlockEntityRequest(ServerPlayer player, BlockPos pos) {
if (!hasPermission(player)) {
return;
}
io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
player.level(),
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(player.position()),
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(player.position()),
() -> {
BlockEntity be = player.level().getBlockEntity(pos);
CompoundTag tag = be != null ? be.saveWithFullMetadata(MinecraftServer.getServer().registryAccess()) : new CompoundTag();
ServuxLitematicaPayload payload = new ServuxLitematicaPayload(ServuxLitematicaPayloadType.PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE);
payload.pos = pos;
payload.nbt = tag;
encodeServerData(player, payload);
}
);
}
public static void onEntityRequest(ServerPlayer player, int entityId) {
if (!hasPermission(player)) {
return;
}
io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
player.level(),
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(player.position()),
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(player.position()),
() -> {
Entity entity = player.level().getEntity(entityId);
if (entity == null) {
return;
}
CompoundTag tag = new CompoundTag();
ServuxLitematicaPayload payload = new ServuxLitematicaPayload(ServuxLitematicaPayloadType.PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE);
payload.entityId = entityId;
if (entity instanceof net.minecraft.world.entity.player.Player) {
ResourceLocation loc = EntityType.getKey(entity.getType());
tag = TagUtil.saveEntity(entity);
tag.putString("id", loc.toString());
payload.nbt = tag;
encodeServerData(player, payload);
} else if (TagUtil.saveEntityAsPassenger(entity, tag)) {
payload.nbt = tag;
encodeServerData(player, payload);
}
}
);
}
public static void onBulkEntityRequest(ServerPlayer player, ChunkPos chunkPos, CompoundTag req) {
if (req == null || req.isEmpty()) {
return;
}
ServerLevel world = player.level();
ChunkAccess chunk = world.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.FULL, false);
if (chunk == null) {
return;
}
if (!req.contains("Task") || req.getStringOr("Task", "").equals("BulkEntityRequest")) {
ServuxProtocol.LOGGER.debug("litematic_data: Sending Bulk NBT Data for ChunkPos [{}] to player {}", chunkPos, player.getName().getString());
long timeStart = System.currentTimeMillis();
ListTag tileList = new ListTag();
ListTag entityList = new ListTag();
int minY = req.getIntOr("minY", -64);
int maxY = req.getIntOr("maxY", 319);
BlockPos pos1 = new BlockPos(chunkPos.getMinBlockX(), minY, chunkPos.getMinBlockZ());
BlockPos pos2 = new BlockPos(chunkPos.getMaxBlockX(), maxY, chunkPos.getMaxBlockZ());
AABB bb = AABB.encapsulatingFullBlocks(pos1, pos2);
Set<BlockPos> teSet = chunk.getBlockEntitiesPos();
List<Entity> entities = world.getEntitiesOfClass(Entity.class, bb, e -> !(e instanceof Player));
for (BlockPos tePos : teSet) {
if ((tePos.getX() < chunkPos.getMinBlockX() || tePos.getX() > chunkPos.getMaxBlockX()) ||
(tePos.getZ() < chunkPos.getMinBlockZ() || tePos.getZ() > chunkPos.getMaxBlockZ()) ||
(tePos.getY() < minY || tePos.getY() > maxY)) {
continue;
}
BlockEntity be = world.getBlockEntity(tePos);
CompoundTag beTag = TagUtil.saveTileWithId(be);
tileList.add(beTag);
}
for (Entity entity : entities) {
CompoundTag entTag = new CompoundTag();
if (TagUtil.saveEntity(entity, entTag)) {
Vec3 posVec = new Vec3(entity.getX() - pos1.getX(), entity.getY() - pos1.getY(), entity.getZ() - pos1.getZ());
NbtUtils.writeEntityPositionToTag(posVec, entTag);
entTag.putInt("entityId", entity.getId());
entityList.add(entTag);
}
}
CompoundTag output = new CompoundTag();
output.putString("Task", "BulkEntityReply");
output.put("TileEntities", tileList);
output.put("Entities", entityList);
output.putInt("chunkX", chunkPos.x);
output.putInt("chunkZ", chunkPos.z);
ServuxProtocol.LOGGER.debug("process bulk entity used: {}ms", System.currentTimeMillis() - timeStart);
ServuxLitematicaPayload send = new ServuxLitematicaPayload(ServuxLitematicaPayloadType.PACKET_S2C_NBT_RESPONSE_START);
send.nbt.merge(output);
encodeServerData(player, send);
}
}
public static void handleClientPasteRequest(ServerPlayer player, @NotNull CompoundTag tags) {
if (!hasPermission(player)) {
player.getBukkitEntity().sendActionBar(Component.text("Insufficient Permissions for the Litematic paste operation", NamedTextColor.RED));
return;
}
if (!player.isCreative()) {
player.getBukkitEntity().sendActionBar(Component.text("Creative Mode is required for the Litematic paste operation", NamedTextColor.RED));
return;
}
if (tags.getStringOr("Task", "").equals("LitematicaPaste")) {
ServuxProtocol.LOGGER.debug("litematic_data: Servux Paste request from player {}", player.getName().getString());
ServerLevel serverLevel = player.level();
long timeStart = System.currentTimeMillis();
SchematicPlacement placement = SchematicPlacement.createFromNbt(tags);
ReplaceBehavior replaceMode = ReplaceBehavior.fromStringStatic(tags.getStringOr("ReplaceMode", ReplaceBehavior.NONE.name()));
io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
player.level(),
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(player.position()),
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(player.position()),
() -> {
placement.pasteTo(serverLevel, replaceMode);
long timeElapsed = System.currentTimeMillis() - timeStart;
player.getBukkitEntity().sendActionBar(
Component.text("Pasted ")
.append(Component.text(placement.getName(), NamedTextColor.AQUA))
.append(Component.text(" to world "))
.append(Component.text(serverLevel.serverLevelData.getLevelName(), NamedTextColor.LIGHT_PURPLE))
.append(Component.text(" in "))
.append(Component.text(timeElapsed, NamedTextColor.GREEN))
.append(Component.text("ms"))
);
});
}
}
@Override
public boolean isActive() {
return fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.litematicsEnabled;
}
public enum ServuxLitematicaPayloadType {
PACKET_S2C_METADATA(1),
PACKET_C2S_METADATA_REQUEST(2),
PACKET_C2S_BLOCK_ENTITY_REQUEST(3),
PACKET_C2S_ENTITY_REQUEST(4),
PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE(5),
PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE(6),
PACKET_C2S_BULK_ENTITY_NBT_REQUEST(7),
// For Packet Splitter (Oversize Packets, S2C)
PACKET_S2C_NBT_RESPONSE_START(10),
PACKET_S2C_NBT_RESPONSE_DATA(11),
// For Packet Splitter (Oversize Packets, C2S)
PACKET_C2S_NBT_RESPONSE_START(12),
PACKET_C2S_NBT_RESPONSE_DATA(13);
public final int type;
ServuxLitematicaPayloadType(int type) {
this.type = type;
Helper.ID_TO_TYPE.put(type, this);
}
public static ServuxLitematicaPayloadType fromId(int id) {
return Helper.ID_TO_TYPE.get(id);
}
private static final class Helper {
static Map<Integer, ServuxLitematicaPayloadType> ID_TO_TYPE = new HashMap<>();
}
}
public static class ServuxLitematicaPayload implements LeavesCustomPayload {
@ID
public static final ResourceLocation CHANNEL = ServuxProtocol.id("litematics");
@Codec
public static final StreamCodec<FriendlyByteBuf, ServuxLitematicaPayload> CODEC = StreamCodec.of(
(buf, payload) -> {
buf.writeVarInt(payload.packetType.type);
switch (payload.packetType) {
case PACKET_C2S_BLOCK_ENTITY_REQUEST -> {
buf.writeVarInt(payload.transactionId);
buf.writeBlockPos(payload.pos);
}
case PACKET_C2S_ENTITY_REQUEST -> {
buf.writeVarInt(payload.transactionId);
buf.writeVarInt(payload.entityId);
}
case PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE -> {
buf.writeBlockPos(payload.pos);
buf.writeNbt(payload.nbt);
}
case PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE -> {
buf.writeVarInt(payload.entityId);
buf.writeNbt(payload.nbt);
}
case PACKET_C2S_BULK_ENTITY_NBT_REQUEST -> {
buf.writeChunkPos(payload.chunkPos);
buf.writeNbt(payload.nbt);
}
case PACKET_S2C_NBT_RESPONSE_DATA, PACKET_C2S_NBT_RESPONSE_DATA ->
buf.writeBytes(payload.buffer.readBytes(payload.buffer.readableBytes()));
case PACKET_C2S_METADATA_REQUEST, PACKET_S2C_METADATA -> buf.writeNbt(payload.nbt);
default -> ServuxProtocol.LOGGER.error("ServuxLitematicaPacket#toPacket: Unknown packet type!");
}
},
buf -> {
ServuxLitematicaPayloadType type = ServuxLitematicaPayloadType.fromId(buf.readVarInt());
if (type == null) {
throw new IllegalStateException("invalid packet type received");
}
ServuxLitematicaPayload payload = new ServuxLitematicaPayload(type);
switch (type) {
case PACKET_C2S_BLOCK_ENTITY_REQUEST -> {
buf.readVarInt();
payload.pos = buf.readBlockPos().immutable();
}
case PACKET_C2S_ENTITY_REQUEST -> {
buf.readVarInt();
payload.entityId = buf.readVarInt();
}
case PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE -> {
payload.pos = buf.readBlockPos().immutable();
payload.nbt = buf.readNbt();
}
case PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE -> {
payload.entityId = buf.readVarInt();
payload.nbt = buf.readNbt();
}
case PACKET_C2S_BULK_ENTITY_NBT_REQUEST -> {
payload.chunkPos = buf.readChunkPos();
payload.nbt = buf.readNbt();
}
case PACKET_C2S_NBT_RESPONSE_DATA, PACKET_S2C_NBT_RESPONSE_DATA ->
payload.buffer = new FriendlyByteBuf(buf.readBytes(buf.readableBytes()));
case PACKET_C2S_METADATA_REQUEST, PACKET_S2C_METADATA -> payload.nbt = buf.readNbt();
}
return payload;
}
);
public static final int PROTOCOL_VERSION = 1;
private final ServuxLitematicaPayloadType packetType;
private final int transactionId;
private int entityId;
private BlockPos pos;
private CompoundTag nbt;
private ChunkPos chunkPos;
private FriendlyByteBuf buffer;
private ServuxLitematicaPayload(ServuxLitematicaPayloadType type) {
this.packetType = type;
this.transactionId = -1;
this.entityId = -1;
this.pos = BlockPos.ZERO;
this.chunkPos = ChunkPos.ZERO;
this.nbt = new CompoundTag();
}
public int getVersion() {
return PROTOCOL_VERSION;
}
public int getTransactionId() {
return this.transactionId;
}
public int getEntityId() {
return this.entityId;
}
public BlockPos getPos() {
return this.pos;
}
public CompoundTag getCompound() {
return this.nbt;
}
public ChunkPos getChunkPos() {
return this.chunkPos;
}
public FriendlyByteBuf getBuffer() {
return this.buffer;
}
public boolean hasBuffer() {
return this.buffer != null && this.buffer.isReadable();
}
public boolean hasNbt() {
return this.nbt != null && !this.nbt.isEmpty();
}
public boolean isEmpty() {
return !this.hasBuffer() && !this.hasNbt();
}
}
}
@@ -0,0 +1,104 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics.container;
import org.apache.commons.lang3.Validate;
import javax.annotation.Nullable;
import java.util.Objects;
public class LitematicaBitArray {
/**
* The long array that is used to store the data for this BitArray.
*/
private final long[] longArray;
/**
* Number of bits a single entry takes up
*/
private final int bitsPerEntry;
/**
* The maximum value for a single entry. This also works as a bitmask for a single entry.
* For instance, if bitsPerEntry were 5, this value would be 31 (ie, {@code 0b00011111}).
*/
private final long maxEntryValue;
/**
* Number of entries in this array (<b>not</b> the length of the long array that internally backs this array)
*/
private final long arraySize;
public LitematicaBitArray(int bitsPerEntryIn, long arraySizeIn) {
this(bitsPerEntryIn, arraySizeIn, null);
}
public LitematicaBitArray(int bitsPerEntryIn, long arraySizeIn, @Nullable long[] longArrayIn) {
Validate.inclusiveBetween(1L, 32L, bitsPerEntryIn);
this.arraySize = arraySizeIn;
this.bitsPerEntry = bitsPerEntryIn;
this.maxEntryValue = (1L << bitsPerEntryIn) - 1L;
this.longArray = Objects.requireNonNullElseGet(longArrayIn, () -> new long[(int) (roundUp(arraySizeIn * bitsPerEntryIn, 64L) / 64L)]);
}
public static long roundUp(long value, long interval) {
if (interval == 0L) {
return 0L;
} else if (value == 0L) {
return interval;
} else {
if (value < 0L) {
interval *= -1L;
}
long remainder = value % interval;
return remainder == 0L ? value : value + interval - remainder;
}
}
public void setAt(long index, int value) {
long startOffset = index * (long) this.bitsPerEntry;
int startArrIndex = (int) (startOffset >> 6); // startOffset / 64
int endArrIndex = (int) (((index + 1L) * (long) this.bitsPerEntry - 1L) >> 6);
int startBitOffset = (int) (startOffset & 0x3F); // startOffset % 64
this.longArray[startArrIndex] = this.longArray[startArrIndex] & ~(this.maxEntryValue << startBitOffset) | ((long) value & this.maxEntryValue) << startBitOffset;
if (startArrIndex != endArrIndex) {
int endOffset = 64 - startBitOffset;
int j1 = this.bitsPerEntry - endOffset;
this.longArray[endArrIndex] = this.longArray[endArrIndex] >>> j1 << j1 | ((long) value & this.maxEntryValue) >> endOffset;
}
}
public int getAt(long index) {
long startOffset = index * (long) this.bitsPerEntry;
int startArrIndex = (int) (startOffset >> 6); // startOffset / 64
int endArrIndex = (int) (((index + 1L) * (long) this.bitsPerEntry - 1L) >> 6);
int startBitOffset = (int) (startOffset & 0x3F); // startOffset % 64
if (startArrIndex == endArrIndex) {
return (int) (this.longArray[startArrIndex] >>> startBitOffset & this.maxEntryValue);
} else {
int endOffset = 64 - startBitOffset;
return (int) ((this.longArray[startArrIndex] >>> startBitOffset | this.longArray[endArrIndex] << endOffset) & this.maxEntryValue);
}
}
public long size() {
return this.arraySize;
}
}
@@ -0,0 +1,118 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics.container;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.nbt.ListTag;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nullable;
public class LitematicaBlockStateContainer {
public static final BlockState AIR_BLOCK_STATE = Blocks.AIR.defaultBlockState();
protected final Vec3i size;
protected final int sizeX;
protected final int sizeY;
protected final int sizeZ;
protected final int sizeLayer;
protected final long totalVolume;
protected LitematicaBitArray storage;
protected LitematicaBlockStatePalette palette;
protected int bits;
public LitematicaBlockStateContainer(int sizeX, int sizeY, int sizeZ, int bits, @Nullable long[] backingLongArray) {
this.sizeX = sizeX;
this.sizeY = sizeY;
this.sizeZ = sizeZ;
this.sizeLayer = sizeX * sizeZ;
this.totalVolume = (long) this.sizeX * (long) this.sizeY * (long) this.sizeZ;
this.size = new Vec3i(this.sizeX, this.sizeY, this.sizeZ);
this.setBits(bits, backingLongArray);
}
public static LitematicaBlockStateContainer createFrom(ListTag palette, long[] blockStates, BlockPos size) {
int bits = Math.max(2, Integer.SIZE - Integer.numberOfLeadingZeros(palette.size() - 1));
LitematicaBlockStateContainer container = new LitematicaBlockStateContainer(size.getX(), size.getY(), size.getZ(), bits, blockStates);
container.palette.readFromNBT(palette);
return container;
}
public Vec3i getSize() {
return this.size;
}
public LitematicaBitArray getArray() {
return this.storage;
}
public BlockState get(int x, int y, int z) {
BlockState state = this.palette.getBlockState(this.storage.getAt(this.getIndex(x, y, z)));
return state == null ? AIR_BLOCK_STATE : state;
}
protected int getIndex(int x, int y, int z) {
return (y * this.sizeLayer) + z * this.sizeX + x;
}
protected void setBits(int bitsIn, @Nullable long[] backingLongArray) {
if (bitsIn != this.bits) {
this.bits = bitsIn;
if (this.bits <= 4) {
this.bits = Math.max(2, this.bits);
this.palette = new LitematicaBlockStatePaletteLinear(this.bits, this);
} else {
this.palette = new LitematicaBlockStatePaletteHashMap(this.bits, this);
}
this.palette.idFor(AIR_BLOCK_STATE);
if (backingLongArray != null) {
this.storage = new LitematicaBitArray(this.bits, this.totalVolume, backingLongArray);
} else {
this.storage = new LitematicaBitArray(this.bits, this.totalVolume);
}
}
}
public int onResize(int bits, BlockState state) {
LitematicaBitArray oldStorage = this.storage;
LitematicaBlockStatePalette oldPalette = this.palette;
final long storageLength = oldStorage.size();
this.setBits(bits, null);
LitematicaBitArray newStorage = this.storage;
for (long index = 0; index < storageLength; ++index) {
newStorage.setAt(index, oldStorage.getAt(index));
}
this.palette.readFromNBT(oldPalette.writeToNBT());
return this.palette.idFor(state);
}
public LitematicaBlockStatePalette getPalette() {
return this.palette;
}
}
@@ -0,0 +1,62 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics.container;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nullable;
public interface LitematicaBlockStatePalette {
/**
* Gets the palette id for the given block state and adds
* the state to the palette if it doesn't exist there yet.
*/
int idFor(BlockState state);
/**
* Gets the block state by the palette id.
*/
@Nullable
BlockState getBlockState(int indexKey);
void requestNewId(BlockState state);
default void readFromNBT(ListTag tagList) {
Registry<Block> lookup = MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.BLOCK);
final int size = tagList.size();
for (int i = 0; i < size; ++i) {
CompoundTag tag = tagList.getCompound(i).orElseThrow();
BlockState state = NbtUtils.readBlockState(lookup, tag);
if (i > 0 || state != LitematicaBlockStateContainer.AIR_BLOCK_STATE) {
this.requestNewId(state);
}
}
}
ListTag writeToNBT();
}
@@ -0,0 +1,90 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics.container;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.world.level.block.state.BlockState;
import org.leavesmc.leaves.protocol.servux.litematics.utils.Int2ObjectBiMap;
import javax.annotation.Nullable;
public class LitematicaBlockStatePaletteHashMap implements LitematicaBlockStatePalette {
private final Int2ObjectBiMap<BlockState> statePaletteMap;
private final LitematicaBlockStateContainer blockStateContainer;
private final int bits;
public LitematicaBlockStatePaletteHashMap(int bitsIn, LitematicaBlockStateContainer blockStateContainer) {
this.bits = bitsIn;
this.blockStateContainer = blockStateContainer;
this.statePaletteMap = Int2ObjectBiMap.create(1 << bitsIn);
}
@Override
public int idFor(BlockState state) {
int i = this.statePaletteMap.getRawId(state);
if (i == -1) {
i = this.statePaletteMap.add(state);
if (i >= (1 << this.bits)) {
i = this.blockStateContainer.onResize(this.bits + 1, state);
}
}
return i;
}
@Override
@Nullable
public BlockState getBlockState(int indexKey) {
return this.statePaletteMap.get(indexKey);
}
public void requestNewId(BlockState state) {
final int origId = this.statePaletteMap.add(state);
if (origId >= (1 << this.bits)) {
int newId = this.blockStateContainer.onResize(this.bits + 1, LitematicaBlockStateContainer.AIR_BLOCK_STATE);
if (newId <= origId) {
this.statePaletteMap.add(state);
}
}
}
@Override
public ListTag writeToNBT() {
ListTag tagList = new ListTag();
for (int id = 0; id < this.statePaletteMap.size(); ++id) {
BlockState state = this.statePaletteMap.get(id);
if (state == null) {
state = LitematicaBlockStateContainer.AIR_BLOCK_STATE;
}
CompoundTag tag = NbtUtils.writeBlockState(state);
tagList.add(tag);
}
return tagList;
}
}
@@ -0,0 +1,91 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics.container;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nullable;
public class LitematicaBlockStatePaletteLinear implements LitematicaBlockStatePalette {
private final BlockState[] states;
private final LitematicaBlockStateContainer blockStateContainer;
private final int bits;
private int currentSize;
public LitematicaBlockStatePaletteLinear(int bitsIn, LitematicaBlockStateContainer blockStateContainer) {
this.states = new BlockState[1 << bitsIn];
this.bits = bitsIn;
this.blockStateContainer = blockStateContainer;
}
@Override
public int idFor(BlockState state) {
for (int i = 0; i < this.currentSize; ++i) {
if (this.states[i] == state) {
return i;
}
}
final int size = this.currentSize;
if (size < this.states.length) {
this.states[size] = state;
++this.currentSize;
return size;
} else {
return this.blockStateContainer.onResize(this.bits + 1, state);
}
}
@Override
@Nullable
public BlockState getBlockState(int indexKey) {
return indexKey >= 0 && indexKey < this.currentSize ? this.states[indexKey] : null;
}
public void requestNewId(BlockState state) {
final int size = this.currentSize;
if (size < this.states.length) {
this.states[size] = state;
++this.currentSize;
}
}
@Override
public ListTag writeToNBT() {
ListTag tagList = new ListTag();
for (int id = 0; id < this.currentSize; ++id) {
BlockState state = this.states[id];
if (state == null) {
state = LitematicaBlockStateContainer.AIR_BLOCK_STATE;
}
CompoundTag tag = NbtUtils.writeBlockState(state);
tagList.add(tag);
}
return tagList;
}
}
@@ -0,0 +1,296 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics.placement;
import com.google.common.collect.ImmutableMap;
import net.minecraft.core.BlockBox;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.phys.AABB;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.servux.ServuxProtocol;
import org.leavesmc.leaves.protocol.servux.litematics.LitematicaSchematic;
import org.leavesmc.leaves.protocol.servux.litematics.selection.Box;
import org.leavesmc.leaves.protocol.servux.litematics.utils.*;
import javax.annotation.Nullable;
import java.util.*;
import java.util.stream.Stream;
public class SchematicPlacement {
private final Map<String, SubRegionPlacement> relativeSubRegionPlacements = new HashMap<>();
private final LitematicaSchematic schematic;
private final BlockPos origin;
private final String name;
private Rotation rotation = Rotation.NONE;
private Mirror mirror = Mirror.NONE;
private SchematicPlacement(LitematicaSchematic schematic, BlockPos origin, String name) {
this.schematic = schematic;
this.origin = origin;
this.name = name;
}
public static SchematicPlacement createFromNbt(CompoundTag tags) {
SchematicPlacement placement = new SchematicPlacement(
LitematicaSchematic.readFromNBT(tags.getCompoundOrEmpty("Schematics")),
NbtUtils.readBlockPosFromArrayTag(tags, "Origin"),
tags.getStringOr("Name", "")
);
placement.mirror = Mirror.values()[tags.getIntOr("Mirror", 0)];
placement.rotation = Rotation.values()[tags.getIntOr("Rotation", 0)];
for (String name : tags.getCompoundOrEmpty("SubRegions").keySet()) {
CompoundTag compound = tags.getCompoundOrEmpty("SubRegions").getCompoundOrEmpty(name);
var sub = new SubRegionPlacement(
compound.getStringOr("Name", "?"),
NbtUtils.readBlockPosFromArrayTag(compound, "Pos"),
Rotation.values()[compound.getIntOr("Rotation", 0)],
Mirror.values()[compound.getIntOr("Mirror", 0)],
compound.getBooleanOr("Enabled", true),
compound.getBooleanOr("IgnoreEntities", false)
);
placement.relativeSubRegionPlacements.put(name, sub);
}
return placement;
}
public static IntBoundingBox getBoundsWithinChunkForBox(Box box, int chunkX, int chunkZ) {
final int chunkXMin = chunkX << 4;
final int chunkZMin = chunkZ << 4;
final int chunkXMax = chunkXMin + 15;
final int chunkZMax = chunkZMin + 15;
BlockPos boxPos1 = box.pos1();
BlockPos boxPos2 = box.pos2();
if (boxPos1 == null || boxPos2 == null) {
return null;
}
int x1 = boxPos1.getX();
int x2 = boxPos2.getX();
int y1 = boxPos1.getY();
int y2 = boxPos2.getY();
int z1 = boxPos1.getZ();
int z2 = boxPos2.getZ();
final int boxXMin = Math.min(x1, x2);
final int boxZMin = Math.min(z1, z2);
final int boxXMax = Math.max(x1, x2);
final int boxZMax = Math.max(z1, z2);
boolean notOverlapping = boxXMin > chunkXMax || boxZMin > chunkZMax || boxXMax < chunkXMin || boxZMax < chunkZMin;
if (!notOverlapping) {
final int xMin = Math.max(chunkXMin, boxXMin);
final int yMin = Math.min(y1, y2);
final int zMin = Math.max(chunkZMin, boxZMin);
final int xMax = Math.min(chunkXMax, boxXMax);
final int yMax = Math.max(y1, y2);
final int zMax = Math.min(chunkZMax, boxZMax);
return new IntBoundingBox(xMin, yMin, zMin, xMax, yMax, zMax);
}
return null;
}
public boolean ignoreEntities() {
return false;
}
public String getName() {
return this.name;
}
public LitematicaSchematic getSchematic() {
return schematic;
}
public BlockPos getOrigin() {
return origin;
}
public Rotation getRotation() {
return rotation;
}
public Mirror getMirror() {
return mirror;
}
@Nullable
public SubRegionPlacement getRelativeSubRegionPlacement(String areaName) {
return this.relativeSubRegionPlacements.get(areaName);
}
public ImmutableMap<String, Box> getSubRegionBoxes(SubRegionPlacement.RequiredEnabled required) {
ImmutableMap.Builder<String, Box> builder = ImmutableMap.builder();
Map<String, BlockPos> areaSizes = this.schematic.getAreaSizes();
for (Map.Entry<String, SubRegionPlacement> entry : this.relativeSubRegionPlacements.entrySet()) {
String name = entry.getKey();
BlockPos areaSize = areaSizes.get(name);
if (areaSize == null) {
ServuxProtocol.LOGGER.warn("SchematicPlacement.getSubRegionBoxes(): Size for sub-region '{}' not found in the schematic '{}'", name, this.schematic.metadata().name());
continue;
}
SubRegionPlacement placement = entry.getValue();
if (placement.matchesRequirement(required)) {
putBoxPosIntoBuilder(builder, name, areaSize, placement);
}
}
return builder.build();
}
private void putBoxPosIntoBuilder(ImmutableMap.Builder<String, Box> builder, String name, BlockPos areaSize, SubRegionPlacement placement) {
BlockPos boxOriginRelative = placement.pos();
BlockPos boxOriginAbsolute = PositionUtils.getTransformedBlockPos(boxOriginRelative, this.mirror, this.rotation).offset(this.origin);
BlockPos pos2 = PositionUtils.getRelativeEndPositionFromAreaSize(areaSize);
pos2 = PositionUtils.getTransformedBlockPos(pos2, this.mirror, this.rotation);
pos2 = PositionUtils.getTransformedBlockPos(pos2, placement.mirror(), placement.rotation()).offset(boxOriginAbsolute);
builder.put(name, new Box(boxOriginAbsolute, pos2, name));
}
public ImmutableMap<String, Box> getSubRegionBoxFor(String regionName, SubRegionPlacement.RequiredEnabled required) {
SubRegionPlacement placement = this.relativeSubRegionPlacements.get(regionName);
if (placement == null) {
return ImmutableMap.of();
}
ImmutableMap.Builder<String, Box> builder = ImmutableMap.builder();
Map<String, BlockPos> areaSizes = this.schematic.getAreaSizes();
if (placement.matchesRequirement(required)) {
BlockPos areaSize = areaSizes.get(regionName);
if (areaSize != null) {
putBoxPosIntoBuilder(builder, regionName, areaSize, placement);
} else {
ServuxProtocol.LOGGER.warn("SchematicPlacement.getSubRegionBoxFor(): Size for sub-region '{}' not found in the schematic '{}'", regionName, this.schematic.metadata().name());
}
}
return builder.build();
}
public Set<String> getRegionsTouchingChunk(int chunkX, int chunkZ) {
ImmutableMap<String, Box> map = this.getSubRegionBoxes(SubRegionPlacement.RequiredEnabled.PLACEMENT_ENABLED);
final int chunkXMin = chunkX << 4;
final int chunkZMin = chunkZ << 4;
final int chunkXMax = chunkXMin + 15;
final int chunkZMax = chunkZMin + 15;
Set<String> set = new HashSet<>();
for (Map.Entry<String, Box> entry : map.entrySet()) {
Box box = entry.getValue();
BlockPos boxPos1 = box.pos1();
BlockPos boxPos2 = box.pos2();
if (boxPos1 == null || boxPos2 == null) {
continue;
}
int x1 = boxPos1.getX();
int x2 = boxPos2.getX();
int z1 = boxPos1.getZ();
int z2 = boxPos2.getZ();
final int boxXMin = Math.min(x1, x2);
final int boxZMin = Math.min(z1, z2);
final int boxXMax = Math.max(x1, x2);
final int boxZMax = Math.max(z1, z2);
boolean notOverlapping = boxXMin > chunkXMax || boxZMin > chunkZMax || boxXMax < chunkXMin || boxZMax < chunkZMin;
if (!notOverlapping) {
set.add(entry.getKey());
}
}
return set;
}
@Nullable
public IntBoundingBox getBoxWithinChunkForRegion(String regionName, int chunkX, int chunkZ) {
Box box = this.getSubRegionBoxFor(regionName, SubRegionPlacement.RequiredEnabled.PLACEMENT_ENABLED).get(regionName);
return box != null ? getBoundsWithinChunkForBox(box, chunkX, chunkZ) : null;
}
private Box getEnclosingBox() {
ImmutableMap<String, Box> boxes = this.getSubRegionBoxes(SubRegionPlacement.RequiredEnabled.ANY);
BlockPos pos1 = null;
BlockPos pos2 = null;
for (Box box : boxes.values()) {
BlockPos tmp;
BlockPos boxPos1 = box.pos1();
BlockPos boxPos2 = box.pos2();
if (boxPos1 == null || boxPos2 == null) {
continue;
}
tmp = PositionUtils.getMinCorner(boxPos1, boxPos2);
if (pos1 == null) {
pos1 = tmp;
} else if (tmp.getX() < pos1.getX() || tmp.getY() < pos1.getY() || tmp.getZ() < pos1.getZ()) {
pos1 = PositionUtils.getMinCorner(tmp, pos1);
}
tmp = PositionUtils.getMaxCorner(boxPos1, boxPos2);
if (pos2 == null) {
pos2 = tmp;
} else if (tmp.getX() > pos2.getX() || tmp.getY() > pos2.getY() || tmp.getZ() > pos2.getZ()) {
pos2 = PositionUtils.getMaxCorner(tmp, pos2);
}
}
if (pos1 != null) {
return new Box(pos1, pos2, "Enclosing Box (Servux)");
}
return null;
}
public Stream<ChunkPos> streamChunkPos(@NotNull BlockBox box) {
AABB aabb = box.aabb();
int i = SectionPos.blockToSectionCoord(aabb.minX);
int j = SectionPos.blockToSectionCoord(aabb.minZ);
int k = SectionPos.blockToSectionCoord(aabb.maxX);
int l = SectionPos.blockToSectionCoord(aabb.maxZ);
return ChunkPos.rangeClosed(new ChunkPos(i, j), new ChunkPos(k, l));
}
public void pasteTo(ServerLevel serverWorld, ReplaceBehavior replaceBehavior) {
Box enclosingBox = this.getEnclosingBox();
if (enclosingBox == null || enclosingBox.pos1() == null || enclosingBox.pos2() == null) {
ServuxProtocol.LOGGER.error("receiver a null enclosing box");
return;
}
streamChunkPos(Objects.requireNonNull(enclosingBox.toVanilla())).forEach(chunkPos ->
SchematicPlacingUtils.placeToWorldWithinChunk(serverWorld, chunkPos, this, replaceBehavior, false)
);
}
}
@@ -0,0 +1,55 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics.placement;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import org.leavesmc.leaves.protocol.servux.ServuxProtocol;
public record SubRegionPlacement(
String name,
BlockPos pos,
Rotation rotation,
Mirror mirror,
boolean enabled,
boolean ignoreEntities
) {
public SubRegionPlacement(String name, BlockPos pos) {
this(name, pos, Rotation.NONE, Mirror.NONE, true, false);
}
public boolean matchesRequirement(RequiredEnabled required) {
if (required == RequiredEnabled.ANY) {
return true;
}
if (required == RequiredEnabled.PLACEMENT_ENABLED) {
return this.enabled();
}
ServuxProtocol.LOGGER.warn("RequiredEnabled.RENDERING_ENABLED is not supported on server side!");
return false;
}
public enum RequiredEnabled {
ANY,
PLACEMENT_ENABLED,
RENDERING_ENABLED
}
}
@@ -0,0 +1,36 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics.selection;
import net.minecraft.core.BlockBox;
import net.minecraft.core.BlockPos;
import org.jetbrains.annotations.Nullable;
public record Box(@Nullable BlockPos pos1, @Nullable BlockPos pos2, String name) {
public Box() {
this(BlockPos.ZERO, BlockPos.ZERO, "Unnamed");
}
@Nullable
public BlockBox toVanilla() {
if (pos1 != null && pos2 != null) {
return new BlockBox(pos1, pos2);
}
return null;
}
}
@@ -0,0 +1,111 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics.utils;
import com.google.common.collect.ImmutableList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntitySpawnReason;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level;
import org.leavesmc.leaves.util.TagFactory;
import javax.annotation.Nullable;
import java.util.Optional;
import java.util.UUID;
public class EntityUtils {
@Nullable
private static Entity createEntityFromNBTSingle(CompoundTag nbt, Level world) {
try {
Optional<Entity> optional = EntityType.create(TagFactory.input(nbt), world, EntitySpawnReason.LOAD);
if (optional.isPresent()) {
Entity entity = optional.get();
entity.setUUID(UUID.randomUUID());
return entity;
}
} catch (Exception ignore) {
}
return null;
}
/**
* Note: This does NOT spawn any of the entities in the world!
*
* @param nbt ()
* @param world ()
* @return ()
*/
@Nullable
public static Entity createEntityAndPassengersFromNBT(CompoundTag nbt, Level world) {
Entity entity = createEntityFromNBTSingle(nbt, world);
if (entity == null) {
return null;
}
if (nbt.contains("Passengers")) {
ListTag tagList = nbt.getListOrEmpty("Passengers");
for (int i = 0; i < tagList.size(); ++i) {
Entity passenger = createEntityAndPassengersFromNBT(tagList.getCompoundOrEmpty(i), world);
if (passenger != null) {
passenger.startRiding(entity, true);
}
}
}
return entity;
}
public static void spawnEntityAndPassengersInWorld(Entity entity, Level world) {
ImmutableList<Entity> passengers = entity.passengers;
if (world.addFreshEntity(entity) && !passengers.isEmpty()) {
for (Entity passenger : passengers) {
passenger.snapTo(
entity.getX(),
entity.getY() + entity.getPassengerRidingPosition(passenger).y(),
entity.getZ(),
passenger.getYRot(), passenger.getXRot()
);
setEntityRotations(passenger, passenger.getYRot(), passenger.getXRot());
spawnEntityAndPassengersInWorld(passenger, world);
}
}
}
public static void setEntityRotations(Entity entity, float yaw, float pitch) {
entity.setYRot(yaw);
entity.yRotO = yaw;
entity.setXRot(pitch);
entity.xRotO = pitch;
if (entity instanceof LivingEntity livingBase) {
livingBase.yHeadRot = yaw;
livingBase.yBodyRot = yaw;
livingBase.yHeadRotO = yaw;
livingBase.yBodyRotO = yaw;
}
}
}
@@ -0,0 +1,28 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics.utils;
public enum FileType {
INVALID,
UNKNOWN,
JSON,
LITEMATICA_SCHEMATIC,
SCHEMATICA_SCHEMATIC,
SPONGE_SCHEMATIC,
VANILLA_STRUCTURE
}
@@ -0,0 +1,211 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics.utils;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterators;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Iterator;
public class Int2ObjectBiMap<K> implements Iterable<K> {
private static final Object EMPTY = null;
private K[] values;
private int[] ids;
private K[] idToValues;
private int nextId;
private int size;
@SuppressWarnings("unchecked")
private Int2ObjectBiMap(int size) {
this.values = (K[]) (new Object[size]);
this.ids = new int[size];
this.idToValues = (K[]) (new Object[size]);
}
private Int2ObjectBiMap(K[] values, int[] ids, K[] idToValues, int nextId, int size) {
this.values = values;
this.ids = ids;
this.idToValues = idToValues;
this.nextId = nextId;
this.size = size;
}
public static <A> Int2ObjectBiMap<A> create(int expectedSize) {
return new Int2ObjectBiMap<>((int) ((float) expectedSize / 0.8F));
}
private static int idealHash(int value) {
value ^= value >>> 16;
value *= -2048144789;
value ^= value >>> 13;
value *= -1028477387;
return value ^ value >>> 16;
}
public int getRawId(@Nullable K value) {
return this.getIdFromIndex(this.findIndex(value, this.getIdealIndex(value)));
}
@Nullable
public K get(int index) {
return index >= 0 && index < this.idToValues.length ? this.idToValues[index] : null;
}
private int getIdFromIndex(int index) {
return index == -1 ? -1 : this.ids[index];
}
public boolean contains(K value) {
return this.getRawId(value) != -1;
}
public boolean containsKey(int index) {
return this.get(index) != null;
}
public int add(K value) {
int i = this.nextId();
this.put(value, i);
return i;
}
private int nextId() {
while (this.nextId < this.idToValues.length && this.idToValues[this.nextId] != null) {
this.nextId++;
}
return this.nextId;
}
private void resize(int newSize) {
K[] objects = this.values;
int[] is = this.ids;
Int2ObjectBiMap<K> int2ObjectBiMap = new Int2ObjectBiMap<>(newSize);
for (int i = 0; i < objects.length; i++) {
if (objects[i] != null) {
int2ObjectBiMap.put(objects[i], is[i]);
}
}
this.values = int2ObjectBiMap.values;
this.ids = int2ObjectBiMap.ids;
this.idToValues = int2ObjectBiMap.idToValues;
this.nextId = int2ObjectBiMap.nextId;
this.size = int2ObjectBiMap.size;
}
public void put(K value, int id) {
int i = Math.max(id, this.size + 1);
if ((float) i >= (float) this.values.length * 0.8F) {
int j = this.values.length << 1;
while (j < id) {
j <<= 1;
}
this.resize(j);
}
int j = this.findFree(this.getIdealIndex(value));
this.values[j] = value;
this.ids[j] = id;
this.idToValues[id] = value;
this.size++;
if (id == this.nextId) {
this.nextId++;
}
}
private int getIdealIndex(@Nullable K value) {
return (idealHash(System.identityHashCode(value)) & 2147483647) % this.values.length;
}
private int findIndex(@Nullable K value, int id) {
for (int i = id; i < this.values.length; i++) {
if (this.values[i] == value) {
return i;
}
if (this.values[i] == EMPTY) {
return -1;
}
}
for (int i = 0; i < id; i++) {
if (this.values[i] == value) {
return i;
}
if (this.values[i] == EMPTY) {
return -1;
}
}
return -1;
}
private int findFree(int size) {
for (int i = size; i < this.values.length; i++) {
if (this.values[i] == EMPTY) {
return i;
}
}
for (int ix = 0; ix < size; ix++) {
if (this.values[ix] == EMPTY) {
return ix;
}
}
throw new RuntimeException("Overflowed :(");
}
public @NotNull Iterator<K> iterator() {
return Iterators.filter(Iterators.forArray(this.idToValues), Predicates.notNull());
}
public void clear() {
Arrays.fill(this.values, null);
Arrays.fill(this.idToValues, null);
this.nextId = 0;
this.size = 0;
}
public int size() {
return this.size;
}
public Int2ObjectBiMap<K> copy() {
return new Int2ObjectBiMap<>(this.values.clone(), this.ids.clone(), this.idToValues.clone(), this.nextId, this.size);
}
public K getOrThrow(int index) {
K object = this.get(index);
if (object == null) {
throw new IllegalArgumentException("No value with id " + index);
} else {
return object;
}
}
}
@@ -0,0 +1,43 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics.utils;
import net.minecraft.core.Vec3i;
public record IntBoundingBox(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
public boolean containsPos(Vec3i pos) {
return pos.getX() >= this.minX && pos.getX() <= this.maxX && pos.getZ() >= this.minZ && pos.getZ() <= this.maxZ && pos.getY() >= this.minY && pos.getY() <= this.maxY;
}
public boolean intersects(IntBoundingBox box) {
return this.maxX >= box.minX && this.minX <= box.maxX && this.maxZ >= box.minZ && this.minZ <= box.maxZ && this.maxY >= box.minY && this.minY <= box.maxY;
}
public IntBoundingBox expand(int amount) {
return this.expand(amount, amount, amount);
}
public IntBoundingBox expand(int x, int y, int z) {
return new IntBoundingBox(this.minX - x, this.minY - y, this.minZ - z, this.maxX + x, this.maxY + y, this.maxZ + z);
}
public IntBoundingBox shrink(int x, int y, int z) {
return this.expand(-x, -y, -z);
}
}
@@ -0,0 +1,92 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics.utils;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.DoubleTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class NbtUtils {
public static void writeBlockPosToTag(Vec3i pos, CompoundTag tag) {
tag.putInt("x", pos.getX());
tag.putInt("y", pos.getY());
tag.putInt("z", pos.getZ());
}
@Nullable
public static BlockPos readBlockPos(@Nullable CompoundTag tag) {
if (tag != null && tag.contains("x") && tag.contains("y") && tag.contains("z")) {
return new BlockPos(tag.getIntOr("x", 0), tag.getIntOr("y", 0), tag.getIntOr("z", 0));
}
return null;
}
public static void writeEntityPositionToTag(Vec3 pos, CompoundTag tag) {
ListTag posList = new ListTag();
posList.add(DoubleTag.valueOf(pos.x));
posList.add(DoubleTag.valueOf(pos.y));
posList.add(DoubleTag.valueOf(pos.z));
tag.put("Pos", posList);
}
@Nullable
public static Vec3 readVec3(@Nullable CompoundTag tag) {
if (tag != null && tag.contains("dx") && tag.contains("dy") && tag.contains("dz")) {
return new Vec3(tag.getDoubleOr("dx", 0.0), tag.getDoubleOr("dy", 0.0), tag.getDoubleOr("dz", 0.0));
}
return null;
}
@Nullable
public static Vec3 readEntityPositionFromTag(@Nullable CompoundTag tag) {
if (tag == null || !tag.contains("Pos")) {
return null;
}
ListTag tagList = tag.getListOrEmpty("Pos");
if (tagList.size() != 3) {
return null;
}
return new Vec3(tagList.getDoubleOr(0, 0.0), tagList.getDoubleOr(1, 0.0), tagList.getDoubleOr(2, 0.0));
}
@Nullable
public static Vec3i readVec3iFromTag(@Nullable CompoundTag tag) {
if (tag != null && tag.contains("x") && tag.contains("y") && tag.contains("z")) {
return new Vec3i(tag.getIntOr("x", 0), tag.getIntOr("y", 0), tag.getIntOr("z", 0));
}
return null;
}
public static BlockPos readBlockPosFromArrayTag(@NotNull CompoundTag tag, String tagName) {
if (tag.contains(tagName)) {
int[] pos = tag.getIntArray(tagName).orElse(new int[0]);
if (pos.length == 3) {
return new BlockPos(pos[0], pos[1], pos[2]);
}
}
return null;
}
}
@@ -0,0 +1,149 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics.utils;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Vec3i;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.phys.Vec3;
import org.leavesmc.leaves.protocol.servux.litematics.placement.SchematicPlacement;
import org.leavesmc.leaves.protocol.servux.litematics.placement.SubRegionPlacement;
public class PositionUtils {
public static Direction rotateYCounterclockwise(Direction direction) {
Direction var10000;
switch (direction.ordinal()) {
case 2 -> var10000 = Direction.WEST;
case 3 -> var10000 = Direction.EAST;
case 4 -> var10000 = Direction.SOUTH;
case 5 -> var10000 = Direction.NORTH;
default -> throw new IllegalStateException("Unable to get CCW facing of " + direction);
}
return var10000;
}
public static BlockPos getMinCorner(BlockPos pos1, BlockPos pos2) {
return new BlockPos(Math.min(pos1.getX(), pos2.getX()), Math.min(pos1.getY(), pos2.getY()), Math.min(pos1.getZ(), pos2.getZ()));
}
public static BlockPos getMaxCorner(BlockPos pos1, BlockPos pos2) {
return new BlockPos(Math.max(pos1.getX(), pos2.getX()), Math.max(pos1.getY(), pos2.getY()), Math.max(pos1.getZ(), pos2.getZ()));
}
public static BlockPos getTransformedPlacementPosition(BlockPos posWithinSub, SchematicPlacement schematicPlacement, SubRegionPlacement placement) {
BlockPos pos = posWithinSub;
pos = getTransformedBlockPos(pos, schematicPlacement.getMirror(), schematicPlacement.getRotation());
pos = getTransformedBlockPos(pos, placement.mirror(), placement.rotation());
return pos;
}
public static BlockPos getRelativeEndPositionFromAreaSize(Vec3i size) {
int x = size.getX();
int y = size.getY();
int z = size.getZ();
x = x >= 0 ? x - 1 : x + 1;
y = y >= 0 ? y - 1 : y + 1;
z = z >= 0 ? z - 1 : z + 1;
return new BlockPos(x, y, z);
}
/**
* Mirrors and then rotates the given position around the origin
*/
public static BlockPos getTransformedBlockPos(BlockPos pos, Mirror mirror, Rotation rotation) {
int x = pos.getX();
int y = pos.getY();
int z = pos.getZ();
boolean isMirrored = true;
switch (mirror) {
case LEFT_RIGHT -> z = -z; // LEFT_RIGHT is essentially NORTH_SOUTH
case FRONT_BACK -> x = -x; // FRONT_BACK is essentially EAST_WEST
default -> isMirrored = false;
}
return switch (rotation) {
case CLOCKWISE_90 -> new BlockPos(-z, y, x);
case COUNTERCLOCKWISE_90 -> new BlockPos(z, y, -x);
case CLOCKWISE_180 -> new BlockPos(-x, y, -z);
default -> isMirrored ? new BlockPos(x, y, z) : pos;
};
}
public static BlockPos getReverseTransformedBlockPos(BlockPos pos, Mirror mirror, Rotation rotation) {
int x = pos.getX();
int y = pos.getY();
int z = pos.getZ();
boolean isRotated = true;
int tmp = x;
switch (rotation) {
case CLOCKWISE_90 -> {
x = z;
z = -tmp;
}
case COUNTERCLOCKWISE_90 -> {
x = -z;
z = tmp;
}
case CLOCKWISE_180 -> {
x = -x;
z = -z;
}
default -> isRotated = false;
}
switch (mirror) {
case LEFT_RIGHT -> z = -z; // LEFT_RIGHT is essentially NORTH_SOUTH
case FRONT_BACK -> x = -x; // FRONT_BACK is essentially EAST_WEST
default -> {
if (!isRotated) {
return pos;
}
}
}
return new BlockPos(x, y, z);
}
public static Vec3 getTransformedPosition(Vec3 originalPos, Mirror mirror, Rotation rotation) {
double x = originalPos.x;
double y = originalPos.y;
double z = originalPos.z;
boolean transformed = true;
switch (mirror) {
case LEFT_RIGHT -> z = 1.0D - z;
case FRONT_BACK -> x = 1.0D - x;
default -> transformed = false;
}
return switch (rotation) {
case COUNTERCLOCKWISE_90 -> new Vec3(z, y, 1.0D - x);
case CLOCKWISE_90 -> new Vec3(1.0D - z, y, x);
case CLOCKWISE_180 -> new Vec3(1.0D - x, y, 1.0D - z);
default -> transformed ? new Vec3(x, y, z) : originalPos;
};
}
}
@@ -0,0 +1,40 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics.utils;
public enum ReplaceBehavior {
NONE("none"),
ALL("all"),
WITH_NON_AIR("with_non_air");
private final String configString;
ReplaceBehavior(String configString) {
this.configString = configString;
}
public static ReplaceBehavior fromStringStatic(String name) {
for (ReplaceBehavior val : ReplaceBehavior.values()) {
if (val.configString.equalsIgnoreCase(name)) {
return val;
}
}
return ReplaceBehavior.NONE;
}
}
@@ -0,0 +1,168 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics.utils;
import javax.annotation.Nullable;
public enum Schema {
// TODO --> Add Schema Versions to this as versions get released
// Minecraft Data Versions
SCHEMA_FUTURE(9999, "FUTURE"),
SCHEMA_1_21_05(4325, "1.21.5"),
SCHEMA_25W10A(4319, "25w10a"),
SCHEMA_25W03A(4304, "25w03a"), // Entity Data Components ( https://www.minecraft.net/en-us/article/minecraft-snapshot-25w03a )
SCHEMA_25W02A(4298, "25w02a"),
SCHEMA_1_21_04(4189, "1.21.4"),
SCHEMA_24W46A(4178, "24w46a"),
SCHEMA_24W44A(4174, "24w44a"),
SCHEMA_1_21_03(4082, "1.21.3"),
SCHEMA_1_21_02(4080, "1.21.2"),
SCHEMA_24W40A(4072, "24w40a"),
SCHEMA_24W37A(4065, "24w37a"),
SCHEMA_24W35A(4062, "24w35a"),
SCHEMA_24W33A(4058, "24w33a"),
SCHEMA_1_21_01(3955, "1.21.1"),
SCHEMA_1_21_00(3953, "1.21"),
SCHEMA_24W21A(3946, "24w21a"),
SCHEMA_24W18A(3940, "24w18a"),
SCHEMA_1_20_05(3837, "1.20.5"),
SCHEMA_24W14A(3827, "24w14a"),
SCHEMA_24W13A(3826, "24w13a"),
SCHEMA_24W12A(3824, "24w12a"),
SCHEMA_24W10A(3821, "24w10a"),
SCHEMA_24W09A(3819, "24w09a"), // Data Components ( https://minecraft.wiki/w/Data_component_format )
SCHEMA_24W07A(3817, "24w07a"),
SCHEMA_24W03A(3804, "24w03a"),
SCHEMA_23W51A(3801, "23w51a"),
SCHEMA_1_20_04(3700, "1.20.4"),
SCHEMA_23W46A(3691, "23w46a"),
SCHEMA_23W43B(3687, "23w43b"),
SCHEMA_23W40A(3679, "23w40a"),
SCHEMA_1_20_02(3578, "1.20.2"),
SCHEMA_23W35A(3571, "23w35a"),
SCHEMA_23W31A(3567, "23w31a"),
SCHEMA_1_20_01(3465, "1.20.1"),
SCHEMA_1_20_00(3463, "1.20"),
SCHEMA_23W18A(3453, "23w18a"),
SCHEMA_23W16A(3449, "23w16a"),
SCHEMA_23W12A(3442, "23w12a"),
SCHEMA_1_19_04(3337, "1.19.4"),
SCHEMA_1_19_03(3218, "1.19.3"),
SCHEMA_1_19_02(3120, "1.19.2"),
SCHEMA_1_19_01(3117, "1.19.1"),
SCHEMA_1_19_00(3105, "1.19"),
SCHEMA_22W19A(3096, "22w19a"),
SCHEMA_22W16A(3091, "22w16a"),
SCHEMA_22W11A(3080, "22w11a"),
SCHEMA_1_18_02(2975, "1.18.2"),
SCHEMA_1_18_01(2865, "1.18.1"),
SCHEMA_1_18_00(2860, "1.18"),
SCHEMA_21W44A(2845, "21w44a"),
SCHEMA_21W41A(2839, "21w41a"),
SCHEMA_21W37A(2834, "21w37a"),
SCHEMA_1_17_01(2730, "1.17.1"),
SCHEMA_1_17_00(2724, "1.17"),
SCHEMA_21W20A(2715, "21w20a"),
SCHEMA_21W15A(2709, "21w15a"),
SCHEMA_21W10A(2699, "21w10a"),
SCHEMA_21W05A(2690, "21w05a"),
SCHEMA_20W49A(2685, "20w49a"),
SCHEMA_20W45A(2681, "20w45a"),
SCHEMA_1_16_05(2586, "1.16.5"),
SCHEMA_1_16_04(2584, "1.16.4"),
SCHEMA_1_16_03(2580, "1.16.3"),
SCHEMA_1_16_02(2578, "1.16.2"),
SCHEMA_1_16_01(2567, "1.16.1"),
SCHEMA_1_16_00(2566, "1.16"),
SCHEMA_20W22A(2555, "20w22a"),
SCHEMA_20W15A(2525, "20w15a"),
SCHEMA_20W06A(2504, "20w06a"),
SCHEMA_1_15_02(2230, "1.15.2"),
SCHEMA_1_15_01(2227, "1.15.1"),
SCHEMA_1_15_00(2225, "1.15"),
SCHEMA_19W46B(2217, "19w46b"),
SCHEMA_19W40A(2208, "19w40a"),
SCHEMA_19W34A(2200, "19w34a"),
SCHEMA_1_14_04(1976, "1.14.4"),
SCHEMA_1_14_03(1968, "1.14.3"),
SCHEMA_1_14_02(1963, "1.14.2"),
SCHEMA_1_14_01(1957, "1.14.1"),
SCHEMA_1_14_00(1952, "1.14"),
SCHEMA_19W14B(1945, "19w14b"),
SCHEMA_19W08B(1934, "19w08b"),
SCHEMA_18W50A(1919, "18w50a"),
SCHEMA_18W43A(1901, "18w43a"),
SCHEMA_1_13_02(1631, "1.13.2"),
SCHEMA_1_13_01(1628, "1.13.1"),
SCHEMA_1_13_00(1519, "1.13"),
SCHEMA_18W22C(1499, "18w22c"),
SCHEMA_18W14B(1481, "18w14b"),
SCHEMA_18W07C(1469, "18w07c"),
SCHEMA_17W50A(1457, "17w50a"),
SCHEMA_17W47A(1451, "17w47a"), // The Flattening ( https://minecraft.wiki/w/Java_Edition_1.13/Flattening )
SCHEMA_17W46A(1449, "17w46a"),
SCHEMA_17W43A(1444, "17w43a"),
SCHEMA_1_12_02(1343, "1.12.2"),
SCHEMA_1_12_01(1241, "1.12.1"),
SCHEMA_1_12_00(1139, "1.12"),
SCHEMA_1_11_02(922, "1.11.2"),
SCHEMA_1_11_00(819, "1.11"),
SCHEMA_1_10_02(512, "1.10.2"),
SCHEMA_1_10_00(510, "1.10"),
SCHEMA_1_09_04(184, "1.9.4"),
SCHEMA_1_09_00(169, "1.9"),
SCHEMA_15W32A(100, "15w32a");
private final int schemaId;
private final String str;
Schema(int id, String ver) {
this.schemaId = id;
this.str = ver;
}
/**
* Returns the Schema of the closest dataVersion, or below it.
*
* @param dataVersion (Schema ID)
* @return (Schema | null)
*/
public static @Nullable Schema getSchemaByDataVersion(int dataVersion) {
for (Schema schema : Schema.values()) {
if (schema.getDataVersion() <= dataVersion) {
return schema;
}
}
return null;
}
public int getDataVersion() {
return this.schemaId;
}
public String getString() {
return this.str;
}
@Override
public String toString() {
return "MC: " + this.getString() + " [Schema: " + this.getDataVersion() + "]";
}
}
@@ -0,0 +1,440 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves 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.
*
* Leaves 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 Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.servux.litematics.utils;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Vec3i;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.Container;
import net.minecraft.world.entity.Display;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.decoration.ItemFrame;
import net.minecraft.world.entity.decoration.Painting;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.ticks.LevelTicks;
import net.minecraft.world.ticks.ScheduledTick;
import org.leavesmc.leaves.protocol.servux.ServuxProtocol;
import org.leavesmc.leaves.protocol.servux.litematics.LitematicaSchematic;
import org.leavesmc.leaves.protocol.servux.litematics.container.LitematicaBlockStateContainer;
import org.leavesmc.leaves.protocol.servux.litematics.placement.SchematicPlacement;
import org.leavesmc.leaves.protocol.servux.litematics.placement.SubRegionPlacement;
import org.leavesmc.leaves.util.TagUtil;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class SchematicPlacingUtils {
public static void placeToWorldWithinChunk(
Level world,
ChunkPos chunkPos,
SchematicPlacement schematicPlacement,
ReplaceBehavior replace,
boolean notifyNeighbors
) {
LitematicaSchematic schematic = schematicPlacement.getSchematic();
Set<String> regionsTouchingChunk = schematicPlacement.getRegionsTouchingChunk(chunkPos.x, chunkPos.z);
BlockPos origin = schematicPlacement.getOrigin();
for (String regionName : regionsTouchingChunk) {
LitematicaSchematic.SubRegion subRegion = schematic.getSubRegion(regionName);
LitematicaBlockStateContainer container = subRegion.blockContainers();
if (container == null) {
continue;
}
SubRegionPlacement placement = schematicPlacement.getRelativeSubRegionPlacement(regionName);
if (placement == null) {
ServuxProtocol.LOGGER.error("receiver a null placement for region: {}", regionName);
continue;
}
if (!placement.enabled()) {
continue;
}
Map<BlockPos, CompoundTag> blockEntityMap = subRegion.tileEntities();
Map<BlockPos, ScheduledTick<Block>> scheduledBlockTicks = subRegion.pendingBlockTicks();
Map<BlockPos, ScheduledTick<Fluid>> scheduledFluidTicks = subRegion.pendingFluidTicks();
if (!placeBlocksWithinChunk(world, chunkPos, regionName, container, blockEntityMap,
origin, schematicPlacement, placement, scheduledBlockTicks, scheduledFluidTicks, replace, notifyNeighbors)) {
ServuxProtocol.LOGGER.warn("Invalid/missing schematic data in schematic '{}' for sub-region '{}'", schematic.metadata().name(), regionName);
}
List<LitematicaSchematic.EntityInfo> entityList = subRegion.entities();
if (!schematicPlacement.ignoreEntities() &&
!placement.ignoreEntities() && entityList != null) {
placeEntitiesToWorldWithinChunk(world, chunkPos, entityList, origin, schematicPlacement, placement);
}
}
}
public static boolean placeBlocksWithinChunk(
Level world, ChunkPos chunkPos, String regionName,
LitematicaBlockStateContainer container,
Map<BlockPos, CompoundTag> blockEntityMap,
BlockPos origin,
SchematicPlacement schematicPlacement,
SubRegionPlacement placement,
@Nullable Map<BlockPos, ScheduledTick<Block>> scheduledBlockTicks,
@Nullable Map<BlockPos, ScheduledTick<Fluid>> scheduledFluidTicks,
ReplaceBehavior replace, boolean notifyNeighbors
) {
IntBoundingBox bounds = schematicPlacement.getBoxWithinChunkForRegion(regionName, chunkPos.x, chunkPos.z);
Vec3i regionSize = schematicPlacement.getSchematic().getSubRegion(regionName).size();
if (bounds == null || container == null || blockEntityMap == null || regionSize == null) {
return false;
}
BlockPos regionPos = placement.pos();
// These are the untransformed relative positions
BlockPos posEndRel = (new BlockPos(PositionUtils.getRelativeEndPositionFromAreaSize(regionSize))).offset(regionPos);
BlockPos posMinRel = PositionUtils.getMinCorner(regionPos, posEndRel);
// The transformed sub-region origin position
BlockPos regionPosTransformed = PositionUtils.getTransformedBlockPos(regionPos, schematicPlacement.getMirror(), schematicPlacement.getRotation());
// The relative offset of the affected region's corners, to the sub-region's origin corner
BlockPos boxMinRel = new BlockPos(bounds.minX() - origin.getX() - regionPosTransformed.getX(), 0, bounds.minZ() - origin.getZ() - regionPosTransformed.getZ());
BlockPos boxMaxRel = new BlockPos(bounds.maxX() - origin.getX() - regionPosTransformed.getX(), 0, bounds.maxZ() - origin.getZ() - regionPosTransformed.getZ());
// Reverse transform that relative offset, to get the untransformed orientation's offsets
boxMinRel = PositionUtils.getReverseTransformedBlockPos(boxMinRel, placement.mirror(), placement.rotation());
boxMaxRel = PositionUtils.getReverseTransformedBlockPos(boxMaxRel, placement.mirror(), placement.rotation());
boxMinRel = PositionUtils.getReverseTransformedBlockPos(boxMinRel, schematicPlacement.getMirror(), schematicPlacement.getRotation());
boxMaxRel = PositionUtils.getReverseTransformedBlockPos(boxMaxRel, schematicPlacement.getMirror(), schematicPlacement.getRotation());
// Get the offset relative to the sub-region's minimum corner, instead of the origin corner (which can be at any corner)
boxMinRel = boxMinRel.subtract(posMinRel.subtract(regionPos));
boxMaxRel = boxMaxRel.subtract(posMinRel.subtract(regionPos));
BlockPos posMin = PositionUtils.getMinCorner(boxMinRel, boxMaxRel);
BlockPos posMax = PositionUtils.getMaxCorner(boxMinRel, boxMaxRel);
final int startX = posMin.getX();
final int startZ = posMin.getZ();
final int endX = posMax.getX();
final int endZ = posMax.getZ();
final int startY = 0;
final int endY = Math.abs(regionSize.getY()) - 1;
BlockPos.MutableBlockPos posMutable = new BlockPos.MutableBlockPos();
if (startX < 0 || startZ < 0 || endX >= container.getSize().getX() || endZ >= container.getSize().getZ()) {
return false;
}
final Rotation rotationCombined = schematicPlacement.getRotation().getRotated(placement.rotation());
final Mirror mirrorMain = schematicPlacement.getMirror();
final BlockState barrier = Blocks.BARRIER.defaultBlockState();
Mirror mirrorSub = placement.mirror();
final boolean ignoreInventories = false;
if (mirrorSub != Mirror.NONE &&
(schematicPlacement.getRotation() == Rotation.CLOCKWISE_90 ||
schematicPlacement.getRotation() == Rotation.COUNTERCLOCKWISE_90)) {
mirrorSub = mirrorSub == Mirror.FRONT_BACK ? Mirror.LEFT_RIGHT : Mirror.FRONT_BACK;
}
final int posMinRelMinusRegX = posMinRel.getX() - regionPos.getX();
final int posMinRelMinusRegY = posMinRel.getY() - regionPos.getY();
final int posMinRelMinusRegZ = posMinRel.getZ() - regionPos.getZ();
for (int y = startY; y <= endY; ++y) {
for (int z = startZ; z <= endZ; ++z) {
for (int x = startX; x <= endX; ++x) {
BlockState state = container.get(x, y, z);
if (state.getBlock() == Blocks.STRUCTURE_VOID) {
continue;
}
posMutable.set(x, y, z);
CompoundTag teNBT = blockEntityMap.get(posMutable);
posMutable.set(posMinRelMinusRegX + x,
posMinRelMinusRegY + y,
posMinRelMinusRegZ + z);
BlockPos pos = PositionUtils.getTransformedPlacementPosition(posMutable, schematicPlacement, placement);
pos = pos.offset(regionPosTransformed).offset(origin);
BlockState stateOld = world.getBlockState(pos);
if ((replace == ReplaceBehavior.NONE && !stateOld.isAir()) ||
(replace == ReplaceBehavior.WITH_NON_AIR && state.isAir())) {
continue;
}
if (mirrorMain != Mirror.NONE) {
state = state.mirror(mirrorMain);
}
if (mirrorSub != Mirror.NONE) {
state = state.mirror(mirrorSub);
}
if (rotationCombined != Rotation.NONE) {
state = state.rotate(rotationCombined);
}
BlockEntity te = world.getBlockEntity(pos);
if (te != null) {
if (te instanceof Container) {
((Container) te).clearContent();
}
world.setBlock(pos, barrier, 2 | 16 | (notifyNeighbors ? 0 : 512));
}
if (world.setBlock(pos, state, 2 | 16 | (notifyNeighbors ? 0 : 512)) && teNBT != null) {
te = world.getBlockEntity(pos);
if (te == null) {
continue;
}
teNBT = teNBT.copy();
NbtUtils.writeBlockPosToTag(pos, teNBT);
try {
TagUtil.loadTileWithComponents(te, teNBT);
} catch (Exception e) {
ServuxProtocol.LOGGER.warn("Failed to load BlockEntity data for {} @ {}", state, pos);
}
}
}
}
}
ServerLevel serverWorld = (ServerLevel) world;
IntBoundingBox box = new IntBoundingBox(startX, startY, startZ, endX, endY, endZ);
if (scheduledBlockTicks != null && !scheduledBlockTicks.isEmpty()) {
LevelTicks<Block> scheduler = serverWorld.getBlockTicks();
for (Map.Entry<BlockPos, ScheduledTick<Block>> entry : scheduledBlockTicks.entrySet()) {
BlockPos pos = entry.getKey();
if (!box.containsPos(pos)) {
continue;
}
posMutable.set(posMinRelMinusRegX + pos.getX(),
posMinRelMinusRegY + pos.getY(),
posMinRelMinusRegZ + pos.getZ());
pos = PositionUtils.getTransformedPlacementPosition(posMutable, schematicPlacement, placement);
pos = pos.offset(regionPosTransformed).offset(origin);
ScheduledTick<Block> tick = entry.getValue();
if (world.getBlockState(pos).getBlock() == tick.type()) {
scheduler.schedule(new ScheduledTick<>(tick.type(), pos, tick.triggerTick(), tick.priority(), tick.subTickOrder()));
}
}
}
if (scheduledFluidTicks != null && !scheduledFluidTicks.isEmpty()) {
LevelTicks<Fluid> scheduler = serverWorld.getFluidTicks();
for (Map.Entry<BlockPos, ScheduledTick<Fluid>> entry : scheduledFluidTicks.entrySet()) {
BlockPos pos = entry.getKey();
if (!box.containsPos(pos)) {
continue;
}
posMutable.set(posMinRelMinusRegX + pos.getX(),
posMinRelMinusRegY + pos.getY(),
posMinRelMinusRegZ + pos.getZ());
pos = PositionUtils.getTransformedPlacementPosition(posMutable, schematicPlacement, placement);
pos = pos.offset(regionPosTransformed).offset(origin);
ScheduledTick<Fluid> tick = entry.getValue();
if (world.getBlockState(pos).getFluidState().getType() == tick.type()) {
scheduler.schedule(new ScheduledTick<>(tick.type(), pos, tick.triggerTick(), tick.priority(), tick.subTickOrder()));
}
}
}
if (!notifyNeighbors) {
return true;
}
for (int y = startY; y <= endY; ++y) {
for (int z = startZ; z <= endZ; ++z) {
for (int x = startX; x <= endX; ++x) {
posMutable.set(posMinRelMinusRegX + x,
posMinRelMinusRegY + y,
posMinRelMinusRegZ + z);
BlockPos pos = PositionUtils.getTransformedPlacementPosition(posMutable, schematicPlacement, placement);
pos = pos.offset(regionPosTransformed).offset(origin);
world.updateNeighborsAt(pos, world.getBlockState(pos).getBlock());
}
}
}
return true;
}
public static void placeEntitiesToWorldWithinChunk(
Level world, ChunkPos chunkPos,
List<LitematicaSchematic.EntityInfo> entityList,
BlockPos origin,
SchematicPlacement schematicPlacement,
SubRegionPlacement placement
) {
BlockPos regionPos = placement.pos();
if (entityList == null) {
return;
}
BlockPos regionPosRelTransformed = PositionUtils.getTransformedBlockPos(regionPos, schematicPlacement.getMirror(), schematicPlacement.getRotation());
final int offX = regionPosRelTransformed.getX() + origin.getX();
final int offY = regionPosRelTransformed.getY() + origin.getY();
final int offZ = regionPosRelTransformed.getZ() + origin.getZ();
final double minX = (chunkPos.x << 4);
final double minZ = (chunkPos.z << 4);
final double maxX = (chunkPos.x << 4) + 16;
final double maxZ = (chunkPos.z << 4) + 16;
final Rotation rotationCombined = schematicPlacement.getRotation().getRotated(placement.rotation());
final Mirror mirrorMain = schematicPlacement.getMirror();
Mirror mirrorSub = placement.mirror();
if (mirrorSub != Mirror.NONE &&
(schematicPlacement.getRotation() == Rotation.CLOCKWISE_90 ||
schematicPlacement.getRotation() == Rotation.COUNTERCLOCKWISE_90)) {
mirrorSub = mirrorSub == Mirror.FRONT_BACK ? Mirror.LEFT_RIGHT : Mirror.FRONT_BACK;
}
for (LitematicaSchematic.EntityInfo info : entityList) {
Vec3 pos = info.posVec();
pos = PositionUtils.getTransformedPosition(pos, schematicPlacement.getMirror(), schematicPlacement.getRotation());
pos = PositionUtils.getTransformedPosition(pos, placement.mirror(), placement.rotation());
double x = pos.x + offX;
double y = pos.y + offY;
double z = pos.z + offZ;
float[] origRot = new float[2];
if (!(x >= minX && x < maxX && z >= minZ && z < maxZ)) {
continue;
}
CompoundTag tag = info.nbt().copy();
String id = tag.getStringOr("id", "");
// Avoid warning about invalid hanging position.
// Note that this position isn't technically correct, but it only needs to be within 16 blocks
// of the entity position to avoid the warning.
if (id.equals("minecraft:glow_item_frame") ||
id.equals("minecraft:item_frame") ||
id.equals("minecraft:leash_knot") ||
id.equals("minecraft:painting")) {
Vec3 p = NbtUtils.readEntityPositionFromTag(tag);
if (p == null) {
p = new Vec3(x, y, z);
NbtUtils.writeEntityPositionToTag(p, tag);
}
tag.store("block_pos", BlockPos.CODEC, new BlockPos((int) p.x, (int) p.y, (int) p.z));
}
ListTag rotation = tag.getListOrEmpty("Rotation");
origRot[0] = rotation.getFloatOr(0, 0F);
origRot[1] = rotation.getFloatOr(1, 0F);
Entity entity = EntityUtils.createEntityAndPassengersFromNBT(tag, world);
if (entity == null) {
continue;
}
rotateEntity(entity, x, y, z, rotationCombined, mirrorMain, mirrorSub);
// Update the sleeping position to the current position
if (entity instanceof LivingEntity living && living.isSleeping()) {
living.setSleepingPos(BlockPos.containing(x, y, z));
}
// Hack fix to fix the painting position offsets.
// The vanilla code will end up moving the position by one in two of the orientations,
// because it sets the hanging position to the given position (floored)
// and then it offsets the position from the hanging position
// by 0.5 or 1.0 blocks depending on the painting size.
if (entity instanceof Painting paintingEntity) {
Direction right = PositionUtils.rotateYCounterclockwise(paintingEntity.getDirection());
if ((paintingEntity.getVariant().value().width() % 2) == 0 &&
right.getAxisDirection() == Direction.AxisDirection.POSITIVE) {
x -= 1.0 * right.getStepX();
z -= 1.0 * right.getStepZ();
}
if ((paintingEntity.getVariant().value().height() % 2) == 0) {
y -= 1.0;
}
entity.teleportTo(x, y, z);
}
if (entity instanceof ItemFrame frameEntity) {
if (frameEntity.getYRot() != origRot[0] && (frameEntity.getXRot() == 90.0F || frameEntity.getXRot() == -90.0F)) {
// Fix Yaw only if Pitch is +/- 90.0F (Floor, Ceiling mounted)
frameEntity.setYRot(origRot[0]);
}
}
EntityUtils.spawnEntityAndPassengersInWorld(entity, world);
if (entity instanceof Display) {
entity.tick(); // Required to set the full data for rendering
}
}
}
public static void rotateEntity(
Entity entity, double x, double y, double z,
Rotation rotationCombined, Mirror mirrorMain, Mirror mirrorSub
) {
float rotationYaw = entity.getYRot();
if (mirrorMain != Mirror.NONE) {
rotationYaw = entity.mirror(mirrorMain);
}
if (mirrorSub != Mirror.NONE) {
rotationYaw = entity.mirror(mirrorSub);
}
if (rotationCombined != Rotation.NONE) {
rotationYaw += entity.getYRot() - entity.rotate(rotationCombined);
}
entity.snapTo(x, y, z, rotationYaw, entity.getXRot());
EntityUtils.setEntityRotations(entity, rotationYaw, entity.getXRot());
}
}

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