Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d09469e84b | |||
| a5e56ff2ca | |||
| e38108c99b | |||
| ab31ebc674 | |||
| 87ba8b9746 | |||
| e71b36e331 | |||
| cfb1ad88f4 | |||
| b40276f037 |
@@ -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 中。
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
|
||||
@@ -13,21 +13,27 @@
|
||||
[English](./README_EN.md) | **中文**
|
||||
|
||||
## 特性
|
||||
- 支持部分Bukkit插件
|
||||
- 可配置的原版特性
|
||||
- Tpsbar 支持
|
||||
- 对单线程区域性能的优化
|
||||
- 更多有用的功能
|
||||
|
||||
- 支持部分Bukkit插件
|
||||
- 可配置的原版特性
|
||||
- Tpsbar 支持
|
||||
- 对单线程区域性能的优化
|
||||
- 更多有用的功能
|
||||
|
||||
## 下载
|
||||
|
||||
任何版本都可以在 [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 +68,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
|
||||
|
||||

|
||||
|
||||
## 特别感谢
|
||||
|
||||
<b>感谢[LegacyLands](https://github.com/LegacyLands)对本项目的赞助,如果你想开发一些跨folia/非folia平台的插件,[legacy-lands-library](https://github.com/LegacyLands/legacy-lands-library/)将会是个不错的lib</b>
|
||||
|
||||

|
||||
|
||||
## 请给我们一个 ⭐Star!
|
||||
|
||||
> 你的每一个免费的 ⭐Star 就是我们每一个前进的动力。
|
||||
|
||||
<a href="https://star-history.com/#LuminolMC/Luminol&LuminolMC/LightingLuminol&LuminolMC/Lophine&Date">
|
||||
|
||||
+19
-6
@@ -2,7 +2,8 @@
|
||||
|
||||
# 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>
|
||||
|
||||
[](LICENSE.md)
|
||||
[](https://github.com/LuminolMC/Lophine/issues)
|
||||
@@ -13,21 +14,27 @@
|
||||
**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 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 +69,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
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
|
||||

|
||||
|
||||
## 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
@@ -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
@@ -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
@@ -4,7 +4,7 @@ mcVersion=1.21.8
|
||||
release=2
|
||||
# 0 for skip release, 1 for pre-release, 2 for release
|
||||
|
||||
luminolRef=059a38cc66981740d5055dca76e156dd5f1d8162
|
||||
luminolRef=a86ef6c5283307c6a9176115b652d6ebbaaa2aa3
|
||||
|
||||
org.gradle.configuration-cache=true
|
||||
org.gradle.caching=true
|
||||
|
||||
+71
-70
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+4
-4
@@ -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 e8c9e739de35313c0212bab97a202fa362fd0887..c990e80f16d03967c9bb6fc5f48f145c01948209 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) {
|
||||
@@ -2223,9 +2224,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 5c21fc0c749973d17df690fe7e2eab597b198131..8aea58c7797940929944973380b6ab67f177fa56 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);
|
||||
@@ -1522,6 +1525,7 @@ public abstract class PlayerList {
|
||||
serverPlayer.connection.send(clientboundUpdateRecipesPacket);
|
||||
serverPlayer.getRecipeBook().sendInitialRecipeBook(serverPlayer);
|
||||
}
|
||||
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleDataPackReload(); // Leaves - protocol core
|
||||
}
|
||||
|
||||
public boolean isAllowCommandsForAllPlayers() {
|
||||
+1
-1
@@ -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 68f0023c4234585a8385a830ed410883c8bd1549..d017cfc5b97552388d8f9ff80ca31b0972b80a2e 100644
|
||||
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
@@ -856,7 +856,7 @@ public class ServerGamePacketListenerImpl
|
||||
+1
-1
@@ -41,7 +41,7 @@ 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 d017cfc5b97552388d8f9ff80ca31b0972b80a2e..a684f5edf420d9655046de872c54e0c1a89103cf 100644
|
||||
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
@@ -3078,7 +3078,7 @@ public class ServerGamePacketListenerImpl
|
||||
@@ -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 87377bba2ebdc0eba5b7b212d971994c35ca0b62..94d0c7bd2dfc2620a67035ce9084e9da219e6509 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() {
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
|
||||
Date: Sat, 2 Aug 2025 12:40:53 +0800
|
||||
Subject: [PATCH] Leaves: Temporarily fix endermite spawn position
|
||||
|
||||
Should be removed when [https://github.com/PaperMC/Paper/pull/12912] merged
|
||||
|
||||
Co-authored by: Lumine1909 <133463833Lumine1909@users.noreply.github.com>
|
||||
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/4c4712d3304db7e977324b2bd6739f0c4433f80a/leaves-server/minecraft-patches/features/0140-Fix-endermite-spawn-position.patch)
|
||||
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
diff --git a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
|
||||
index bded685ed1653e9a1e2d7d4a9c4f68da58a6b06e..7123e56d86db01073e8ea71ccce0f6308070c8ab 100644
|
||||
--- a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
|
||||
+++ b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
|
||||
@@ -204,6 +204,9 @@ public class ThrownEnderpearl extends ThrowableItemProjectile {
|
||||
Vec3 vec3 = this.oldPosition();
|
||||
if (owner instanceof ServerPlayer serverPlayer) {
|
||||
if (serverPlayer.connection.isAcceptingMessages()) {
|
||||
+ // Paper start - fix endermite spawn position
|
||||
+ double x = serverPlayer.getX(), y = serverPlayer.getY(), z = serverPlayer.getZ();
|
||||
+ float yaw = serverPlayer.getYRot(), pitch = serverPlayer.getXRot();
|
||||
// CraftBukkit start
|
||||
ServerPlayer serverPlayer1 = serverPlayer.teleport(new TeleportTransition(serverLevel, vec3, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.ENDER_PEARL));
|
||||
if (serverPlayer1 == null) {
|
||||
@@ -214,7 +217,8 @@ public class ThrownEnderpearl extends ThrowableItemProjectile {
|
||||
if (this.random.nextFloat() < 0.05F && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) {
|
||||
Endermite endermite = EntityType.ENDERMITE.create(serverLevel, EntitySpawnReason.TRIGGERED);
|
||||
if (endermite != null) {
|
||||
- endermite.snapTo(owner.getX(), owner.getY(), owner.getZ(), owner.getYRot(), owner.getXRot());
|
||||
+ endermite.snapTo(x, y, z, yaw, pitch);
|
||||
+ // Paper end - fix endermite spawn position
|
||||
serverLevel.addFreshEntity(endermite, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.ENDER_PEARL);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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 8aea58c7797940929944973380b6ab67f177fa56..9c4e5789f6881ca300cc183294e50b03dd03f6c6 100644
|
||||
--- a/net/minecraft/server/players/PlayerList.java
|
||||
+++ b/net/minecraft/server/players/PlayerList.java
|
||||
@@ -1086,6 +1086,10 @@ public abstract class PlayerList {
|
||||
@@ -1089,6 +1089,10 @@ public abstract class PlayerList {
|
||||
player.getBukkitEntity().recalculatePermissions(); // CraftBukkit
|
||||
this.server.getCommands().sendCommands(player);
|
||||
} // Paper - Add sendOpLevel API
|
||||
@@ -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 a684f5edf420d9655046de872c54e0c1a89103cf..105675bef05408bd5baf1b6129c0392065bbe79d 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
|
||||
+4
-1
@@ -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 44b7ac045c028f3bc940d78f158079e20582f4b8..bdcd25b98fa8189af874e4715db7d4440752756e 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||||
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||||
@@ -496,6 +496,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) {
|
||||
@@ -1112,6 +1113,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;
|
||||
|
||||
+4
-2
@@ -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
|
||||
+25
@@ -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;
|
||||
|
||||
|
||||
+46
@@ -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;
|
||||
|
||||
|
||||
+2
-2
@@ -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,9 @@
|
||||
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) {
|
||||
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
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,21 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
+383
@@ -0,0 +1,383 @@
|
||||
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,85 @@
|
||||
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,71 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+278
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* 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.bukkit.Bukkit;
|
||||
import org.leavesmc.leaves.plugin.MinecraftInternalPlugin;
|
||||
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) {
|
||||
Bukkit.getGlobalRegionScheduler().run(MinecraftInternalPlugin.INSTANCE, (task) -> {
|
||||
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) {
|
||||
Bukkit.getGlobalRegionScheduler().run(MinecraftInternalPlugin.INSTANCE, (task) -> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
* 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.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 HashMap<DataLogger.Type, Tag> DATA = new HashMap<>();
|
||||
|
||||
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) {
|
||||
DATA.clear();
|
||||
LOGGERS.forEach((type, logger) -> {
|
||||
if (!isLoggerTypeEnabled(type)) {
|
||||
return;
|
||||
}
|
||||
DATA.put(type, logger.getResult(server));
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
if (DATA.containsKey(type)) {
|
||||
nbt.put(type.getSerializedName(), DATA.get(type));
|
||||
}
|
||||
}
|
||||
sendPacket(player, new HudDataPayload(HudDataPayloadType.PACKET_S2C_DATA_LOGGER_TICK, nbt));
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
+419
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+281
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+62
@@ -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);
|
||||
}
|
||||
}
|
||||
+442
@@ -0,0 +1,442 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+104
@@ -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;
|
||||
}
|
||||
}
|
||||
+118
@@ -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;
|
||||
}
|
||||
}
|
||||
+62
@@ -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();
|
||||
}
|
||||
+90
@@ -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;
|
||||
}
|
||||
}
|
||||
+91
@@ -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;
|
||||
}
|
||||
}
|
||||
+296
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
+55
@@ -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
|
||||
}
|
||||
}
|
||||
+36
@@ -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;
|
||||
}
|
||||
}
|
||||
+111
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -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
|
||||
}
|
||||
+211
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -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);
|
||||
}
|
||||
}
|
||||
+92
@@ -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;
|
||||
}
|
||||
}
|
||||
+149
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
+40
@@ -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;
|
||||
}
|
||||
}
|
||||
+168
@@ -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() + "]";
|
||||
}
|
||||
}
|
||||
|
||||
+440
@@ -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());
|
||||
}
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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 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.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.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);
|
||||
|
||||
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) {
|
||||
try {
|
||||
return (CompoundTag) TickData.CODEC.encodeStart(server.registryAccess().createSerializationContext(NbtOps.INSTANCE), this.build(server)).getOrThrow();
|
||||
} catch (Exception e) {
|
||||
return new CompoundTag();
|
||||
}
|
||||
}
|
||||
|
||||
private TickData build(MinecraftServer server) {
|
||||
ServerTickRateManager tickManager = server.tickRateManager();
|
||||
boolean frozen = tickManager.isFrozen();
|
||||
boolean sprinting = tickManager.isSprinting();
|
||||
final double mspt = server.tickTimes5s.getAverage();
|
||||
double tps = 1000.0D / Math.max(sprinting ? 0.0 : tickManager.millisecondsPerTick(), mspt);
|
||||
|
||||
if (frozen) {
|
||||
tps = 0.0d;
|
||||
}
|
||||
|
||||
return new TickData(
|
||||
mspt, tps,
|
||||
tickManager.getRemainingSprintTicks(),
|
||||
frozen, sprinting,
|
||||
tickManager.isSteppingForward()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
+35
@@ -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,3 +1,20 @@
|
||||
/*
|
||||
* 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 fun.bm.lophine.config.modules.misc.ContainerExpansionConfig;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+5
-3
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user