Compare commits

...

14 Commits

Author SHA1 Message Date
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
90 changed files with 9881 additions and 10337 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 中。
+9 -9
View File
@@ -39,14 +39,6 @@ jobs:
- name: CreateMojmapPaperclipJar
run: ./gradlew --refresh-dependencies createMojmapPaperclipJar
- name: Publish to repo
if: github.event_name != 'pull_request' && 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: Upload Artifact
uses: actions/upload-artifact@v4
with:
@@ -57,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" />
+13 -10
View File
@@ -1,5 +1,5 @@
为Lophine贡献代码
===========
===============
[English](./CONTRIBUTING_EN.md) | **中文**
@@ -45,17 +45,20 @@ Lophine 使用和 Folia 一样的补丁系统,并为了针对不同部分的
4. 进入 仓库根目录下的 `*-api``*-server` 文件夹进行修改。
以下为对上述各个文件夹的简单描述,详细描述可以参考[这里](https://github.com/Toffikk/paperweight-examples/blob/18241979c88068d5b061d95ad69c98ecb201c246/README.md)
1. API部分
- `lophine-api` :对新增API的修改
- `luminol-api` :对luminol-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原版服务器的修改和新增文件应当在此文件夹下进行
- `luminol-server` :对luminol-server的修改应当在此文件夹下进行
- `folia-server` :对folia-Server的修改应当在此文件夹下进行
- `paper-server` :对于paper对服务器逻辑的修改应当在此文件夹下进行
- `lophine-server` :对Minecraft原版服务器的修改和新增文件应当在此文件夹下进行
- `luminol-server` :对luminol-server的修改应当在此文件夹下进行
- `folia-server` :对folia-Server的修改应当在此文件夹下进行
- `paper-server` :对于paper对服务器逻辑的修改应当在此文件夹下进行
顺便一提,仓库根目录下的 `*-api``*-server` 并不是正常的 git 仓库:
@@ -82,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` 来修改已被修改的补丁;
+11 -9
View File
@@ -1,5 +1,5 @@
Contributing to Lophine
===========
=======================
**English** | [中文](./CONTRIBUTING.md)
@@ -50,16 +50,18 @@ If you have forked the main repository, then you should follow the steps below:
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` : 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
- `lophine-api` : Modifications to the new API
- `luminol-api` : Modifications to Luminol API should be carried out in this folder
- `folia-api` : Modifications to Folia API should be carried out in this folder
- `paper-api` : Modifications to Paper API/Spigot API/Bukkit API should be carried out in this folder
2. Server part
- `lophine-server` : Changes and new files to the Minecraft vanilla server should be made in this folder
- `luminol-server` : Changes to luminol-server should be made in this folder
- `folia-server` : Changes to folia-server should be made in this folder
- `paper-server` : Modifications to the server logic for paper should be made in this folder
- `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.
+1 -1
View File
@@ -4,7 +4,7 @@ mcVersion=1.21.8
release=2
# 0 for skip release, 1 for pre-release, 2 for release
luminolRef=059a38cc66981740d5055dca76e156dd5f1d8162
luminolRef=98ea236c84adf0fc960d1e0b288413a41bb22ada
org.gradle.configuration-cache=true
org.gradle.caching=true
+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
+2 -2
View File
@@ -51,7 +51,7 @@
// 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")
@@ -277,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",
@@ -70,7 +70,7 @@
"Build-Number" to (build ?: ""),
"Build-Time" to buildTime.toString(),
"Git-Branch" to gitBranch,
@@ -448,7 +_,7 @@
@@ -447,7 +_,7 @@
}
fill {
@@ -5,29 +5,17 @@ Subject: [PATCH] Rebrand to Lophine
diff --git a/src/main/java/me/earthme/luminol/config/ConfigManager.java b/src/main/java/me/earthme/luminol/config/ConfigManager.java
index 26a812cd526acfd1edf31a5c9799fa6deeae927a..9b01cf6f066b2998a2b178321782be1c96f73b0c 100644
index 0a3fa2756dae0eea9e2c0fa6bbaa425584763036..6c1cb2866c81a1bc861289aedb2c318f910a8498 100644
--- a/src/main/java/me/earthme/luminol/config/ConfigManager.java
+++ b/src/main/java/me/earthme/luminol/config/ConfigManager.java
@@ -19,6 +19,7 @@ public class ConfigManager {
public static void initConfigs() throws IOException {
configfiles.put("luminol", new ConfigsInstance(new File("luminol_config"), "luminol", "me.earthme.luminol.config.modules"));
+ configfiles.put("lophine", new ConfigsInstance(new File("lophine_config"), "lophine", "fun.bm.lophine.config.modules")); // add lophine config to global config
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();
}
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/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,18 @@
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;
@@ -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) {
@@ -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
@@ -20,10 +20,10 @@ index 0d5ed9602492c8393088954105e9a19032067b8b..e2fc15d84fafde6847ecead23c551694
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 6675b83c9f15d0341dcf169ec3ac20f401c5d10b..e8c9e739de35313c0212bab97a202fa362fd0887 100644
index 86d20d884fb59d0121bdcc291af1fc97b8bf603d..a0f1db50adf5163a671d3d9e9df7adb7de4250c6 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -2349,6 +2349,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -2352,6 +2352,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
return this.reloadResources(selectedIds, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN);
}
@@ -5,10 +5,10 @@ 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 06e41bca6b964ca71ea7bca547b03cdb3610ba31..b7bc75648634aefc549a69631ab6e401eb902e7d 100644
index 11e6197dec541b28733715f0d7eaa4c7b834d632..6b7c92fdf562dfa46eee8e8acbc82b02fc3226c5 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -1341,6 +1341,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 06e41bca6b964ca71ea7bca547b03cdb3610ba31..b7bc75648634aefc549a69631ab6e401
}
this.level().broadcastEntityEvent(this, (byte)3);
@@ -1364,6 +1371,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,7 +48,7 @@ index 06e41bca6b964ca71ea7bca547b03cdb3610ba31..b7bc75648634aefc549a69631ab6e401
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 d55a6989fd68f0bcf7bd05e9420fda1115afa6c0..5369a6597e48ed9f0d8f7f0b9685ce4950df4fe5 100644
index 567d63ae589705e13403814b36e35d00c241a69e..919628d93add71d9b30cae29587a0049cce6973d 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
@@ -134,7 +134,7 @@ index d55a6989fd68f0bcf7bd05e9420fda1115afa6c0..5369a6597e48ed9f0d8f7f0b9685ce49
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 510514e363f4816a6e286fb9b5488153e03c9e55..4638af10539a316acae64fb26881c8e699c496e0 100644
index 510514e363f4816a6e286fb9b5488153e03c9e55..ce5602283757c564b45ec0765b5f1868ffcd180b 100644
--- a/net/minecraft/world/entity/raid/Raider.java
+++ b/net/minecraft/world/entity/raid/Raider.java
@@ -157,7 +157,13 @@ public abstract class Raider extends PatrollingMonster {
@@ -142,13 +142,13 @@ index 510514e363f4816a6e286fb9b5488153e03c9e55..4638af10539a316acae64fb26881c8e6
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
+ // Luminol start - Raid changes adapt DamageSource trace
+ // Luminol start - raid revert adapt Cross Region Damage trace
+ if (fun.bm.lophine.config.modules.experiment.EntityDamageSourceTraceConfig.enabled) {
+ 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
+ // Luminol end - raid revert adapt Cross Region Damage trace
}
this.setPatrolLeader(false);
}
@@ -18,7 +18,7 @@ index 6868b915bf3deb85783a638d4441a15fea6da2dc..70740381c6501c1a518c52b24381edd1
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 2f733a3f75f9fe637864158a31d7507eac121d45..f7fcb4782b49374df8fe8f4401a4259f037221f7 100644
index 75f327fff68097c1c9f65767552f686421e1e397..ed7421895311d89ad9726804893eff423868d375 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -147,7 +147,7 @@ import net.minecraft.world.waypoints.WaypointTransmitter;
@@ -30,7 +30,7 @@ index 2f733a3f75f9fe637864158a31d7507eac121d45..f7fcb4782b49374df8fe8f4401a4259f
// CraftBukkit start
private static final int CURRENT_LEVEL = 2;
public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first snapTo
@@ -6439,4 +6439,46 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -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
@@ -5,7 +5,7 @@ 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..adf4b57aeedf80128dfa03c790616f54062b33e7 100644
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
@@ -18,7 +18,7 @@ index 79242037e868a140513f032e74e8594c0c6ff41c..adf4b57aeedf80128dfa03c790616f54
Player tradingPlayer = this.getTradingPlayer();
+ if (fun.bm.lophine.config.modules.misc.VillagerConfig.villagerInfiniteTrade) {
+ for (MerchantOffer merchantOffer : offers) {
+ merchantOffer.maxUses = 16384;
+ merchantOffer.maxUses = 524288;
+ }
+ }
+ // Lophine end - infinite trade
@@ -26,7 +26,7 @@ index 79242037e868a140513f032e74e8594c0c6ff41c..adf4b57aeedf80128dfa03c790616f54
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..a876335e26daec9a9a3a2f00ee5cb23cfd913aa6 100644
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 {
@@ -38,7 +38,7 @@ index 6e2fa442ddcd4ef3c4fe789b9ebac9dd409c9de7..a876335e26daec9a9a3a2f00ee5cb23c
+ MerchantOffers offers = this.getOffers().copy();
+ if (fun.bm.lophine.config.modules.misc.VillagerConfig.villagerInfiniteTrade) {
+ for (MerchantOffer merchantOffer : offers) {
+ merchantOffer.maxUses = 16384;
+ merchantOffer.maxUses = 5242884;
+ }
+ }
+ // Lophine end - infinite trade
@@ -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: 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/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() {
@@ -8,10 +8,10 @@ As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/6a2b9ee6b330cd18b
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
index 6dc5f3e88a3886b03b99d59b317085fdc3e2fa9a..e556bcaf2a5863073a047947f5962179d054e8d9 100644
index 9c7366fce56f2c8105c2e5b48f8d618e028f2604..7ab1d97081ffa8cd7ce69cf3165a312697f1fdbc 100644
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -280,8 +280,8 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
@@ -281,8 +281,8 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
if (this.stopReadingPackets) {
return;
}
@@ -23,7 +23,7 @@ index 6dc5f3e88a3886b03b99d59b317085fdc3e2fa9a..e556bcaf2a5863073a047947f5962179
synchronized (PACKET_LIMIT_LOCK) {
if (this.allPacketCounts != null) {
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 68f0023c4234585a8385a830ed410883c8bd1549..206b4a532eba02b2a1f6f3b832970bc4c94fc45b 100644
index 227f0c315582b9c499915bebfb820049839466e6..1d52376f034dc5690e8b399512146b47b92c3c14 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -856,7 +856,7 @@ public class ServerGamePacketListenerImpl
@@ -51,7 +51,7 @@ index 68f0023c4234585a8385a830ed410883c8bd1549..206b4a532eba02b2a1f6f3b832970bc4
// CraftBukkit start - replaced with thread safe throttle
if (org.spigotmc.SpigotConfig.enableSpamExclusions) {
for (String exclude : org.spigotmc.SpigotConfig.spamExclusions) {
@@ -3333,7 +3335,7 @@ public class ServerGamePacketListenerImpl
@@ -3335,7 +3337,7 @@ public class ServerGamePacketListenerImpl
@Override
public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) {
// Paper start - auto recipe limit
@@ -28,7 +28,7 @@ index 643797124fe5a4489d0b7419b7e600c04f283ef2..04b4100da96aad50f08e8c59200eec93
public String serialize(HolderLookup.Provider levelRegistry) {
diff --git a/net/minecraft/server/commands/GiveCommand.java b/net/minecraft/server/commands/GiveCommand.java
index 5a1c21b05545a03fbb00cf734605049870d3eecb..a0459bc6b2241e7fca74bfbfce058882a8ad7f49 100644
index f7002d6752aa03603eb06e298ba1d7130cf13826..72c87f7c0392d087440db9d267a608598f6feb6d 100644
--- a/net/minecraft/server/commands/GiveCommand.java
+++ b/net/minecraft/server/commands/GiveCommand.java
@@ -55,7 +55,7 @@ public class GiveCommand {
@@ -41,10 +41,10 @@ index 5a1c21b05545a03fbb00cf734605049870d3eecb..a0459bc6b2241e7fca74bfbfce058882
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 206b4a532eba02b2a1f6f3b832970bc4c94fc45b..39b5422af26fce5f1d5437ed35676b511bf13241 100644
index 1d52376f034dc5690e8b399512146b47b92c3c14..4b215dc69887df9382bde4cfa2f37afadfe1bcb2 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -3078,7 +3078,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;
@@ -53,7 +53,7 @@ index 206b4a532eba02b2a1f6f3b832970bc4c94fc45b..39b5422af26fce5f1d5437ed35676b51
toPlace = Math.min(toPlace, slot.container.getMaxStackSize() - clickedItem.getCount());
if (toPlace == 1) {
action = InventoryAction.PLACE_ONE;
@@ -3114,7 +3114,7 @@ public class ServerGamePacketListenerImpl
@@ -3115,7 +3115,7 @@ public class ServerGamePacketListenerImpl
}
} else if (ItemStack.isSameItemSameComponents(cursor, clickedItem)) {
if (clickedItem.getCount() >= 0) {
@@ -62,15 +62,15 @@ index 206b4a532eba02b2a1f6f3b832970bc4c94fc45b..39b5422af26fce5f1d5437ed35676b51
// As of 1.5, this is result slots only
action = InventoryAction.PICKUP_ALL;
}
@@ -3325,6 +3325,7 @@ public class ServerGamePacketListenerImpl
@@ -3326,6 +3326,7 @@ public class ServerGamePacketListenerImpl
this.player.containerMenu.broadcastFullState();
} else {
this.player.containerMenu.broadcastChanges();
+ 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.
}
@@ -3435,7 +3436,7 @@ public class ServerGamePacketListenerImpl
@@ -3437,7 +3438,7 @@ public class ServerGamePacketListenerImpl
}
boolean flag1 = packet.slotNum() >= 1 && packet.slotNum() <= 45;
@@ -79,7 +79,7 @@ index 206b4a532eba02b2a1f6f3b832970bc4c94fc45b..39b5422af26fce5f1d5437ed35676b51
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();
@@ -3477,6 +3478,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();
@@ -88,10 +88,10 @@ index 206b4a532eba02b2a1f6f3b832970bc4c94fc45b..39b5422af26fce5f1d5437ed35676b51
} else if (flag && flag2) {
if (this.dropSpamThrottler.isUnderThreshold()) {
diff --git a/net/minecraft/world/Container.java b/net/minecraft/world/Container.java
index b382665cc125b8b5c0938e5e55984e4bf91d37ff..86cac164a2bf0e76528396e6aabbfd64cfc29559 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());
}
@@ -127,10 +127,10 @@ index d907e24d563e27acab2f2bf9711b1755ea9afd19..75170c8d3be477a6ea2a1d62018a6ab6
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 b745eb5d9c68547335247910ff2ae8d5fb36349c..0c2bf0f8edb34a9011092ea94e54f7bbcee0bbbc 100644
index fd4f1e63f980b6959fc80084651991f8f8bc6c34..e5e8c752129a6ebb0dd1315c9b8ec459be11ef07 100644
--- a/net/minecraft/world/entity/item/ItemEntity.java
+++ b/net/minecraft/world/entity/item/ItemEntity.java
@@ -279,10 +279,15 @@ 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();
@@ -229,7 +229,7 @@ index 02d2efef2dc0f0e12eac0c71fa290af706f7694d..99f109e2653eff10c011f380694bd77a
default SlotAccess getChestVehicleSlot(final int index) {
diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java
index 9b6cd35e4dfa894069238a1365db16050aa5542b..7e9bfb433a252bb4211e490365265d35590f2474 100644
index fcb2e0a722ed94acf08da06f06b20f42652896bc..9489a50df2de3d641235153c5a0217d96c1f4fa9 100644
--- a/net/minecraft/world/inventory/AbstractContainerMenu.java
+++ b/net/minecraft/world/inventory/AbstractContainerMenu.java
@@ -295,6 +295,13 @@ public abstract class AbstractContainerMenu {
@@ -319,7 +319,7 @@ index 9b6cd35e4dfa894069238a1365db16050aa5542b..7e9bfb433a252bb4211e490365265d35
default -> stack.getCount();
};
}
@@ -897,7 +904,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()) {
@@ -355,10 +355,10 @@ index 5ceb8964476b40db4511bec91ff13c4f522a1357..f416d7976e41a06b5a8be93485eaa233
@Nullable
diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java
index 6154da1158756cff072d7e8cae10e20eafed1eec..93bbe7b473b8150954f06602a9f311867fe9a550 100644
index 33c3a5e39e8e1aa023baab76ef94f8aed481480e..a27e1d53abc2d8b8912226add33510067e10ed52 100644
--- a/net/minecraft/world/item/ItemStack.java
+++ b/net/minecraft/world/item/ItemStack.java
@@ -162,7 +162,7 @@ public final class ItemStack implements DataComponentHolder {
@@ -166,7 +166,7 @@ public final class ItemStack implements DataComponentHolder, ChangePublisher<net
@Deprecated
@Nullable
private Item item;
@@ -367,7 +367,7 @@ index 6154da1158756cff072d7e8cae10e20eafed1eec..93bbe7b473b8150954f06602a9f31186
@Nullable
private Entity entityRepresentation;
@@ -189,7 +189,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);
@@ -377,7 +377,7 @@ index 6154da1158756cff072d7e8cae10e20eafed1eec..93bbe7b473b8150954f06602a9f31186
}
}
@@ -198,13 +199,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 {
@@ -397,7 +397,7 @@ index 6154da1158756cff072d7e8cae10e20eafed1eec..93bbe7b473b8150954f06602a9f31186
} finally {
net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(prev);
}
@@ -299,7 +302,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();
@@ -420,10 +420,10 @@ index 38b03c7b02bdfc579e5e126c12de3d878e26d188..caf31218a1aff84367da126ee1dc46b8
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 36a72a11d28f99bfe85868461925b778cc01478e..2a9f204a514d697178fb3ffac25aece0024f0347 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);
@@ -433,10 +433,10 @@ index 36a72a11d28f99bfe85868461925b778cc01478e..2a9f204a514d697178fb3ffac25aece0
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 5a094257a31f0500278a706a418e1697f8810ffb..63d6a43dab067aa4c8fb67095c455130196eef9f 100644
index 10b2cc20bbf3b70e4e09dbbe14b90506e6366158..1535bb8cec8692e8d26eaff090288f1b8f75c253 100644
--- a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
@@ -145,7 +145,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);
@@ -446,10 +446,10 @@ index 5a094257a31f0500278a706a418e1697f8810ffb..63d6a43dab067aa4c8fb67095c455130
}
diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
index 5de769e7bd56bb3355c3c711d46ce5e103ea4409..8ba900fda7cce45679cde2b8b3272654129135ca 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);
@@ -458,7 +458,7 @@ index 5de769e7bd56bb3355c3c711d46ce5e103ea4409..8ba900fda7cce45679cde2b8b3272654
}
@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
@@ -469,4 +469,4 @@ index 5de769e7bd56bb3355c3c711d46ce5e103ea4409..8ba900fda7cce45679cde2b8b3272654
+ 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,86 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Mon, 28 Jul 2025 01:06:40 +0800
Subject: [PATCH] Leaves: Vanilla hopper
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/2c9f83c7b99178851edf5608b37b8c2adddd93cd/leaves-server/minecraft-patches/features/0095-Vanilla-hopper.patch)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
index 8ba900fda7cce45679cde2b8b3272654129135ca..9d7ec9f4f18ed263540ccddf16c6c1a92d3477a4 100644
--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
@@ -280,36 +280,49 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
ItemStack movedItem = origItemStack;
final int originalItemCount = origItemStack.getCount();
final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount);
- container.setChanged(); // original logic always marks source inv as changed even if no move happens.
- movedItem.setCount(movedItemCount);
- if (!worldData.skipPullModeEventFire) { // Folia - region threading
- movedItem = callPullMoveEvent(hopper, container, movedItem);
- if (movedItem == null) { // cancelled
+ // Leaves start - fix vanilla
+ if (fun.bm.lophine.config.modules.misc.RedStoneConfig.vanillaHopper && movedItem.getCount() <= movedItemCount) {
+ movedItem = origItemStack.copy();
+ final ItemStack remainingItem = addItem(container, hopper, container.removeItem(i, movedItemCount), null);
+ if (remainingItem.isEmpty()) {
+ container.setChanged();
+ return true;
+ }
+ container.setItem(i, movedItem);
+ } else {
+ container.setChanged(); // original logic always marks source inv as changed even if no move happens.
+ movedItem.setCount(movedItemCount);
+
+ if (!worldData.skipPullModeEventFire) {
+ movedItem = callPullMoveEvent(hopper, container, movedItem);
+ if (movedItem == null) { // cancelled
+ origItemStack.setCount(originalItemCount);
+ // Drastically improve performance by returning true.
+ // No plugin could have relied on the behavior of false as the other call
+ // site for IMIE did not exhibit the same behavior
+ return true;
+ }
+ }
+ final ItemStack remainingItem = addItem(container, hopper, movedItem, null);
+ final int remainingItemCount = remainingItem.getCount();
+ if (remainingItemCount != movedItemCount) {
+ origItemStack = origItemStack.copy(true);
origItemStack.setCount(originalItemCount);
- // Drastically improve performance by returning true.
- // No plugin could have relied on the behavior of false as the other call
- // site for IMIE did not exhibit the same behavior
+ if (!origItemStack.isEmpty()) {
+ origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount);
+ }
+
+ IGNORE_TILE_UPDATES.set(true);
+ container.setItem(i, origItemStack);
+ IGNORE_TILE_UPDATES.set(false);
+ container.setChanged();
return true;
}
- }
-
- final ItemStack remainingItem = addItem(container, hopper, movedItem, null);
- final int remainingItemCount = remainingItem.getCount();
- if (remainingItemCount != movedItemCount) {
- origItemStack = origItemStack.copy(true);
origItemStack.setCount(originalItemCount);
- if (!origItemStack.isEmpty()) {
- origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount);
- }
- IGNORE_TILE_UPDATES.set(true); // Folia - region threading
- container.setItem(i, origItemStack);
- IGNORE_TILE_UPDATES.set(false); // Folia - region threading
- container.setChanged();
- return true;
}
- origItemStack.setCount(originalItemCount);
+ // Leaves end - fix vanilla
if (level.paperConfig().hopper.cooldownWhenFull) {
applyCooldown(hopper);
@@ -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,25 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sat, 26 Jul 2025 13:40:28 +0800
Subject: [PATCH] Paper: Fix quick craft
Co-authored by: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
As a part of : Paper (https://github.com/PaperMC/Paper/pull/11831/files#diff-3991edc9191addaab9f9701589f8fb4a05001c6f3c9ce1f51d22001f8de805ca)
Licensed under: MIT (https://github.com/PaperMC/Paper/blob/main/licenses/MIT.md)
diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
index 5369a6597e48ed9f0d8f7f0b9685ce4950df4fe5..919628d93add71d9b30cae29587a0049cce6973d 100644
--- a/net/minecraft/world/entity/LivingEntity.java
+++ b/net/minecraft/world/entity/LivingEntity.java
@@ -4012,11 +4012,6 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
return null;
} else {
double d = this.getEyeY() - 0.3F;
- // Paper start
- final ItemStack tmp = stack.copy();
- stack.setCount(0);
- stack = tmp;
- // Paper end
ItemEntity itemEntity = new ItemEntity(this.level(), this.getX(), d, this.getZ(), stack);
itemEntity.setPickUpDelay(40);
if (includeThrower) {
@@ -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 5c21fc0c749973d17df690fe7e2eab597b198131..f01e3147cb49ee37d8e33d4146aa1a72cf38014f 100644
index 593dbcda4b497291eb1ecdc64b947a8d279f8be6..661f797b39853104b16d75ca7ba0eda156a74af3 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -1086,6 +1086,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
@@ -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 d679ab599dfd0bdbdc3ab5530d7fcd1c38baf7fa..6b4e2956222a0ca7f72f73032dc98878fbd257ee 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 d679ab599dfd0bdbdc3ab5530d7fcd1c38baf7fa..6b4e2956222a0ca7f72f73032dc98878
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 d679ab599dfd0bdbdc3ab5530d7fcd1c38baf7fa..6b4e2956222a0ca7f72f73032dc98878
}
@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) {
@@ -5,7 +5,7 @@ Subject: [PATCH] Spawn invulnerable time
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index b7bc75648634aefc549a69631ab6e401eb902e7d..0e2d575200f3ff3ec1600a0663152e3c37ea67b0 100644
index 6b7c92fdf562dfa46eee8e8acbc82b02fc3226c5..144b2c9396b8162f9cce8b343d998907bf7fb5aa 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -232,6 +232,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
@@ -24,7 +24,7 @@ index b7bc75648634aefc549a69631ab6e401eb902e7d..0e2d575200f3ff3ec1600a0663152e3c
if (this.invulnerableTime > 0) {
this.invulnerableTime--;
}
@@ -1431,6 +1433,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 {
@@ -1,51 +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 93d722d437121e605cee0e168ac17ee9bef0610e..cb2a2fbd17157726b285665fdca0ab9663e0d1de 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -1111,7 +1111,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() && !(fun.bm.lophine.config.modules.misc.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);
}
}
@@ -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 4b215dc69887df9382bde4cfa2f37afadfe1bcb2..3ce1e5a7d7095c15250b94d032587f584119824d 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
File diff suppressed because it is too large Load Diff
@@ -1,8 +1,11 @@
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 2f41a92465b9da28e026297cc3528898bb1c8412..fb5300ec1bd19b3dfd6a2b4106440b9f73c48205 100644
@@ -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;
@@ -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
@@ -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";
}
}
@@ -11,9 +11,6 @@ public class OldFeatureConfig implements IConfigModule {
@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;
@@ -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";
}
}
@@ -7,7 +7,7 @@ import me.earthme.luminol.config.flags.ConfigInfo;
public class VillagerConfig implements IConfigModule {
@ConfigInfo(baseName = "villager-infinite-trade", comments =
"""
Allow villager infinite trade (limit of 16384 times)
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;
@@ -6,8 +6,8 @@ import me.earthme.luminol.config.flags.ConfigInfo;
public class NetworkConfig implements IConfigModule {
@ConfigInfo(baseName = "unlimit-packet", comments = """
Disable packet limit""")
public static boolean disablePacketLimit = true;
Disable packet limit""")
public static boolean disablePacketLimit = false;
@Override
public EnumConfigCategory getCategory() {
@@ -6,6 +6,7 @@ import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.config.flags.TransformedConfig;
public class RemovedConfig implements IConfigModule {
@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 =
@@ -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());
}
}
@@ -0,0 +1,178 @@
/*
* 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.logger;
import com.google.common.collect.ImmutableList;
import com.mojang.serialization.Codec;
import io.papermc.paper.threadedregions.TickRegionScheduler;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.Tag;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.ServerTickRateManager;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.NaturalSpawner;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.servux.ServuxHudDataProtocol;
import org.leavesmc.leaves.protocol.servux.logger.data.MobCapData;
import org.leavesmc.leaves.protocol.servux.logger.data.TickData;
import java.util.ArrayList;
import java.util.function.Function;
public abstract class DataLogger<T extends Tag> {
private final Type type;
public DataLogger(Type type) {
this.type = type;
}
public Type getType() {
return this.type;
}
public abstract T getResult(MinecraftServer server, ServuxHudDataProtocol protocol, ServerPlayer player);
public enum Type implements StringRepresentable {
TPS("tps", Tps::new, Tps.CODEC),
MOB_CAPS("mob_caps", MobCaps::new, MobCaps.CODEC);
public static final EnumCodec<Type> CODEC = StringRepresentable.fromEnum(Type::values);
public static final ImmutableList<Type> VALUES = ImmutableList.copyOf(values());
private final String name;
private final Function<Type, DataLogger<?>> factory;
private final Codec<?> codec;
Type(String name, Function<Type, DataLogger<?>> factory, Codec<?> codec) {
this.name = name;
this.factory = factory;
this.codec = codec;
}
public static @Nullable Type fromStringStatic(String name) {
for (Type type : VALUES) {
if (type.name.equalsIgnoreCase(name)) {
return type;
}
}
return null;
}
public Function<Type, DataLogger<?>> getFactory() {
return factory;
}
public @Nullable DataLogger<?> init() {
return this.factory.apply(this);
}
public Codec<?> codec() {
return this.codec;
}
@Override
public @NotNull String getSerializedName() {
return name;
}
}
public static class Tps extends DataLogger<CompoundTag> {
public static final Codec<CompoundTag> CODEC = CompoundTag.CODEC;
public Tps(Type type) {
super(type);
}
@Override
public CompoundTag getResult(MinecraftServer server, ServuxHudDataProtocol protocol, ServerPlayer player) {
this.build(server, protocol, player);
return null;
}
private TickData build(MinecraftServer server, ServuxHudDataProtocol protocol, ServerPlayer player) {
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()),
() -> {
ServerTickRateManager tickManager = server.tickRateManager();
boolean frozen = tickManager.isFrozen();
boolean sprinting = tickManager.isSprinting();
io.papermc.paper.threadedregions.TickData.TickReportData tickData = TickRegionScheduler.getCurrentRegion().getData().getRegionSchedulingHandle().getTickReport5s(System.nanoTime());
final double tps = tickData.tpsData().segmentAll().average();
final double mspt = tickData.timePerTickData().segmentAll().average() / 1.0E6;
TickData tk = new TickData(
mspt, tps,
tickManager.getRemainingSprintTicks(),
frozen, sprinting,
tickManager.isSteppingForward()
);
Tag ret = TickData.CODEC.encodeStart(server.registryAccess().createSerializationContext(NbtOps.INSTANCE), tk).getOrThrow();
protocol.applyData(Type.TPS, player, ret);
}
);
return null;
}
}
public static class MobCaps extends DataLogger<CompoundTag> {
public static final Codec<CompoundTag> CODEC = CompoundTag.CODEC;
public MobCaps(Type type) {
super(type);
}
@Override
public CompoundTag getResult(MinecraftServer server, ServuxHudDataProtocol protocol, ServerPlayer player) {
CompoundTag nbt = new CompoundTag();
for (ServerLevel world : server.getAllLevels()) {
NaturalSpawner.SpawnState info = world.getChunkSource().getLastSpawnState();
if (info == null) {
continue;
}
int chunks = info.getSpawnableChunkCount();
Object2IntMap<MobCategory> counts = info.getMobCategoryCounts();
MobCapData mobCapData = new MobCapData(new ArrayList<>(), world.getGameTime());
for (MobCategory category : MobCategory.values()) {
mobCapData.data().add(new MobCapData.Cap(counts.getOrDefault(category, 0), NaturalSpawner.globalLimitForCategory(world, category, chunks)));
}
try {
nbt.put(world.dimension().location().toString(), MobCapData.CODEC.encodeStart(world.registryAccess().createSerializationContext(NbtOps.INSTANCE), mobCapData).getPartialOrThrow());
} catch (Exception ignored) {
}
}
return nbt;
}
}
}
@@ -0,0 +1,50 @@
/*
* 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.logger.data;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.PrimitiveCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.world.entity.MobCategory;
import java.util.List;
public record MobCapData(List<Cap> data, long worldTick) {
public static final int CAP_COUNT = MobCategory.values().length;
private static MobCapData of(int count, List<Cap> data, long worldTick) {
return new MobCapData(data, worldTick);
}
public static final Codec<MobCapData> CODEC = RecordCodecBuilder.create(
(inst) -> inst.group(
PrimitiveCodec.INT.fieldOf("cap_count").forGetter($ -> CAP_COUNT),
Codec.list(Cap.CODEC).fieldOf("cap_data").forGetter(MobCapData::data),
PrimitiveCodec.LONG.fieldOf("WorldTick").forGetter(MobCapData::worldTick)
).apply(inst, MobCapData::of)
);
public record Cap(int current, int cap) {
public static Codec<Cap> CODEC = RecordCodecBuilder.create(
(inst) -> inst.group(
PrimitiveCodec.INT.fieldOf("current").forGetter(Cap::current),
PrimitiveCodec.INT.fieldOf("cap").forGetter(Cap::cap)
).apply(inst, Cap::new)
);
}
}
@@ -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.servux.logger.data;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.PrimitiveCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
public record TickData(double mspt, double tps, long sprintTicks, boolean frozen, boolean sprinting, boolean stepping) {
public static Codec<TickData> CODEC = RecordCodecBuilder.create(
(inst) -> inst.group(
PrimitiveCodec.DOUBLE.fieldOf("mspt").forGetter(TickData::mspt),
PrimitiveCodec.DOUBLE.fieldOf("tps").forGetter(TickData::tps),
PrimitiveCodec.LONG.fieldOf("sprintTicks").forGetter(TickData::sprintTicks),
PrimitiveCodec.BOOL.fieldOf("frozen").forGetter(TickData::frozen),
PrimitiveCodec.BOOL.fieldOf("sprinting").forGetter(TickData::sprinting),
PrimitiveCodec.BOOL.fieldOf("stepping").forGetter(TickData::stepping)
).apply(inst, TickData::new)
);
}
@@ -1,203 +0,0 @@
package org.leavesmc.leaves.util;
import fun.bm.lophine.config.modules.misc.ContainerExpansionConfig;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.item.component.ItemContainerContents;
import net.minecraft.world.level.block.ShulkerBoxBlock;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
public class ItemOverstackUtils {
private static final List<ItemUtil> overstackUtils = List.of(
new ShulkerBox()
// new CurseEnchantedBook()
);
public static int getItemStackMaxCount(ItemStack stack) {
int size;
for (ItemUtil util : overstackUtils) {
if ((size = util.getMaxServerStackCount(stack)) != -1) {
return size;
}
}
return stack.getMaxStackSize();
}
public static int getNetworkMaxCount(ItemStack stack) {
int size;
for (ItemUtil util : overstackUtils) {
if ((size = util.getMaxClientStackCount(stack)) != -1) {
return size;
}
}
return stack.getMaxStackSize();
}
public static boolean tryStackItems(ItemEntity self, ItemEntity other) {
for (ItemUtil util : overstackUtils) {
if (util.tryStackItems(self, other)) {
return true;
}
}
return false;
}
public static boolean hasOverstackingItem() {
return overstackUtils.stream().anyMatch(ItemUtil::isEnabled);
}
public static int getItemStackMaxCountReal(ItemStack stack) {
CompoundTag nbt = Optional.ofNullable(stack.get(DataComponents.CUSTOM_DATA)).orElse(CustomData.EMPTY).copyTag();
return nbt.getInt("Leaves.RealStackSize").orElse(stack.getMaxStackSize());
}
public static ItemStack encodeMaxStackSize(ItemStack itemStack) {
int realMaxStackSize = getItemStackMaxCountReal(itemStack);
int modifiedMaxStackSize = getNetworkMaxCount(itemStack);
if (itemStack.getMaxStackSize() != modifiedMaxStackSize) {
itemStack.set(DataComponents.MAX_STACK_SIZE, modifiedMaxStackSize);
CompoundTag nbt = itemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
nbt.putInt("Leaves.RealStackSize", realMaxStackSize);
itemStack.set(DataComponents.CUSTOM_DATA, CustomData.of(nbt));
}
return itemStack;
}
public static ItemStack decodeMaxStackSize(ItemStack itemStack) {
int realMaxStackSize = getItemStackMaxCountReal(itemStack);
if (itemStack.getMaxStackSize() != realMaxStackSize) {
itemStack.set(DataComponents.MAX_STACK_SIZE, realMaxStackSize);
CompoundTag nbt = itemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
nbt.remove("Leaves.RealStackSize");
if (nbt.isEmpty()) {
itemStack.remove(DataComponents.CUSTOM_DATA);
} else {
itemStack.set(DataComponents.CUSTOM_DATA, CustomData.of(nbt));
}
}
return itemStack;
}
public static boolean isStackable(ItemStack itemStack) {
return getItemStackMaxCount(itemStack) > 1 && (!itemStack.isDamageableItem() || !itemStack.isDamaged());
}
private interface ItemUtil {
boolean isEnabled();
boolean tryStackItems(ItemEntity self, ItemEntity other);
// number -> modified count, -1 -> I don't care
int getMaxServerStackCount(ItemStack stack);
// number -> modified count, -1 -> I don't care
default int getMaxClientStackCount(ItemStack stack) {
return getMaxServerStackCount(stack);
}
}
private static class ShulkerBox implements ItemUtil {
public static boolean shulkerBoxCheck(@NotNull ItemStack stack1, @NotNull ItemStack stack2) {
if (ContainerExpansionConfig.nbtShulkerStackable) {
return Objects.equals(stack1.getComponents(), stack2.getComponents());
}
return shulkerBoxNoItem(stack1) && shulkerBoxNoItem(stack2) && Objects.equals(stack1.getComponents(), stack2.getComponents());
}
public static boolean shulkerBoxNoItem(@NotNull ItemStack stack) {
return stack.getComponents().getOrDefault(DataComponents.CONTAINER, ItemContainerContents.EMPTY).stream().findAny().isEmpty();
}
@Override
public boolean isEnabled() {
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)
) {
return false;
}
ItemStack otherStack = other.getItem();
if (selfStack.getItem() == otherStack.getItem()
&& shulkerBoxCheck(selfStack, otherStack)
&& selfStack.getCount() != ContainerExpansionConfig.shulkerCount) {
int amount = Math.min(otherStack.getCount(), ContainerExpansionConfig.shulkerCount - selfStack.getCount());
selfStack.grow(amount);
self.setItem(selfStack);
self.pickupDelay = Math.max(other.pickupDelay, self.pickupDelay);
self.age = Math.min(other.getAge(), self.age);
otherStack.shrink(amount);
if (otherStack.isEmpty()) {
other.discard();
} else {
other.setItem(otherStack);
}
return true;
}
return false;
}
@Override
public int getMaxServerStackCount(ItemStack stack) {
if (isEnabled() && stack.getItem() instanceof BlockItem bi &&
bi.getBlock() instanceof ShulkerBoxBlock && (ContainerExpansionConfig.nbtShulkerStackable || shulkerBoxNoItem(stack))) {
return ContainerExpansionConfig.shulkerCount;
}
return -1;
}
}
// only use shulker box part
/* public static class CurseEnchantedBook implements ItemUtil {
public static boolean isCursedEnchantedBook(ItemStack stack) {
ItemEnchantments enchantments = stack.getOrDefault(DataComponents.STORED_ENCHANTMENTS, ItemEnchantments.EMPTY);
if (enchantments.size() != 1) {
return false;
}
return stack.getBukkitStack().getEnchantmentLevel(org.bukkit.enchantments.Enchantment.BINDING_CURSE) == 1 ||
stack.getBukkitStack().getEnchantmentLevel(org.bukkit.enchantments.Enchantment.BINDING_CURSE) == 1;
}
@Override
public boolean isEnabled() {
return LeavesConfig.modify.oldMC.allowGrindstoneOverstacking;
}
@Override
public boolean tryStackItems(ItemEntity self, ItemEntity other) {
return false;
}
@Override
public int getMaxServerStackCount(ItemStack stack) {
return -1;
}
@Override
public int getMaxClientStackCount(ItemStack stack) {
if (isEnabled() && isCursedEnchantedBook(stack)) {
return 2;
}
return -1;
}
}*/
}
@@ -0,0 +1,46 @@
/*
* 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.util;
import net.minecraft.core.RegistryAccess;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.ProblemReporter;
import net.minecraft.world.level.storage.TagValueInput;
import net.minecraft.world.level.storage.TagValueOutput;
public class TagFactory {
private static final RegistryAccess.Frozen registryAccess = MinecraftServer.getServer().registryAccess();
public static TagValueOutput output() {
return TagValueOutput.createWithContext(ProblemReporter.DISCARDING, registryAccess);
}
public static TagValueOutput output(CompoundTag tag) {
return TagValueOutput.createWrappingWithContext(ProblemReporter.DISCARDING, registryAccess, tag);
}
public static TagValueInput input() {
return input(new CompoundTag());
}
public static TagValueInput input(CompoundTag tag) {
return (TagValueInput) TagValueInput.create(ProblemReporter.DISCARDING, registryAccess, tag);
}
}
@@ -0,0 +1,88 @@
/*
* 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.util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.storage.TagValueInput;
import net.minecraft.world.level.storage.TagValueOutput;
import org.jetbrains.annotations.Nullable;
public class TagUtil {
public static CompoundTag saveEntity(@Nullable Entity entity) {
if (entity == null) {
return new CompoundTag();
}
TagValueOutput output = TagFactory.output();
entity.save(output);
return output.buildResult();
}
public static boolean saveEntity(Entity entity, CompoundTag tag) {
if (entity == null) {
return false;
}
TagValueOutput output = TagFactory.output(tag);
return entity.save(output);
}
public static CompoundTag saveEntityWithoutId(Entity entity) {
if (entity == null) {
return new CompoundTag();
}
TagValueOutput output = TagFactory.output();
entity.saveWithoutId(output);
return output.buildResult();
}
public static CompoundTag saveTileWithId(@Nullable BlockEntity entity) {
if (entity == null) {
return new CompoundTag();
}
TagValueOutput output = TagFactory.output();
entity.saveWithId(output);
return output.buildResult();
}
public static boolean saveEntityAsPassenger(@Nullable Entity entity, CompoundTag tag) {
if (entity == null) {
return false;
}
TagValueOutput output = TagFactory.output(tag);
return entity.saveAsPassenger(output);
}
public static void loadEntity(Entity entity, CompoundTag tag) {
if (entity == null) {
return;
}
TagValueInput input = TagFactory.input(tag);
entity.load(input);
}
public static void loadTileWithComponents(BlockEntity entity, CompoundTag tag) {
if (entity == null) {
return;
}
TagValueInput input = TagFactory.input(tag);
entity.loadWithComponents(input);
}
}
+1 -1
View File
@@ -1,3 +1,3 @@
Special thanks (Logo author):
Helvetica Volubi <suisuroru@blue-millennium.fun>
bdlvl <bdlvl@mc-mygo.top>
bdlvl <bdlvl@blendreams.cn>
+5 -3
View File
@@ -13,14 +13,15 @@ release_tag="$mcversion-$commitid"
jarName="$project_id-$mcversion-paperclip.jar"
jarName_dir="lophine-server/build/libs/$jarName"
flag_release=false
pre=false
make_latest=false
if [ "$release" = "1" ]; then
pre=true
make_latest=false
flag_release=true
make_latest=true
elif [ "$release" = "2" ]; then
pre=false
flag_release=true
make_latest=true
fi
@@ -35,4 +36,5 @@ echo "pre=$pre" >> $GITHUB_ENV
echo "tag=$release_tag" >> $GITHUB_ENV
echo "jar=$jarName" >> $GITHUB_ENV
echo "jar_dir=$jarName_dir" >> $GITHUB_ENV
echo "flag_release=$flag_release" >> $GITHUB_ENV
echo "make_latest=$make_latest" >> $GITHUB_ENV