Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d57a67913 | |||
| ecc514b340 | |||
| 75baf3dfaf | |||
| 4ed8600511 | |||
| 0160aac4c6 | |||
| cbed24020a | |||
| 682029a451 | |||
| f3334ae6ec | |||
| df0e0eb82b | |||
| d09469e84b | |||
| a5e56ff2ca | |||
| e38108c99b | |||
| ab31ebc674 | |||
| 87ba8b9746 | |||
| e71b36e331 | |||
| cfb1ad88f4 | |||
| b40276f037 | |||
| e1b3a14ece | |||
| c257ebd19c | |||
| 52b6803d68 | |||
| 71f83be588 | |||
| 5733cc4e35 | |||
| a8147bb171 | |||
| 17f0269ee6 | |||
| f574f277eb | |||
| 94e7c4e822 | |||
| 01582648ee | |||
| db9bd6e469 | |||
| 0eebea7a21 | |||
| 1707673c8d | |||
| 7179463c36 |
@@ -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 中。
|
||||
|
||||
+17
-15
@@ -1,10 +1,12 @@
|
||||
name: Lophine CI - ver/1.21.7
|
||||
name: Lophine CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "ver/1.21.7" ]
|
||||
branches:
|
||||
- "**"
|
||||
pull_request:
|
||||
branches: [ "ver/1.21.7" ]
|
||||
branches:
|
||||
- "**"
|
||||
|
||||
permissions: write-all
|
||||
|
||||
@@ -20,7 +22,7 @@ jobs:
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: zulu
|
||||
java-version: 22
|
||||
java-version: 21
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
@@ -32,18 +34,10 @@ jobs:
|
||||
run: git config --global user.email "ci@luminolmc.com" && git config --global user.name "LuminolMC CI"
|
||||
|
||||
- name: Apply All Patches
|
||||
run: ./gradlew applyAllPatches
|
||||
run: ./gradlew --refresh-dependencies applyAllPatches
|
||||
|
||||
- name: CreateMojmapPaperclipJar
|
||||
run: ./gradlew createMojmapPaperclipJar
|
||||
|
||||
- name: Publish to repo
|
||||
if: github.event_name != 'pull_request' && false # do not push to any repo
|
||||
continue-on-error: true
|
||||
run: ./gradlew generateDevelopmentBundle publish -PpublishDevBundle=true
|
||||
env:
|
||||
PRIVATE_MAVEN_REPO_PASSWORD: ${{ secrets.PRIVATE_MAVEN_REPO_PASSWORD }}
|
||||
PRIVATE_MAVEN_REPO_USERNAME: ${{ secrets.PRIVATE_MAVEN_REPO_USERNAME }}
|
||||
run: ./gradlew --refresh-dependencies createMojmapPaperclipJar
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -55,8 +49,16 @@ jobs:
|
||||
if: github.event_name != 'pull_request'
|
||||
run: sh scripts/SetENV.sh
|
||||
|
||||
- name: Publish to repo
|
||||
if: github.event_name != 'pull_request' && env.flag_release == 'true' && false # do not push to any repo
|
||||
continue-on-error: true
|
||||
run: ./gradlew --refresh-dependencies generateDevelopmentBundle publish -PpublishDevBundle=true
|
||||
env:
|
||||
PRIVATE_MAVEN_REPO_PASSWORD: ${{ secrets.PRIVATE_MAVEN_REPO_PASSWORD }}
|
||||
PRIVATE_MAVEN_REPO_USERNAME: ${{ secrets.PRIVATE_MAVEN_REPO_USERNAME }}
|
||||
|
||||
- name: Create Release
|
||||
if: github.event_name != 'pull_request'
|
||||
if: github.event_name != 'pull_request' && env.flag_release == 'true'
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
tag: ${{ env.tag }}
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
<h4>Lophine 是一个基于Luminol的分支,具有许多有用的优化和可配置的原版特性,目标是在Folia上实现更多生电的实现</h4>
|
||||
|
||||

|
||||
[](LICENSE.md)
|
||||
[](https://github.com/LuminolMC/Lophine/issues)
|
||||

|
||||
@@ -13,21 +14,29 @@
|
||||
[English](./README_EN.md) | **中文**
|
||||
|
||||
## 特性
|
||||
- 支持部分Bukkit插件
|
||||
- 可配置的原版特性
|
||||
- Tpsbar 支持
|
||||
- 对单线程区域性能的优化
|
||||
- 更多有用的功能
|
||||
|
||||
- 支持部分Bukkit插件
|
||||
- 可配置的原版特性
|
||||
- Tpsbar 支持
|
||||
- 针对folia bug的一些修复
|
||||
- linear和b_linear(linear的重新实现)存档格式支持
|
||||
- 对单线程区域性能的优化
|
||||
- 更多有用的功能
|
||||
|
||||
## 下载
|
||||
|
||||
任何版本都可以在 [Release](https://github.com/LuminolMC/Lophine/releases), 中找到,你也可以通过[以下步骤](./README.md#构建)自己构建。
|
||||
|
||||
## 构建
|
||||
|
||||
要构建一个paperclip jar,你需要运行以下命令。你可以在lophine-server/build/libs中找到jar(注意:需要`JDK21`)
|
||||
|
||||
```shell
|
||||
./gradlew applyAllPatches && ./gradlew createMojmapPaperclipJar
|
||||
```
|
||||
|
||||
## 使用API
|
||||
|
||||
使用 Gradle:
|
||||
|
||||
```kotlin
|
||||
@@ -62,26 +71,32 @@ dependencies {
|
||||
```
|
||||
|
||||
## 联系方式
|
||||
|
||||
> 如果您对这个项目感兴趣或有任何问题,请随时向我们提问。
|
||||
|
||||
**QQ群: [1015048616](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=QML5kIVsniPi1PlZvnjHQT_02EHsZ5Jc&authKey=%2FTCJsZC7JFQ9sxAroPCKuYnlV57Z5fyqp36ewXZk3Sn4iJ9p4MB1JKdc%2FFcX3HOM&noverify=0&group_code=1015048616)** | QQ频道: [点击加入](https://pd.qq.com/s/eq9krf9j) | Telegram: [点击加入](https://t.me/LuminolMinecraft) | Discord: [点击加入](https://discord.gg/Qd7m3V6eDx)
|
||||
|
||||
|
||||
## 关于 Issue
|
||||
|
||||
当您遇到任何问题时,请向我们提问,我们将尽力解决,但请记得清楚地描述您的问题并提供足够的日志等信息。
|
||||
|
||||
## 贡献代码
|
||||
|
||||
可查看 [Contributing](./docs/CONTRIBUTING.md)
|
||||
|
||||
## BStats
|
||||
|
||||

|
||||
|
||||
## 特别感谢
|
||||
|
||||
<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">
|
||||
|
||||
+22
-6
@@ -2,8 +2,10 @@
|
||||
|
||||
# Lophine
|
||||
|
||||
<h4>LightingLuminol is a Luminol fork with many useful optimizations and configurable vanilla features, aims to provide more function for survival-usable circuit on folia</h4>
|
||||
<h4>Lophine is a Luminol fork with many useful optimizations and configurable vanilla features, aims to provide
|
||||
more function for survival-usable circuit on folia</h4>
|
||||
|
||||

|
||||
[](LICENSE.md)
|
||||
[](https://github.com/LuminolMC/Lophine/issues)
|
||||

|
||||
@@ -13,21 +15,29 @@
|
||||
**English** | [中文](./README.md)
|
||||
|
||||
## Features
|
||||
- Supported some Bukkit plugin
|
||||
- Configurable vanilla features
|
||||
- Tpsbar support
|
||||
- Useful optimizations to improve the performance of single threaded region
|
||||
- More functions
|
||||
|
||||
- Supported some Bukkit plugin
|
||||
- Configurable vanilla features
|
||||
- Tpsbar support
|
||||
- Useful fixes of bugs of folia
|
||||
- Linear and BufferedLinear(AKA. b_linear)(a reimplementation of linear) region format support
|
||||
- Useful optimizations to improve the performance of single threaded region
|
||||
- More functions
|
||||
|
||||
## Download
|
||||
|
||||
Any versions are available in the [release](https://github.com/LuminolMC/Lophine/releases), also you can build it by yourself through [the following steps](./README_EN.md#Build).
|
||||
|
||||
## Build
|
||||
|
||||
To build a paperclip jar, you need to run the following command. You can find the jar in lophine-server/build/libs(Note: `JDK21` is needed)
|
||||
|
||||
```shell
|
||||
./gradlew applyAllPatches && ./gradlew createMojmapPaperclipJar
|
||||
```
|
||||
|
||||
## Using API
|
||||
|
||||
For gradle:
|
||||
|
||||
```kotlin
|
||||
@@ -62,25 +72,31 @@ For maven
|
||||
```
|
||||
|
||||
## Contact
|
||||
|
||||
> If you are interested in this project or have any issue, feel free to ask us.
|
||||
|
||||
**QQ Group: [1015048616](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=hTPlI5j6XB8pgk4sdx6RkjhBPGG1r4IR&authKey=pnu6uCKQP7Sja2CJWC15Qi3BeI%2FAsh8tU4m5muufMBjbB3zz%2BwHBZCTRRdSNKhld&noverify=0&group_code=1015048616)** | QQ Channel: [Click To Join](https://pd.qq.com/s/eq9krf9j) | Telegram: [Click To Join](https://t.me/LuminolMinecraft) | Discord: [Click To Join](https://discord.gg/Qd7m3V6eDx)
|
||||
|
||||
## About Issue
|
||||
|
||||
When you meet any problems, just ask us, we will do our best to solve it, but remember to state your problem clear and provide enough logs etc.</br>
|
||||
|
||||
## Pull Requests
|
||||
|
||||
See [Contributing](./docs/CONTRIBUTING_EN.md)
|
||||
|
||||
## BStats
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||
|
||||
+5
-3
@@ -1,8 +1,10 @@
|
||||
group=fun.bm.lophine
|
||||
version=1.21.7-R0.1-SNAPSHOT
|
||||
mcVersion=1.21.7
|
||||
version=1.21.8-R0.1-SNAPSHOT
|
||||
mcVersion=1.21.8
|
||||
release=2
|
||||
# 0 for skip release, 1 for pre-release, 2 for release
|
||||
|
||||
luminolRef=5ec3986e676e309dd9a406693f1e396c941c18f0
|
||||
luminolRef=f8bd34a2dff31a137b0808b83b7503a22fcd0527
|
||||
|
||||
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
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
// Luminol start - Dependenices insert
|
||||
implementation("com.electronwill.night-config:toml:3.8.2") // Night config
|
||||
implementation("com.github.luben:zstd-jni:1.5.4-1")
|
||||
@@ -264,14 +_,14 @@
|
||||
@@ -276,14 +_,14 @@
|
||||
val gitBranch = git.exec(providers, "rev-parse", "--abbrev-ref", "HEAD").get().trim()
|
||||
attributes(
|
||||
"Main-Class" to "org.bukkit.craftbukkit.Main",
|
||||
@@ -62,7 +62,7 @@
|
||||
- "Specification-Title" to "Luminol",
|
||||
+ "Specification-Title" to "Lophine",
|
||||
"Specification-Version" to project.version,
|
||||
"Specification-Vendor" to "Luminol Team",
|
||||
"Specification-Vendor" to "LuminolMC org",
|
||||
- "Brand-Id" to "luminolmc:luminol",
|
||||
- "Brand-Name" to "Luminol",
|
||||
+ "Brand-Id" to "luminolmc:lophine",
|
||||
@@ -70,3 +70,12 @@
|
||||
"Build-Number" to (build ?: ""),
|
||||
"Build-Time" to buildTime.toString(),
|
||||
"Git-Branch" to gitBranch,
|
||||
@@ -447,7 +_,7 @@
|
||||
}
|
||||
|
||||
fill {
|
||||
- project("luminol")
|
||||
+ project("lophine")
|
||||
versionFamily(paperweight.minecraftVersion.map { it.split(".", "-").takeWhile { part -> part.toIntOrNull() != null }.take(2).joinToString(".") })
|
||||
version(paperweight.minecraftVersion)
|
||||
|
||||
|
||||
@@ -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 a67fc80eb3182415557b4bdf7f146aefb43103da..52fec4c88fee3c027c954365538516aaa8335c7f 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 {
|
||||
@@ -21,6 +21,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
|
||||
public static void initConfigs() {
|
||||
configfiles.put("luminol", ConfigsInstance.of(new File("luminol_config"), "luminol", "me.earthme.luminol.config.modules"));
|
||||
+ configfiles.put("lophine", ConfigsInstance.of(new File("lophine_config"), "lophine", "fun.bm.lophine.config.modules")); // add lophine config to global config
|
||||
preLoad();
|
||||
}
|
||||
|
||||
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,30 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
|
||||
Date: Mon, 28 Jul 2025 23:02:47 +0800
|
||||
Subject: [PATCH] Transformed Configs
|
||||
|
||||
|
||||
diff --git a/src/main/java/me/earthme/luminol/config/modules/experiment/CommandBlockExecutionConfig.java b/src/main/java/me/earthme/luminol/config/modules/experiment/CommandBlockExecutionConfig.java
|
||||
index 531214d7ab28ece4bf84e82b39d1c9f983f0fb0c..b68fa0427bb975e91e76f73ab4cc089a424937a3 100644
|
||||
--- a/src/main/java/me/earthme/luminol/config/modules/experiment/CommandBlockExecutionConfig.java
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/experiment/CommandBlockExecutionConfig.java
|
||||
@@ -5,6 +5,7 @@ import me.earthme.luminol.config.IConfigModule;
|
||||
import me.earthme.luminol.config.flags.ConfigInfo;
|
||||
|
||||
public class CommandBlockExecutionConfig implements IConfigModule {
|
||||
+ @me.earthme.luminol.config.flags.TransformedConfig(name = "command_block_enabled", category = {"experiment", "command"}, originInstance = "lophine")
|
||||
@ConfigInfo(baseName = "enabled")
|
||||
public static boolean enabled = false;
|
||||
|
||||
diff --git a/src/main/java/me/earthme/luminol/config/modules/misc/PaperPacketLimiterConfig.java b/src/main/java/me/earthme/luminol/config/modules/misc/PaperPacketLimiterConfig.java
|
||||
index c269446dda05cfc848407f80f53820c8fcd0c7b4..e69ffae53aa24496a29a4c4890cdcb366d2bec12 100644
|
||||
--- a/src/main/java/me/earthme/luminol/config/modules/misc/PaperPacketLimiterConfig.java
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/PaperPacketLimiterConfig.java
|
||||
@@ -5,6 +5,7 @@ import me.earthme.luminol.config.IConfigModule;
|
||||
import me.earthme.luminol.config.flags.ConfigInfo;
|
||||
|
||||
public class PaperPacketLimiterConfig implements IConfigModule {
|
||||
+ @me.earthme.luminol.config.flags.TransformedConfig(name = "unlimit-packet", category = {"misc", "network"}, originInstance = "lophine")
|
||||
@ConfigInfo(baseName = "force_disable")
|
||||
public static boolean forceDisable = false;
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
|
||||
Date: Wed, 13 Aug 2025 22:23:46 +0800
|
||||
Subject: [PATCH] Leaves: Item overstack util
|
||||
|
||||
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
|
||||
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/d93e9766d3797d130f66179e70a1b374c546fef7/leaves-server/src/main/java/org/leavesmc/leaves/util/ItemOverstackUtils.java)
|
||||
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
diff --git a/src/main/java/org/leavesmc/leaves/util/ItemOverstackUtils.java b/src/main/java/org/leavesmc/leaves/util/ItemOverstackUtils.java
|
||||
index 5995c499298454a060433746c64be5fcc8d202e2..3452399de0fa464ce04a7cf1a05b440346bef662 100644
|
||||
--- a/src/main/java/org/leavesmc/leaves/util/ItemOverstackUtils.java
|
||||
+++ b/src/main/java/org/leavesmc/leaves/util/ItemOverstackUtils.java
|
||||
@@ -17,29 +17,30 @@
|
||||
|
||||
package org.leavesmc.leaves.util;
|
||||
|
||||
+import fun.bm.lophine.config.modules.misc.ContainerExpansionConfig;
|
||||
//import io.papermc.paper.datacomponent.DataComponentTypes;
|
||||
//import io.papermc.paper.datacomponent.item.ItemEnchantments;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
-//import net.minecraft.world.item.BlockItem;
|
||||
+import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
-//import net.minecraft.world.item.component.ItemContainerContents;
|
||||
-//import net.minecraft.world.level.block.ShulkerBoxBlock;
|
||||
+import net.minecraft.world.item.component.ItemContainerContents;
|
||||
+import net.minecraft.world.level.block.ShulkerBoxBlock;
|
||||
//import org.bukkit.enchantments.Enchantment;
|
||||
-//import org.jetbrains.annotations.NotNull;
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
//import org.leavesmc.leaves.LeavesConfig;
|
||||
|
||||
import java.util.List;
|
||||
-//import java.util.Objects;
|
||||
+import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ItemOverstackUtils {
|
||||
|
||||
private static final List<ItemUtil> overstackUtils = List.of(
|
||||
-/* new ShulkerBox(),
|
||||
- new CurseEnchantedBook()*/
|
||||
+ new ShulkerBox()
|
||||
+ // new CurseEnchantedBook() // only use shulker box part
|
||||
);
|
||||
|
||||
public static int getItemStackMaxCount(ItemStack stack) {
|
||||
@@ -109,6 +110,7 @@ public class ItemOverstackUtils {
|
||||
|
||||
public static float getItemStackSignalStrength(int maxStackSize, ItemStack itemStack) {
|
||||
float result = (float) itemStack.getCount() / Math.min(maxStackSize, itemStack.getMaxStackSize());
|
||||
+ // only use shulker box part
|
||||
/* if (LeavesConfig.modify.oldMC.allowGrindstoneOverstacking && CurseEnchantedBook.isCursedEnchantedBook(itemStack)) {
|
||||
return result;
|
||||
}*/
|
||||
@@ -134,9 +136,9 @@ public class ItemOverstackUtils {
|
||||
}
|
||||
}
|
||||
|
||||
-/* private static class ShulkerBox implements ItemUtil {
|
||||
+ private static class ShulkerBox implements ItemUtil {
|
||||
public static boolean shulkerBoxCheck(@NotNull ItemStack stack1, @NotNull ItemStack stack2) {
|
||||
- if (LeavesConfig.modify.shulkerBox.sameNbtStackable) {
|
||||
+ if (ContainerExpansionConfig.nbtShulkerStackable) {
|
||||
return Objects.equals(stack1.getComponents(), stack2.getComponents());
|
||||
}
|
||||
return shulkerBoxNoItem(stack1) && shulkerBoxNoItem(stack2) && Objects.equals(stack1.getComponents(), stack2.getComponents());
|
||||
@@ -148,24 +150,24 @@ public class ItemOverstackUtils {
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
- return LeavesConfig.modify.shulkerBox.shulkerBoxStackSize > 1;
|
||||
+ return ContainerExpansionConfig.shulkerCount > 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryStackItems(ItemEntity self, ItemEntity other) {
|
||||
ItemStack selfStack = self.getItem();
|
||||
if (!isEnabled() ||
|
||||
- !(selfStack.getItem() instanceof BlockItem blockItem) ||
|
||||
- !(blockItem.getBlock() instanceof ShulkerBoxBlock)
|
||||
+ !(selfStack.getItem() instanceof BlockItem blockItem) ||
|
||||
+ !(blockItem.getBlock() instanceof ShulkerBoxBlock)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemStack otherStack = other.getItem();
|
||||
if (selfStack.getItem() == otherStack.getItem()
|
||||
- && shulkerBoxCheck(selfStack, otherStack)
|
||||
- && selfStack.getCount() != org.leavesmc.leaves.LeavesConfig.modify.shulkerBox.shulkerBoxStackSize) {
|
||||
- int amount = Math.min(otherStack.getCount(), org.leavesmc.leaves.LeavesConfig.modify.shulkerBox.shulkerBoxStackSize - selfStack.getCount());
|
||||
+ && shulkerBoxCheck(selfStack, otherStack)
|
||||
+ && selfStack.getCount() != ContainerExpansionConfig.shulkerCount) {
|
||||
+ int amount = Math.min(otherStack.getCount(), ContainerExpansionConfig.shulkerCount - selfStack.getCount());
|
||||
|
||||
selfStack.grow(amount);
|
||||
self.setItem(selfStack);
|
||||
@@ -187,14 +189,15 @@ public class ItemOverstackUtils {
|
||||
@Override
|
||||
public int getMaxServerStackCount(ItemStack stack) {
|
||||
if (isEnabled() && stack.getItem() instanceof BlockItem bi &&
|
||||
- bi.getBlock() instanceof ShulkerBoxBlock && (LeavesConfig.modify.shulkerBox.sameNbtStackable || shulkerBoxNoItem(stack))) {
|
||||
- return LeavesConfig.modify.shulkerBox.shulkerBoxStackSize;
|
||||
+ bi.getBlock() instanceof ShulkerBoxBlock && (ContainerExpansionConfig.nbtShulkerStackable || shulkerBoxNoItem(stack))) {
|
||||
+ return ContainerExpansionConfig.shulkerCount;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
- public static class CurseEnchantedBook implements ItemUtil {
|
||||
+// only use shulker box part
|
||||
+/* public static class CurseEnchantedBook implements ItemUtil {
|
||||
public static boolean isCursedEnchantedBook(ItemStack stack) {
|
||||
ItemEnchantments enchantments = stack.getBukkitStack().getData(DataComponentTypes.STORED_ENCHANTMENTS);
|
||||
if (enchantments == null || enchantments.enchantments().size() != 1) {
|
||||
+23
-2
@@ -4,8 +4,29 @@ Date: Mon, 7 Jul 2025 16:30:54 +0800
|
||||
Subject: [PATCH] Add config to allow infinite trade
|
||||
|
||||
|
||||
diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java
|
||||
index 79242037e868a140513f032e74e8594c0c6ff41c..8570c5089b605125680f8558ae201550df2d0a47 100644
|
||||
--- a/net/minecraft/world/entity/npc/Villager.java
|
||||
+++ b/net/minecraft/world/entity/npc/Villager.java
|
||||
@@ -490,8 +490,15 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
|
||||
}
|
||||
|
||||
private void resendOffersToTradingPlayer() {
|
||||
- MerchantOffers offers = this.getOffers();
|
||||
+ // Lophine start - infinite trade
|
||||
+ MerchantOffers offers = this.getOffers().copy();
|
||||
Player tradingPlayer = this.getTradingPlayer();
|
||||
+ if (fun.bm.lophine.config.modules.misc.VillagerConfig.villagerInfiniteTrade) {
|
||||
+ for (MerchantOffer merchantOffer : offers) {
|
||||
+ merchantOffer.maxUses = 524288;
|
||||
+ }
|
||||
+ }
|
||||
+ // Lophine end - infinite trade
|
||||
if (tradingPlayer != null && !offers.isEmpty()) {
|
||||
tradingPlayer.sendMerchantOffers(
|
||||
tradingPlayer.containerMenu.containerId,
|
||||
diff --git a/net/minecraft/world/item/trading/Merchant.java b/net/minecraft/world/item/trading/Merchant.java
|
||||
index 6e2fa442ddcd4ef3c4fe789b9ebac9dd409c9de7..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 {
|
||||
@@ -17,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
|
||||
+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
|
||||
+8
-57
@@ -1,14 +1,14 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
|
||||
Date: Tue, 29 Apr 2025 23:03:56 +0800
|
||||
Subject: [PATCH] Add config to enable Cross Region Damage trace
|
||||
Subject: [PATCH] Add config to enable cross region damage trace
|
||||
|
||||
|
||||
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
|
||||
index 06e41bca6b964ca71ea7bca547b03cdb3610ba31..b7bc75648634aefc549a69631ab6e401eb902e7d 100644
|
||||
index 11e6197dec541b28733715f0d7eaa4c7b834d632..6b7c92fdf562dfa46eee8e8acbc82b02fc3226c5 100644
|
||||
--- a/net/minecraft/server/level/ServerPlayer.java
|
||||
+++ b/net/minecraft/server/level/ServerPlayer.java
|
||||
@@ -1341,6 +1341,13 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
|
||||
@@ -1345,6 +1345,13 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
|
||||
this.awardStat(Stats.ENTITY_KILLED_BY.get(killCredit.getType()));
|
||||
killCredit.awardKillScore(this, cause);
|
||||
this.createWitherRose(killCredit);
|
||||
@@ -22,7 +22,7 @@ index 06e41bca6b964ca71ea7bca547b03cdb3610ba31..b7bc75648634aefc549a69631ab6e401
|
||||
}
|
||||
|
||||
this.level().broadcastEntityEvent(this, (byte)3);
|
||||
@@ -1364,6 +1371,24 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
|
||||
@@ -1368,6 +1375,24 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
|
||||
}
|
||||
// Leaves end - exp fix
|
||||
|
||||
@@ -48,40 +48,10 @@ index 06e41bca6b964ca71ea7bca547b03cdb3610ba31..b7bc75648634aefc549a69631ab6e401
|
||||
AABB aabb = new AABB(this.blockPosition()).inflate(32.0, 10.0, 32.0);
|
||||
this.level()
|
||||
diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
|
||||
index d55a6989fd68f0bcf7bd05e9420fda1115afa6c0..5369a6597e48ed9f0d8f7f0b9685ce4950df4fe5 100644
|
||||
index 567d63ae589705e13403814b36e35d00c241a69e..3f4b1c54f9207ba1854a8c8da965cbaca5ea517d 100644
|
||||
--- a/net/minecraft/world/entity/LivingEntity.java
|
||||
+++ b/net/minecraft/world/entity/LivingEntity.java
|
||||
@@ -1216,6 +1216,29 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
|
||||
}
|
||||
}
|
||||
|
||||
+ // Luminol Start - raid revert adapt Cross Region Damage trace
|
||||
+ public boolean addEffect(MobEffectInstance effectInstance, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause, boolean fireEvent, boolean async) {
|
||||
+ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(entity)) {
|
||||
+ return addEffect(effectInstance, entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN, true);
|
||||
+ } else if (fun.bm.lophine.config.modules.experiment.EntityDamageSourceTraceConfig.enabled) {
|
||||
+ postToEntityThreadAddEffect(effectInstance, entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN, true);
|
||||
+ return true;
|
||||
+ }
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ private void postToEntityThreadAddEffect(MobEffectInstance effectInstance, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause, boolean fireEvent) {
|
||||
+ if (entity != null)
|
||||
+ entity.getBukkitEntity().taskScheduler.schedule((Entity nmsEntity) -> {
|
||||
+ try {
|
||||
+ addEffect(effectInstance, nmsEntity, cause, fireEvent);
|
||||
+ } catch (Throwable ex) {
|
||||
+ LOGGER.error(ex.getMessage(), ex);
|
||||
+ }
|
||||
+ }, null, 1L );
|
||||
+ }
|
||||
+ // Luminol End - raid revert adapt Cross Region Damage trace
|
||||
+
|
||||
public boolean canBeAffected(MobEffectInstance effectInstance) {
|
||||
if (this.getType().is(EntityTypeTags.IMMUNE_TO_INFESTED)) {
|
||||
return !effectInstance.is(MobEffects.INFESTED);
|
||||
@@ -1948,6 +1971,13 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
|
||||
@@ -1948,6 +1948,13 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
|
||||
final LivingEntity killer = this.getKillCredit();
|
||||
if (killer != null) {
|
||||
killer.awardKillScore(this, damageSource);
|
||||
@@ -95,7 +65,7 @@ index d55a6989fd68f0bcf7bd05e9420fda1115afa6c0..5369a6597e48ed9f0d8f7f0b9685ce49
|
||||
}
|
||||
}); // Paper end
|
||||
this.postDeathDropItems(deathEvent); // Paper
|
||||
@@ -1958,6 +1988,18 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
|
||||
@@ -1958,6 +1965,18 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
|
||||
return deathEvent; // Paper
|
||||
}
|
||||
|
||||
@@ -114,7 +84,7 @@ index d55a6989fd68f0bcf7bd05e9420fda1115afa6c0..5369a6597e48ed9f0d8f7f0b9685ce49
|
||||
protected void dropEquipment(ServerLevel level) {
|
||||
}
|
||||
protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) {} // Paper - method for post death logic that cannot be ran before the event is potentially cancelled
|
||||
@@ -2556,6 +2598,18 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
|
||||
@@ -2556,6 +2575,18 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,22 +103,3 @@ index d55a6989fd68f0bcf7bd05e9420fda1115afa6c0..5369a6597e48ed9f0d8f7f0b9685ce49
|
||||
public final float getMaxHealth() {
|
||||
return (float)this.getAttributeValue(Attributes.MAX_HEALTH);
|
||||
}
|
||||
diff --git a/net/minecraft/world/entity/raid/Raider.java b/net/minecraft/world/entity/raid/Raider.java
|
||||
index 510514e363f4816a6e286fb9b5488153e03c9e55..4638af10539a316acae64fb26881c8e699c496e0 100644
|
||||
--- a/net/minecraft/world/entity/raid/Raider.java
|
||||
+++ b/net/minecraft/world/entity/raid/Raider.java
|
||||
@@ -157,7 +157,13 @@ public abstract class Raider extends PatrollingMonster {
|
||||
net.minecraft.world.effect.MobEffectInstance mobeffect1 = new net.minecraft.world.effect.MobEffectInstance(net.minecraft.world.effect.MobEffects.BAD_OMEN, fun.bm.lophine.config.modules.misc.RaidChangesConfig.infinite ? net.minecraft.world.effect.MobEffectInstance.INFINITE_DURATION : 120000, i, false, false, true);
|
||||
|
||||
if (!serverLevel.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_DISABLE_RAIDS)) {
|
||||
- entityhuman.addEffect(mobeffect1, entityhuman, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN, true); // CraftBukkit
|
||||
+ // Luminol start - Raid changes adapt DamageSource trace
|
||||
+ if (fun.bm.lophine.config.modules.experiment.EntityDamageSourceTraceConfig.enabled) {
|
||||
+ entityhuman.addEffect(mobeffect1, entityhuman, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN, true, true);
|
||||
+ } else {
|
||||
+ entityhuman.addEffect(mobeffect1, entityhuman, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN, true); // CraftBukkit
|
||||
+ }
|
||||
+ // Luminol end - Raid changes adapt DamageSource trace
|
||||
}
|
||||
this.setPatrolLeader(false);
|
||||
}
|
||||
+3
-3
@@ -5,7 +5,7 @@ Subject: [PATCH] Add config to enable datapack command
|
||||
|
||||
|
||||
diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java
|
||||
index 0d5ed9602492c8393088954105e9a19032067b8b..e2fc15d84fafde6847ecead23c55169449275e1b 100644
|
||||
index 8a90aa5c145f53afbb3f2371f97cb09974f30bbb..b6e10157c572dadb1bff18f8431166966eed46d9 100644
|
||||
--- a/net/minecraft/commands/Commands.java
|
||||
+++ b/net/minecraft/commands/Commands.java
|
||||
@@ -192,7 +192,9 @@ public class Commands {
|
||||
@@ -20,10 +20,10 @@ index 0d5ed9602492c8393088954105e9a19032067b8b..e2fc15d84fafde6847ecead23c551694
|
||||
DefaultGameModeCommands.register(this.dispatcher);
|
||||
//DialogCommand.register(this.dispatcher, context); // Folia - region threading - TODO
|
||||
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
||||
index 6675b83c9f15d0341dcf169ec3ac20f401c5d10b..e8c9e739de35313c0212bab97a202fa362fd0887 100644
|
||||
index 100e8fdc52c201d386e3c779d19f6b5abde39820..d08a5b553f4e5932d960fbcdeb6a43419730e32a 100644
|
||||
--- a/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/net/minecraft/server/MinecraftServer.java
|
||||
@@ -2349,6 +2349,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -2352,6 +2352,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
return this.reloadResources(selectedIds, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN);
|
||||
}
|
||||
|
||||
-207
@@ -1,207 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
|
||||
Date: Fri, 11 Apr 2025 16:53:57 +0800
|
||||
Subject: [PATCH] Add config to revert raid changes
|
||||
|
||||
Co-authored by: huanli233 <392352840@qq.com>
|
||||
Some of changes is a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/b5793e809b6346e16b7ea218496806a312562e27/leaves-server/minecraft-patches/features/0106-Revert-raid-changes.patch)
|
||||
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
diff --git a/net/minecraft/world/effect/BadOmenMobEffect.java b/net/minecraft/world/effect/BadOmenMobEffect.java
|
||||
index 80f17f33f670018240c854df589cf90cdeab6e70..985f27d249810bf133a520d937b7006e57f553ec 100644
|
||||
--- a/net/minecraft/world/effect/BadOmenMobEffect.java
|
||||
+++ b/net/minecraft/world/effect/BadOmenMobEffect.java
|
||||
@@ -22,6 +22,11 @@ class BadOmenMobEffect extends MobEffect {
|
||||
&& !serverPlayer.isSpectator()
|
||||
&& level.getDifficulty() != Difficulty.PEACEFUL
|
||||
&& level.isVillage(serverPlayer.blockPosition())) {
|
||||
+ // Leaves start - Revert raid changes
|
||||
+ if (fun.bm.lophine.config.modules.misc.RaidChangesConfig.trigger) {
|
||||
+ return level.getRaids().createOrExtendRaid(serverPlayer, serverPlayer.blockPosition()) == null;
|
||||
+ }
|
||||
+ // Leaves end - Revert raid changes
|
||||
Raid raidAt = level.getRaidAt(serverPlayer.blockPosition());
|
||||
if (raidAt == null || raidAt.getRaidOmenLevel() < raidAt.getMaxRaidOmenLevel()) {
|
||||
serverPlayer.addEffect(new MobEffectInstance(MobEffects.RAID_OMEN, 600, amplifier));
|
||||
diff --git a/net/minecraft/world/entity/raid/Raid.java b/net/minecraft/world/entity/raid/Raid.java
|
||||
index 1910bff3f08147bfd63703097e30b9f88c84e337..26f4d7359f638a11ea2d768d18091e261d6b1791 100644
|
||||
--- a/net/minecraft/world/entity/raid/Raid.java
|
||||
+++ b/net/minecraft/world/entity/raid/Raid.java
|
||||
@@ -340,7 +340,20 @@ public class Raid {
|
||||
}
|
||||
|
||||
if (flag1) {
|
||||
- this.waveSpawnPos = this.getValidSpawnPos(level);
|
||||
+ // Luminol Start - Raid revert
|
||||
+ if (!fun.bm.lophine.config.modules.misc.RaidChangesConfig.posRevert) {
|
||||
+ this.waveSpawnPos = this.getValidSpawnPos(level);
|
||||
+ } else {
|
||||
+ int n4 = 0;
|
||||
+ if (this.raidCooldownTicks < 100) {
|
||||
+ n4 = 1;
|
||||
+ }
|
||||
+ if (this.raidCooldownTicks < 40) {
|
||||
+ n4 = 2;
|
||||
+ }
|
||||
+ this.waveSpawnPos = this.getValidSpawnPos(level, n4);
|
||||
+ }
|
||||
+ // Luminol End - Raid revert
|
||||
}
|
||||
|
||||
if (this.raidCooldownTicks == 300 || this.raidCooldownTicks % 20 == 0) {
|
||||
@@ -375,7 +388,14 @@ public class Raid {
|
||||
int i = 0;
|
||||
|
||||
while (this.shouldSpawnGroup()) {
|
||||
- BlockPos blockPos = this.waveSpawnPos.orElseGet(() -> this.findRandomSpawnPos(level, 20));
|
||||
+ // Luminol Start - Raid revert
|
||||
+ BlockPos blockPos;
|
||||
+ if (!fun.bm.lophine.config.modules.misc.RaidChangesConfig.posRevert) {
|
||||
+ blockPos = this.waveSpawnPos.orElseGet(() -> this.findRandomSpawnPos(level, 20));
|
||||
+ } else {
|
||||
+ blockPos = this.waveSpawnPos.isPresent() ? this.waveSpawnPos.get() : this.findRandomSpawnPos(level, i, 20);
|
||||
+ }
|
||||
+ // Luminol End - Raid revert
|
||||
if (blockPos != null) {
|
||||
this.started = true;
|
||||
this.spawnGroup(level, blockPos);
|
||||
@@ -387,7 +407,7 @@ public class Raid {
|
||||
i++;
|
||||
}
|
||||
|
||||
- if (i > 5) {
|
||||
+ if (i > (fun.bm.lophine.config.modules.misc.RaidChangesConfig.posRevert ? 3 : 5)) { // Luminol - Raid revert
|
||||
org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(level, this, org.bukkit.event.raid.RaidStopEvent.Reason.UNSPAWNABLE); // CraftBukkit
|
||||
this.stop();
|
||||
break;
|
||||
@@ -458,6 +478,17 @@ public class Raid {
|
||||
return blockPos != null ? Optional.of(blockPos) : Optional.empty();
|
||||
}
|
||||
|
||||
+ // Luminol Start - Raid revert
|
||||
+ private Optional<BlockPos> getValidSpawnPos(ServerLevel level, int n) {
|
||||
+ for (int i = 0; i < 3; ++i) {
|
||||
+ BlockPos blockPos = this.findRandomSpawnPos(level, n, 1);
|
||||
+ if (blockPos == null) continue;
|
||||
+ return Optional.of(blockPos);
|
||||
+ }
|
||||
+ return Optional.empty();
|
||||
+ }
|
||||
+ // Luminol End - Raid revert
|
||||
+
|
||||
private boolean hasMoreWaves() {
|
||||
return this.hasBonusWave() ? !this.hasSpawnedBonusWave() : !this.isFinalWave();
|
||||
}
|
||||
@@ -683,7 +714,7 @@ public class Raid {
|
||||
int i2 = this.center.getX() + Mth.floor(Mth.cos(f2) * 32.0F * f) + level.random.nextInt(3) * Mth.floor(f);
|
||||
int i3 = this.center.getZ() + Mth.floor(Mth.sin(f2) * 32.0F * f) + level.random.nextInt(3) * Mth.floor(f);
|
||||
int height = level.getHeight(Heightmap.Types.WORLD_SURFACE, i2, i3);
|
||||
- if (Mth.abs(height - this.center.getY()) <= 96) {
|
||||
+ if (fun.bm.lophine.config.modules.misc.RaidChangesConfig.heightCheck || Mth.abs(height - this.center.getY()) <= 96) { // Leaves - Disable height check
|
||||
mutableBlockPos.set(i2, height, i3);
|
||||
if (!level.isVillage(mutableBlockPos) || i <= 7) {
|
||||
int i4 = 10;
|
||||
@@ -702,6 +733,26 @@ public class Raid {
|
||||
return null;
|
||||
}
|
||||
|
||||
+ // Luminol Start - Raid revert
|
||||
+ @Nullable
|
||||
+ private BlockPos findRandomSpawnPos(ServerLevel level, int n, int n2) {
|
||||
+ int n3 = 2 - n;
|
||||
+ BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
|
||||
+ SpawnPlacementType spawnPlacementType = SpawnPlacements.getPlacementType(EntityType.RAVAGER);
|
||||
+ for (int i = 0; i < n2; ++i) {
|
||||
+ float f = level.random.nextFloat() * ((float)Math.PI * 2);
|
||||
+ int n4 = this.center.getX() + Mth.floor(Mth.cos(f) * 32.0f * (float)n3) + level.random.nextInt(5);
|
||||
+ int n5 = this.center.getZ() + Mth.floor(Mth.sin(f) * 32.0f * (float)n3) + level.random.nextInt(5);
|
||||
+ int n6 = level.getHeight(Heightmap.Types.WORLD_SURFACE, n4, n5);
|
||||
+ mutableBlockPos.set(n4, n6, n5);
|
||||
+ if (level.isVillage(mutableBlockPos) && n < 2) continue;
|
||||
+ if (!level.hasChunksAt(mutableBlockPos.getX() - 10, mutableBlockPos.getZ() - 10, mutableBlockPos.getX() + 10, mutableBlockPos.getZ() + 10) || !level.isPositionEntityTicking(mutableBlockPos) || !spawnPlacementType.isSpawnPositionOk(level, mutableBlockPos, EntityType.RAVAGER) && (!level.getBlockState(mutableBlockPos.below()).is(Blocks.SNOW) || !level.getBlockState(mutableBlockPos).isAir())) continue;
|
||||
+ return mutableBlockPos;
|
||||
+ }
|
||||
+ return null;
|
||||
+ }
|
||||
+ // Luminol End - Raid revert
|
||||
+
|
||||
private boolean addWaveMob(ServerLevel level, int wave, Raider raider) {
|
||||
// Folia start - make raids thread-safe
|
||||
if (!this.ownsRaid(level)) {
|
||||
diff --git a/net/minecraft/world/entity/raid/Raider.java b/net/minecraft/world/entity/raid/Raider.java
|
||||
index 3e30989c99cd11c8c94d6fd194d2969e3a7efda6..510514e363f4816a6e286fb9b5488153e03c9e55 100644
|
||||
--- a/net/minecraft/world/entity/raid/Raider.java
|
||||
+++ b/net/minecraft/world/entity/raid/Raider.java
|
||||
@@ -128,6 +128,41 @@ public abstract class Raider extends PatrollingMonster {
|
||||
|
||||
currentRaid.removeFromRaid(serverLevel, this, false);
|
||||
}
|
||||
+
|
||||
+ // Leaves start - Revert raid changes
|
||||
+ if (fun.bm.lophine.config.modules.misc.RaidChangesConfig.effect && !this.hasRaid()) {
|
||||
+ ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD);
|
||||
+ net.minecraft.world.entity.player.Player entityhuman = null;
|
||||
+ if (entity instanceof net.minecraft.world.entity.player.Player player) {
|
||||
+ entityhuman = player;
|
||||
+ } else if (entity instanceof net.minecraft.world.entity.animal.wolf.Wolf wolf) {
|
||||
+ LivingEntity entityliving = wolf.getOwner();
|
||||
+ if (wolf.isTame() && entityliving instanceof net.minecraft.world.entity.player.Player player) {
|
||||
+ entityhuman = player;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (entityhuman != null && !itemstack.isEmpty() && this.isCaptain()) {
|
||||
+ net.minecraft.world.effect.MobEffectInstance mobeffect = entityhuman.getEffect(net.minecraft.world.effect.MobEffects.BAD_OMEN);
|
||||
+ int i = 1;
|
||||
+
|
||||
+ if (mobeffect != null) {
|
||||
+ i += mobeffect.getAmplifier();
|
||||
+ entityhuman.removeEffectNoUpdate(net.minecraft.world.effect.MobEffects.BAD_OMEN);
|
||||
+ } else {
|
||||
+ --i;
|
||||
+ }
|
||||
+
|
||||
+ i = net.minecraft.util.Mth.clamp(i, 0, 4);
|
||||
+ net.minecraft.world.effect.MobEffectInstance mobeffect1 = new net.minecraft.world.effect.MobEffectInstance(net.minecraft.world.effect.MobEffects.BAD_OMEN, fun.bm.lophine.config.modules.misc.RaidChangesConfig.infinite ? net.minecraft.world.effect.MobEffectInstance.INFINITE_DURATION : 120000, i, false, false, true);
|
||||
+
|
||||
+ if (!serverLevel.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_DISABLE_RAIDS)) {
|
||||
+ entityhuman.addEffect(mobeffect1, entityhuman, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN, true); // CraftBukkit
|
||||
+ }
|
||||
+ this.setPatrolLeader(false);
|
||||
+ }
|
||||
+ }
|
||||
+ // Leaves end - Revert raid changes
|
||||
}
|
||||
|
||||
super.die(cause);
|
||||
@@ -156,7 +191,7 @@ public abstract class Raider extends PatrollingMonster {
|
||||
}
|
||||
|
||||
public boolean hasRaid() {
|
||||
- return this.level() instanceof ServerLevel serverLevel && (this.getCurrentRaid() != null || serverLevel.getRaidAt(this.blockPosition()) != null);
|
||||
+ return !fun.bm.lophine.config.modules.misc.RaidChangesConfig.selfCheck && (this.level() instanceof ServerLevel serverLevel && (this.getCurrentRaid() != null || serverLevel.getRaidAt(this.blockPosition()) != null)); // Leaves - Disable raid self check
|
||||
}
|
||||
|
||||
public boolean hasActiveRaid() {
|
||||
diff --git a/net/minecraft/world/item/component/OminousBottleAmplifier.java b/net/minecraft/world/item/component/OminousBottleAmplifier.java
|
||||
index 33907bb190ffa22ccf9ea424b1e536297878711a..2fca459f2c888079322f6b957bbee3ac0e33c4bc 100644
|
||||
--- a/net/minecraft/world/item/component/OminousBottleAmplifier.java
|
||||
+++ b/net/minecraft/world/item/component/OminousBottleAmplifier.java
|
||||
@@ -29,7 +29,7 @@ public record OminousBottleAmplifier(int value) implements ConsumableListener, T
|
||||
|
||||
@Override
|
||||
public void onConsume(Level level, LivingEntity entity, ItemStack stack, Consumable consumable) {
|
||||
- entity.addEffect(new MobEffectInstance(MobEffects.BAD_OMEN, 120000, this.value, false, false, true)); // Paper - properly resend entities - diff on change for below
|
||||
+ entity.addEffect(new MobEffectInstance(MobEffects.BAD_OMEN, fun.bm.lophine.config.modules.misc.RaidChangesConfig.infinite ? net.minecraft.world.effect.MobEffectInstance.INFINITE_DURATION : 120000, this.value, false, false, true)); // Paper - properly resend entities - diff on change for below // Luminol - Raid effect infinite
|
||||
}
|
||||
|
||||
// Paper start - properly resend entities - collect packets for bundle
|
||||
@@ -41,7 +41,7 @@ public record OminousBottleAmplifier(int value) implements ConsumableListener, T
|
||||
|
||||
@Override
|
||||
public void addToTooltip(Item.TooltipContext context, Consumer<Component> tooltipAdder, TooltipFlag flag, DataComponentGetter componentGetter) {
|
||||
- List<MobEffectInstance> list = List.of(new MobEffectInstance(MobEffects.BAD_OMEN, 120000, this.value, false, false, true));
|
||||
+ List<MobEffectInstance> list = List.of(new MobEffectInstance(MobEffects.BAD_OMEN, fun.bm.lophine.config.modules.misc.RaidChangesConfig.infinite ? net.minecraft.world.effect.MobEffectInstance.INFINITE_DURATION : 120000, this.value, false, false, true)); // Luminol - Raid effect infinite
|
||||
PotionContents.addPotionTooltip(list, tooltipAdder, 1.0F, context.tickRate());
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: MrHua269 <wangxyper@163.com>
|
||||
Date: Wed, 5 Feb 2025 15:22:19 +0800
|
||||
Subject: [PATCH] Add config to enable Raytracing tracker
|
||||
Subject: [PATCH] Add config to enable raytracing tracker
|
||||
|
||||
|
||||
diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
|
||||
@@ -18,7 +18,7 @@ index 6868b915bf3deb85783a638d4441a15fea6da2dc..70740381c6501c1a518c52b24381edd1
|
||||
double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1);
|
||||
if (rangeY != -1) {
|
||||
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
|
||||
index dc3d29b7d1590ba366755fef51e4254d901d0da8..34b1597c56046f8fc5ab5fe9d8a5d3c96a4bc134 100644
|
||||
index 75f327fff68097c1c9f65767552f686421e1e397..ed7421895311d89ad9726804893eff423868d375 100644
|
||||
--- a/net/minecraft/world/entity/Entity.java
|
||||
+++ b/net/minecraft/world/entity/Entity.java
|
||||
@@ -147,7 +147,7 @@ import net.minecraft.world.waypoints.WaypointTransmitter;
|
||||
@@ -30,7 +30,7 @@ index dc3d29b7d1590ba366755fef51e4254d901d0da8..34b1597c56046f8fc5ab5fe9d8a5d3c9
|
||||
// CraftBukkit start
|
||||
private static final int CURRENT_LEVEL = 2;
|
||||
public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first snapTo
|
||||
@@ -6352,4 +6352,46 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
|
||||
@@ -6467,4 +6467,46 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
|
||||
// Paper end - Expose entity id counter
|
||||
|
||||
public boolean shouldTickHot() { return this.tickCount > 20 * 10 && this.isAlive(); } // KioCG
|
||||
+3
-3
@@ -97,10 +97,10 @@ index 0357792de0ed8ec9058d1847c8b45c33ff365af6..4945c11f573a53df2f5be4beb7af6a3f
|
||||
this.scheduler.regionFailed(this, false, thr);
|
||||
// regionFailed will schedule a shutdown, so we should avoid letting this region tick further
|
||||
diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java
|
||||
index 8a90aa5c145f53afbb3f2371f97cb09974f30bbb..0d5ed9602492c8393088954105e9a19032067b8b 100644
|
||||
index b6e10157c572dadb1bff18f8431166966eed46d9..e2fc15d84fafde6847ecead23c55169449275e1b 100644
|
||||
--- a/net/minecraft/commands/Commands.java
|
||||
+++ b/net/minecraft/commands/Commands.java
|
||||
@@ -243,7 +243,11 @@ public class Commands {
|
||||
@@ -245,7 +245,11 @@ public class Commands {
|
||||
TeleportCommand.register(this.dispatcher);
|
||||
TellRawCommand.register(this.dispatcher, context);
|
||||
//TestCommand.register(this.dispatcher, context); // Folia - region threading
|
||||
@@ -114,7 +114,7 @@ index 8a90aa5c145f53afbb3f2371f97cb09974f30bbb..0d5ed9602492c8393088954105e9a190
|
||||
TitleCommand.register(this.dispatcher, context);
|
||||
//TriggerCommand.register(this.dispatcher); // Folia - region threading - TODO later
|
||||
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
||||
index 2ed2f4cca3a81117c0beaf7a11bbbe8156f6a9ce..6675b83c9f15d0341dcf169ec3ac20f401c5d10b 100644
|
||||
index d08a5b553f4e5932d960fbcdeb6a43419730e32a..a0f1db50adf5163a671d3d9e9df7adb7de4250c6 100644
|
||||
--- a/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/net/minecraft/server/MinecraftServer.java
|
||||
@@ -267,7 +267,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
+17
@@ -4,6 +4,23 @@ Date: Mon, 7 Jul 2025 18:19:00 +0800
|
||||
Subject: [PATCH] Add config to enable unsafe waypoint bar
|
||||
|
||||
|
||||
diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java
|
||||
index e2fc15d84fafde6847ecead23c55169449275e1b..b0a06cdfaa49237f372ecd131d47289160476b07 100644
|
||||
--- a/net/minecraft/commands/Commands.java
|
||||
+++ b/net/minecraft/commands/Commands.java
|
||||
@@ -253,7 +253,11 @@ public class Commands {
|
||||
TimeCommand.register(this.dispatcher);
|
||||
TitleCommand.register(this.dispatcher, context);
|
||||
//TriggerCommand.register(this.dispatcher); // Folia - region threading - TODO later
|
||||
- //WaypointCommand.register(this.dispatcher, context); // Folia - region threading - TODO later
|
||||
+ // Lophine start - unsafe waypoint bar
|
||||
+ if (fun.bm.lophine.config.modules.experiment.WaypointConfig.forceEnableWaypointUnsafe) {
|
||||
+ WaypointCommand.register(this.dispatcher, context); // Folia - region threading - TODO later
|
||||
+ }
|
||||
+ // Lophine end - unsafe waypoint bar
|
||||
WeatherCommand.register(this.dispatcher);
|
||||
WorldBorderCommand.register(this.dispatcher);
|
||||
if (JvmProfiler.INSTANCE.isAvailable()) {
|
||||
diff --git a/net/minecraft/server/waypoints/ServerWaypointManager.java b/net/minecraft/server/waypoints/ServerWaypointManager.java
|
||||
index 0f8cacbb8fe55a60e2f0c98bf36c005b29f41a4b..36e9b027ce5213f8914728293633e78e9905c1b2 100644
|
||||
--- a/net/minecraft/server/waypoints/ServerWaypointManager.java
|
||||
@@ -0,0 +1,198 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
|
||||
Date: Sun, 3 Aug 2025 15:24:01 +0800
|
||||
Subject: [PATCH] Leaves: Base Protocol Core
|
||||
|
||||
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
|
||||
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/ea91106ae57fc4cc14e2e0225009cf9919072f7f/leaves-server/minecraft-patches/features/0004-Leaves-Protocol-Core.patch)
|
||||
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
diff --git a/io/papermc/paper/threadedregions/RegionizedServer.java b/io/papermc/paper/threadedregions/RegionizedServer.java
|
||||
index 8405157ec35a443e238fa6866772c839621d73d7..4edb6e6e6287f232f7eef45c747554264ec05319 100644
|
||||
--- a/io/papermc/paper/threadedregions/RegionizedServer.java
|
||||
+++ b/io/papermc/paper/threadedregions/RegionizedServer.java
|
||||
@@ -334,6 +334,8 @@ public final class RegionizedServer {
|
||||
}
|
||||
// Luminol end - Add a config to enable tick command
|
||||
|
||||
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleTick(tickCount); // Leaves - protocol
|
||||
+
|
||||
// tick connections
|
||||
this.tickConnections();
|
||||
|
||||
@@ -443,6 +445,8 @@ public final class RegionizedServer {
|
||||
|
||||
world.updateTickData();
|
||||
|
||||
+ MinecraftServer.getServer().handleTickCount(tickCount); // Lophine - reuse tick count
|
||||
+
|
||||
world.moonrise$getChunkTaskScheduler().chunkHolderManager.processTicketUpdates(); // required to eventually process ticket updates
|
||||
}
|
||||
|
||||
diff --git a/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java b/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java
|
||||
index fb263fa1f30a7dfcb7ec2656abfb38e5fe88eac9..56fd1ed7ccaf96e7eedea60fbdbf7f934939d563 100644
|
||||
--- a/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java
|
||||
+++ b/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java
|
||||
@@ -40,13 +40,22 @@ public interface CustomPacketPayload {
|
||||
|
||||
@Override
|
||||
public void encode(B buffer, CustomPacketPayload value) {
|
||||
+ // Leaves start - protocol core
|
||||
+ if (value instanceof org.leavesmc.leaves.protocol.core.LeavesCustomPayload payload) {
|
||||
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.encode(buffer, payload);
|
||||
+ return;
|
||||
+ }
|
||||
+ // Leaves end - protocol core
|
||||
this.writeCap(buffer, value.type(), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomPacketPayload decode(B buffer) {
|
||||
ResourceLocation resourceLocation = buffer.readResourceLocation();
|
||||
- return (CustomPacketPayload)this.findCodec(resourceLocation).decode(buffer);
|
||||
+ // Leaves start - protocol core
|
||||
+ var payload = org.leavesmc.leaves.protocol.core.LeavesProtocolManager.decode(resourceLocation, buffer);
|
||||
+ return java.util.Objects.requireNonNullElseGet(payload, () -> this.findCodec(resourceLocation).decode(buffer));
|
||||
+ // Leaves end - protocol core
|
||||
}
|
||||
};
|
||||
}
|
||||
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
||||
index a0f1db50adf5163a671d3d9e9df7adb7de4250c6..af13707933af14171447d5ab6faacb64ddf194a7 100644
|
||||
--- a/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/net/minecraft/server/MinecraftServer.java
|
||||
@@ -305,6 +305,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
|
||||
// Folia start - regionised ticking
|
||||
public final io.papermc.paper.threadedregions.RegionizedServer regionizedServer = new io.papermc.paper.threadedregions.RegionizedServer();
|
||||
+ private int tickCount;
|
||||
|
||||
@Override
|
||||
public <V> CompletableFuture<V> submit(java.util.function.Supplier<V> task) {
|
||||
@@ -2226,9 +2227,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
return false;
|
||||
}
|
||||
|
||||
+ // Lophine start - reuse tick count
|
||||
public int getTickCount() {
|
||||
- throw new UnsupportedOperationException(); // Folia - region threading
|
||||
+ if (false) throw new UnsupportedOperationException(); // Folia - region threading
|
||||
+ return this.tickCount;
|
||||
+ }
|
||||
+
|
||||
+ public void handleTickCount(int tickCount1) {
|
||||
+ tickCount = tickCount1;
|
||||
}
|
||||
+ // Lophine end - reuse tick count
|
||||
|
||||
public int getSpawnProtectionRadius() {
|
||||
return 16;
|
||||
diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
|
||||
index 714813cba67da4a737682fe9276cb0d96fea336d..9677a3d62de11dafc8b9debe7a18ee6d14c7a64d 100644
|
||||
--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
|
||||
+++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
|
||||
@@ -57,6 +57,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
|
||||
public @Nullable String playerBrand;
|
||||
public final java.util.Set<String> pluginMessagerChannels;
|
||||
// Paper end - retain certain values
|
||||
+ public final GameProfile profile; // Leaves - protocol core
|
||||
|
||||
public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie) {
|
||||
this.server = server;
|
||||
@@ -70,6 +71,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
|
||||
this.pluginMessagerChannels = cookie.channels();
|
||||
this.keepAlive = cookie.keepAlive();
|
||||
// Paper end
|
||||
+ this.profile = cookie.gameProfile(); // Leaves - protocol core
|
||||
}
|
||||
|
||||
// Paper start - configuration phase API
|
||||
@@ -160,6 +162,18 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
|
||||
|
||||
@Override
|
||||
public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {
|
||||
+ // Leaves start - protocol
|
||||
+ if (packet.payload() instanceof org.leavesmc.leaves.protocol.core.LeavesCustomPayload leavesPayload) {
|
||||
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePayload(org.leavesmc.leaves.protocol.core.ProtocolUtils.createSelector(this), leavesPayload);
|
||||
+ return;
|
||||
+ }
|
||||
+ if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.DiscardedPayload(net.minecraft.resources.ResourceLocation id, byte[] data)) {
|
||||
+ if (org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleBytebuf(org.leavesmc.leaves.protocol.core.ProtocolUtils.createSelector(this), id, io.netty.buffer.Unpooled.wrappedBuffer(data))) {
|
||||
+ return;
|
||||
+ }
|
||||
+ }
|
||||
+ // Leaves end - protocol
|
||||
+
|
||||
// Paper start
|
||||
if (!(packet.payload() instanceof final net.minecraft.network.protocol.common.custom.DiscardedPayload discardedPayload)) {
|
||||
return;
|
||||
@@ -219,10 +233,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
|
||||
final String channel = new String(data, from, length, java.nio.charset.StandardCharsets.US_ASCII);
|
||||
if (register) {
|
||||
bridge.addChannel(channel);
|
||||
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleMinecraftRegister(channel, org.leavesmc.leaves.protocol.core.ProtocolUtils.createSelector(this)); // Leaves - protocol
|
||||
} else {
|
||||
bridge.removeChannel(channel);
|
||||
}
|
||||
- // Paper end
|
||||
+ // Paper end
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -385,9 +400,9 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
|
||||
net.minecraft.server.level.ServerPlayer player = serverGamePacketListener.player;
|
||||
org.bukkit.event.player.PlayerKickEvent.Cause cause = disconnectionDetails.disconnectionReason().orElseThrow().game().orElse(org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN);
|
||||
org.bukkit.event.player.PlayerKickEvent event = new org.bukkit.event.player.PlayerKickEvent(
|
||||
- player.getBukkitEntity(),
|
||||
- io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionDetails.reason()),
|
||||
- rawLeaveMessage, cause
|
||||
+ player.getBukkitEntity(),
|
||||
+ io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionDetails.reason()),
|
||||
+ rawLeaveMessage, cause
|
||||
|
||||
);
|
||||
|
||||
@@ -420,10 +435,10 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
|
||||
|
||||
private void disconnect0(DisconnectionDetails disconnectionDetails) {
|
||||
this.connection
|
||||
- .send(
|
||||
- new ClientboundDisconnectPacket(disconnectionDetails.reason()),
|
||||
- PacketSendListener.thenRun(() -> this.connection.disconnect(disconnectionDetails))
|
||||
- );
|
||||
+ .send(
|
||||
+ new ClientboundDisconnectPacket(disconnectionDetails.reason()),
|
||||
+ PacketSendListener.thenRun(() -> this.connection.disconnect(disconnectionDetails))
|
||||
+ );
|
||||
this.onDisconnect(disconnectionDetails);
|
||||
this.connection.setReadOnly();
|
||||
// CraftBukkit - Don't wait
|
||||
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
|
||||
index 4ef2816210f06082f2209c51d04aad6bb188232b..593dbcda4b497291eb1ecdc64b947a8d279f8be6 100644
|
||||
--- a/net/minecraft/server/players/PlayerList.java
|
||||
+++ b/net/minecraft/server/players/PlayerList.java
|
||||
@@ -423,6 +423,8 @@ public abstract class PlayerList {
|
||||
//return; // Folia - region threading - must still allow the player to connect, as we must add to chunk map before handling disconnect
|
||||
}
|
||||
|
||||
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player);
|
||||
+
|
||||
final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage();
|
||||
|
||||
if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure
|
||||
@@ -605,6 +607,7 @@ public abstract class PlayerList {
|
||||
return this.remove(player, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? player.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(player.getDisplayName())));
|
||||
}
|
||||
public @Nullable net.kyori.adventure.text.Component remove(ServerPlayer player, net.kyori.adventure.text.Component leaveMessage) {
|
||||
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerLeave(player); // Leaves - protocol
|
||||
// Paper end - Fix kick event leave message not being sent
|
||||
ServerLevel serverLevel = player.level();
|
||||
player.awardStat(Stats.LEAVE_GAME);
|
||||
@@ -1520,6 +1523,7 @@ public abstract class PlayerList {
|
||||
serverPlayer.connection.send(clientboundUpdateRecipesPacket);
|
||||
serverPlayer.getRecipeBook().sendInitialRecipeBook(serverPlayer);
|
||||
}
|
||||
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleDataPackReload(); // Leaves - protocol core
|
||||
}
|
||||
|
||||
public boolean isAllowCommandsForAllPlayers() {
|
||||
+117
-173
@@ -1,30 +1,34 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
|
||||
Date: Mon, 9 Jun 2025 19:23:10 +0800
|
||||
Subject: [PATCH] Stackable ShulkerBoxes
|
||||
Date: Sat, 26 Jul 2025 01:11:54 +0800
|
||||
Subject: [PATCH] Leaves: Item overstack util
|
||||
|
||||
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
|
||||
Some of changes is a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/9d32c5bd3df7c76055aff886ed9efda02e45a45a/leaves-server/minecraft-patches/features/0034-Stackable-ShulkerBoxes.patch)
|
||||
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/46cccfc39cafbca1681620d03947bc3ad558327f/leaves-server/minecraft-patches/features/0034-Item-overstack-util.patch)
|
||||
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
diff --git a/net/minecraft/commands/arguments/item/ItemInput.java b/net/minecraft/commands/arguments/item/ItemInput.java
|
||||
index 643797124fe5a4489d0b7419b7e600c04f283ef2..3b4d97c76db9c742072c92dd7c8b39553c62a1f1 100644
|
||||
index 643797124fe5a4489d0b7419b7e600c04f283ef2..04b4100da96aad50f08e8c59200eec934e7e873d 100644
|
||||
--- a/net/minecraft/commands/arguments/item/ItemInput.java
|
||||
+++ b/net/minecraft/commands/arguments/item/ItemInput.java
|
||||
@@ -39,8 +39,9 @@ public class ItemInput {
|
||||
@@ -39,11 +39,13 @@ public class ItemInput {
|
||||
public ItemStack createItemStack(int count, boolean allowOversizedStacks) throws CommandSyntaxException {
|
||||
ItemStack itemStack = new ItemStack(this.item, count);
|
||||
itemStack.applyComponents(this.components);
|
||||
- if (allowOversizedStacks && count > itemStack.getMaxStackSize()) {
|
||||
- throw ERROR_STACK_TOO_BIG.create(this.getItemName(), itemStack.getMaxStackSize());
|
||||
+ int maxCount = fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemStack); // Lophine - Stackable ShulkerBoxes
|
||||
+ if (allowOversizedStacks && count > maxCount) { // Lophine - Stackable ShulkerBoxes
|
||||
+ throw ERROR_STACK_TOO_BIG.create(this.getItemName(), maxCount); // Lophine - Stackable ShulkerBoxes
|
||||
+ // Leaves start - item over-stack util
|
||||
+ if (allowOversizedStacks && count > org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemStack)) {
|
||||
+ throw ERROR_STACK_TOO_BIG.create(this.getItemName(), org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemStack));
|
||||
} else {
|
||||
return itemStack;
|
||||
}
|
||||
+ // Leaves end - item over-stack util
|
||||
}
|
||||
|
||||
public String serialize(HolderLookup.Provider levelRegistry) {
|
||||
diff --git a/net/minecraft/server/commands/GiveCommand.java b/net/minecraft/server/commands/GiveCommand.java
|
||||
index 5a1c21b05545a03fbb00cf734605049870d3eecb..2ade5ce23cdab08b1746e2cbe99bfe1d3da42f02 100644
|
||||
index f7002d6752aa03603eb06e298ba1d7130cf13826..72c87f7c0392d087440db9d267a608598f6feb6d 100644
|
||||
--- a/net/minecraft/server/commands/GiveCommand.java
|
||||
+++ b/net/minecraft/server/commands/GiveCommand.java
|
||||
@@ -55,7 +55,7 @@ public class GiveCommand {
|
||||
@@ -32,76 +36,76 @@ index 5a1c21b05545a03fbb00cf734605049870d3eecb..2ade5ce23cdab08b1746e2cbe99bfe1d
|
||||
ItemStack itemStack = item.createItemStack(1, false);
|
||||
final Component displayName = itemStack.getDisplayName(); // Paper - get display name early
|
||||
- int maxStackSize = itemStack.getMaxStackSize();
|
||||
+ int maxStackSize = fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemStack); // Lophine - Stackable ShulkerBoxes
|
||||
+ int maxStackSize = org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemStack); // Leaves - item over-stack util
|
||||
int i = maxStackSize * 100;
|
||||
if (count > i) {
|
||||
source.sendFailure(Component.translatable("commands.give.failed.toomanyitems", i, itemStack.getDisplayName()));
|
||||
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
index 3a78b917cfa2947f26a25e4d77d825e8113a83ef..bec1592ea6ce909bf9b1067e0a32d00c2c4e43f5 100644
|
||||
index d74a05822c6c145971dc526a62d652d35b4f5d9d..d63d8f7523938088bf9efc1c1b8ad9f0583338bb 100644
|
||||
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
@@ -3072,7 +3072,7 @@ public class ServerGamePacketListenerImpl
|
||||
@@ -3079,7 +3079,7 @@ public class ServerGamePacketListenerImpl
|
||||
} else if (slot.mayPlace(cursor)) {
|
||||
if (ItemStack.isSameItemSameComponents(clickedItem, cursor)) {
|
||||
int toPlace = packet.buttonNum() == 0 ? cursor.getCount() : 1;
|
||||
- toPlace = Math.min(toPlace, clickedItem.getMaxStackSize() - clickedItem.getCount());
|
||||
+ toPlace = Math.min(toPlace, fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(clickedItem) - clickedItem.getCount()); // Lophine - Stackable ShulkerBoxes
|
||||
+ toPlace = Math.min(toPlace, org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(clickedItem) - clickedItem.getCount()); // Leaves - item over-stack util
|
||||
toPlace = Math.min(toPlace, slot.container.getMaxStackSize() - clickedItem.getCount());
|
||||
if (toPlace == 1) {
|
||||
action = InventoryAction.PLACE_ONE;
|
||||
@@ -3108,7 +3108,7 @@ public class ServerGamePacketListenerImpl
|
||||
@@ -3115,7 +3115,7 @@ public class ServerGamePacketListenerImpl
|
||||
}
|
||||
} else if (ItemStack.isSameItemSameComponents(cursor, clickedItem)) {
|
||||
if (clickedItem.getCount() >= 0) {
|
||||
- if (clickedItem.getCount() + cursor.getCount() <= cursor.getMaxStackSize()) {
|
||||
+ if (clickedItem.getCount() + cursor.getCount() <= fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(cursor)) { // Lophine - Stackable ShulkerBoxes
|
||||
+ if (clickedItem.getCount() + cursor.getCount() <= org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(cursor)) { // Leaves - item over-stack util
|
||||
// As of 1.5, this is result slots only
|
||||
action = InventoryAction.PICKUP_ALL;
|
||||
}
|
||||
@@ -3319,6 +3319,7 @@ public class ServerGamePacketListenerImpl
|
||||
@@ -3326,6 +3326,7 @@ public class ServerGamePacketListenerImpl
|
||||
this.player.containerMenu.broadcastFullState();
|
||||
} else {
|
||||
this.player.containerMenu.broadcastChanges();
|
||||
+ if (fun.bm.lophine.utils.ShulkerBoxesUtil.shouldCheck()) this.player.containerMenu.broadcastCarriedItem(); // Lophine - Stackable ShulkerBoxes
|
||||
+ if (org.leavesmc.leaves.util.ItemOverstackUtils.hasOverstackingItem()) this.player.containerMenu.broadcastCarriedItem(); // Leaves - item over-stack util - force send carried item
|
||||
}
|
||||
if (packet.buttonNum() == Inventory.SLOT_OFFHAND && this.player.containerMenu != this.player.inventoryMenu) this.player.containerSynchronizer.sendOffHandSlotChange(); // Paper - update offhand data when the player is clicking in an inventory not their own as the sychronizer does not include offhand slots
|
||||
if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.updateEquipmentOnPlayerActions) this.player.detectEquipmentUpdates(); // Paper - Force update attributes.
|
||||
}
|
||||
@@ -3429,7 +3430,7 @@ public class ServerGamePacketListenerImpl
|
||||
@@ -3437,7 +3438,7 @@ public class ServerGamePacketListenerImpl
|
||||
}
|
||||
|
||||
boolean flag1 = packet.slotNum() >= 1 && packet.slotNum() <= 45;
|
||||
- boolean flag2 = itemStack.isEmpty() || itemStack.getCount() <= itemStack.getMaxStackSize();
|
||||
+ boolean flag2 = itemStack.isEmpty() || itemStack.getCount() <= fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemStack); // Lophine - Stackable ShulkerBoxes
|
||||
+ boolean flag2 = itemStack.isEmpty() || itemStack.getCount() <= org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemStack); // Leaves - item over-stack util
|
||||
if (flag || (flag1 && !ItemStack.matches(this.player.inventoryMenu.getSlot(packet.slotNum()).getItem(), packet.itemStack()))) { // Insist on valid slot
|
||||
// CraftBukkit start - Call click event
|
||||
org.bukkit.inventory.InventoryView inventory = this.player.inventoryMenu.getBukkitView();
|
||||
@@ -3471,6 +3472,7 @@ public class ServerGamePacketListenerImpl
|
||||
@@ -3479,6 +3480,7 @@ public class ServerGamePacketListenerImpl
|
||||
this.player.inventoryMenu.getSlot(packet.slotNum()).setByPlayer(itemStack);
|
||||
this.player.inventoryMenu.setRemoteSlot(packet.slotNum(), itemStack);
|
||||
this.player.inventoryMenu.broadcastChanges();
|
||||
+ if (fun.bm.lophine.utils.ShulkerBoxesUtil.shouldCheck()) this.player.containerMenu.broadcastCarriedItem(); // Lophine - Stackable ShulkerBoxes
|
||||
+ if (org.leavesmc.leaves.util.ItemOverstackUtils.hasOverstackingItem()) this.player.containerMenu.sendSingleSlot(packet.slotNum(), itemStack); // Leaves - item over-stack util - force send carried item
|
||||
if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.updateEquipmentOnPlayerActions) this.player.detectEquipmentUpdates(); // Paper - Force update attributes.
|
||||
} else if (flag && flag2) {
|
||||
if (this.dropSpamThrottler.isUnderThreshold()) {
|
||||
diff --git a/net/minecraft/world/Container.java b/net/minecraft/world/Container.java
|
||||
index b382665cc125b8b5c0938e5e55984e4bf91d37ff..5d8bd05f966cf9befe8b8f3359b2fa4ce2428e98 100644
|
||||
index 2afaf6a3f18af7889f4f3b35cee9bbb5e4ec01f2..da99b4bc7fe8460945070915073be141f9bd6778 100644
|
||||
--- a/net/minecraft/world/Container.java
|
||||
+++ b/net/minecraft/world/Container.java
|
||||
@@ -32,6 +32,12 @@ public interface Container extends Clearable, Iterable<ItemStack> {
|
||||
@@ -32,6 +32,12 @@ public interface Container extends Clearable, Iterable<ItemStack>, org.leavesmc.
|
||||
return Math.min(this.getMaxStackSize(), stack.getMaxStackSize());
|
||||
}
|
||||
|
||||
+ // Leaves start - stackable shulker boxes
|
||||
+ // Leaves start - item over-stack util
|
||||
+ default int getMaxStackLeaves(ItemStack stack) {
|
||||
+ return Math.min(this.getMaxStackSize(), fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(stack));
|
||||
+ return Math.min(this.getMaxStackSize(), org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(stack));
|
||||
+ }
|
||||
+ // Leaves end - stackable shulker boxes
|
||||
+ // Leaves end - item over-stack util
|
||||
+
|
||||
void setChanged();
|
||||
|
||||
boolean stillValid(Player player);
|
||||
diff --git a/net/minecraft/world/SimpleContainer.java b/net/minecraft/world/SimpleContainer.java
|
||||
index d907e24d563e27acab2f2bf9711b1755ea9afd19..6189cb7c1745a242b610e89c859cc849f16cc9e3 100644
|
||||
index d907e24d563e27acab2f2bf9711b1755ea9afd19..75170c8d3be477a6ea2a1d62018a6ab630b0e54e 100644
|
||||
--- a/net/minecraft/world/SimpleContainer.java
|
||||
+++ b/net/minecraft/world/SimpleContainer.java
|
||||
@@ -211,7 +211,7 @@ public class SimpleContainer implements Container, StackedContentsCompatible {
|
||||
@@ -109,7 +113,7 @@ index d907e24d563e27acab2f2bf9711b1755ea9afd19..6189cb7c1745a242b610e89c859cc849
|
||||
public void setItem(int index, ItemStack stack) {
|
||||
this.items.set(index, stack);
|
||||
- stack.limitSize(this.getMaxStackSize(stack));
|
||||
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - stackable shulker boxes
|
||||
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - item over-stack util
|
||||
this.setChanged();
|
||||
}
|
||||
|
||||
@@ -118,114 +122,88 @@ index d907e24d563e27acab2f2bf9711b1755ea9afd19..6189cb7c1745a242b610e89c859cc849
|
||||
|
||||
private void moveItemsBetweenStacks(ItemStack stack, ItemStack other) {
|
||||
- int maxStackSize = this.getMaxStackSize(other);
|
||||
+ int maxStackSize = this.getMaxStackLeaves(other); // Leaves - stackable shulker boxes
|
||||
+ int maxStackSize = this.getMaxStackLeaves(other); // Leaves - item over-stack util
|
||||
int min = Math.min(stack.getCount(), maxStackSize - other.getCount());
|
||||
if (min > 0) {
|
||||
other.grow(min);
|
||||
diff --git a/net/minecraft/world/entity/item/ItemEntity.java b/net/minecraft/world/entity/item/ItemEntity.java
|
||||
index b745eb5d9c68547335247910ff2ae8d5fb36349c..6d8675b918f92dc3ce85825edf2b33469950ef9d 100644
|
||||
index fd4f1e63f980b6959fc80084651991f8f8bc6c34..e5e8c752129a6ebb0dd1315c9b8ec459be11ef07 100644
|
||||
--- a/net/minecraft/world/entity/item/ItemEntity.java
|
||||
+++ b/net/minecraft/world/entity/item/ItemEntity.java
|
||||
@@ -279,10 +279,45 @@ public class ItemEntity extends Entity implements TraceableEntity {
|
||||
@@ -283,10 +283,15 @@ public class ItemEntity extends Entity implements TraceableEntity, ChangePublish
|
||||
|
||||
private boolean isMergable() {
|
||||
ItemStack item = this.getItem();
|
||||
- return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < this.despawnRate && item.getCount() < item.getMaxStackSize(); // Paper - Alternative item-despawn-rate
|
||||
+ return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < this.despawnRate && item.getCount() < fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(item); // Paper - Alternative item-despawn-rate // Lophine - Stackable ShulkerBoxes
|
||||
+ }
|
||||
+
|
||||
+ // Lophine start - Stackable ShulkerBoxes
|
||||
+ private boolean tryMergeShulkerBox(ItemEntity ote) {
|
||||
+ ItemStack sf = this.getItem();
|
||||
+ ItemStack ot = ote.getItem();
|
||||
+ if (!fun.bm.lophine.utils.ShulkerBoxesUtil.checkShulkerBox(sf)) return false;
|
||||
+ if (sf.getItem().equals(ot.getItem())
|
||||
+ && fun.bm.lophine.utils.ShulkerBoxesUtil.emptyShulkerBoxCheck(sf)
|
||||
+ && fun.bm.lophine.utils.ShulkerBoxesUtil.emptyShulkerBoxCheck(ot)
|
||||
+ && Objects.equals(sf.getComponents(), ot.getComponents()) // empty block entity tags are cleaned up when spawning
|
||||
+ && sf.getCount() != fun.bm.lophine.utils.ShulkerBoxesUtil.getShulkerBoxesMaxCountUnsafe()) {
|
||||
+ int count = Math.min(ot.getCount(), fun.bm.lophine.utils.ShulkerBoxesUtil.getShulkerBoxesMaxCountUnsafe() - sf.getCount());
|
||||
+ sf.grow(count);
|
||||
+ this.setItem(sf);
|
||||
+
|
||||
+ this.pickupDelay = Math.max(ote.pickupDelay, this.pickupDelay);
|
||||
+ this.age = Math.min(ote.getAge(), this.age);
|
||||
+ ot.shrink(count);
|
||||
+ if (ot.isEmpty()) {
|
||||
+ ote.discard();
|
||||
+ }
|
||||
+ else {
|
||||
+ ote.setItem(ot);
|
||||
+ }
|
||||
+ return true;
|
||||
+ }
|
||||
+ return false;
|
||||
+ return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < this.despawnRate && item.getCount() < org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(item); // Paper - Alternative item-despawn-rate // Leaves - item over-stack util
|
||||
}
|
||||
+ // Lophine end - Stackable ShulkerBoxes
|
||||
|
||||
private void tryToMerge(ItemEntity itemEntity) {
|
||||
+ // Lophine start - Stackable ShulkerBoxes
|
||||
+ if (fun.bm.lophine.utils.ShulkerBoxesUtil.shouldCheck()
|
||||
+ && this.tryMergeShulkerBox(itemEntity)) {
|
||||
+ // Leaves start - item over-stack util
|
||||
+ if (org.leavesmc.leaves.util.ItemOverstackUtils.tryStackItems(this, itemEntity)) {
|
||||
+ return;
|
||||
+ }
|
||||
+ // Lophine end - Stackable ShulkerBoxes
|
||||
+ // Leaves end - item over-stack util
|
||||
ItemStack item = this.getItem();
|
||||
ItemStack item1 = itemEntity.getItem();
|
||||
if (Objects.equals(this.target, itemEntity.target) && areMergable(item, item1)) {
|
||||
diff --git a/net/minecraft/world/entity/player/Inventory.java b/net/minecraft/world/entity/player/Inventory.java
|
||||
index a6bb436dc80daf6901dc027a6011ead4b3ed27e2..53210a7d32241c47c2b1cd698b515726f0a1fd0d 100644
|
||||
index a6bb436dc80daf6901dc027a6011ead4b3ed27e2..71df58ae26325a2d28b6679733c474c0c59f7654 100644
|
||||
--- a/net/minecraft/world/entity/player/Inventory.java
|
||||
+++ b/net/minecraft/world/entity/player/Inventory.java
|
||||
@@ -166,8 +166,8 @@ public class Inventory implements Container, Nameable {
|
||||
@@ -165,10 +165,12 @@ public class Inventory implements Container, Nameable {
|
||||
}
|
||||
|
||||
private boolean hasRemainingSpaceForItem(ItemStack destination, ItemStack origin) {
|
||||
+ // Leaves start - item over-stack util
|
||||
return !destination.isEmpty()
|
||||
- && destination.isStackable()
|
||||
- && destination.getCount() < this.getMaxStackSize(destination)
|
||||
+ && fun.bm.lophine.utils.ShulkerBoxesUtil.isStackable(destination) // Lophine - Stackable ShulkerBoxes
|
||||
+ && destination.getCount() < fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(destination) // Lophine - Stackable ShulkerBoxes
|
||||
+ && org.leavesmc.leaves.util.ItemOverstackUtils.isStackable(destination)
|
||||
+ && destination.getCount() < org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(destination)
|
||||
&& ItemStack.isSameItemSameComponents(destination, origin); // Paper - check if itemstack is stackable first
|
||||
+ // Leaves end - item over-stack util
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ public class Inventory implements Container, Nameable {
|
||||
// CraftBukkit start - Watch method above! :D
|
||||
@@ -181,7 +183,7 @@ public class Inventory implements Container, Nameable {
|
||||
}
|
||||
|
||||
if (this.hasRemainingSpaceForItem(itemInSlot, itemStack)) {
|
||||
- remains -= (itemInSlot.getMaxStackSize() < this.getMaxStackSize() ? itemInSlot.getMaxStackSize() : this.getMaxStackSize()) - itemInSlot.getCount();
|
||||
+ remains -= (fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemInSlot) < this.getMaxStackSize() ? fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemInSlot) : this.getMaxStackSize()) - itemInSlot.getCount(); // Lophine - Stackable ShulkerBoxes
|
||||
+ remains -= (org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemInSlot) < this.getMaxStackSize() ? org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemInSlot) : this.getMaxStackSize()) - itemInSlot.getCount(); // Leaves - item over-stack util
|
||||
}
|
||||
if (remains <= 0) {
|
||||
return itemStack.getCount();
|
||||
@@ -190,7 +190,7 @@ public class Inventory implements Container, Nameable {
|
||||
@@ -190,7 +192,7 @@ public class Inventory implements Container, Nameable {
|
||||
|
||||
ItemStack itemInOffhand = this.equipment.get(EquipmentSlot.OFFHAND);
|
||||
if (this.hasRemainingSpaceForItem(itemInOffhand, itemStack)) {
|
||||
- remains -= (itemInOffhand.getMaxStackSize() < this.getMaxStackSize() ? itemInOffhand.getMaxStackSize() : this.getMaxStackSize()) - itemInOffhand.getCount();
|
||||
+ remains -= (fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemInOffhand) < this.getMaxStackSize() ? fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemInOffhand) : this.getMaxStackSize()) - itemInOffhand.getCount(); // Lophine - Stackable ShulkerBoxes
|
||||
+ remains -= (org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemInOffhand) < this.getMaxStackSize() ? org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemInOffhand) : this.getMaxStackSize()) - itemInOffhand.getCount(); // Leaves - item over-stack util
|
||||
}
|
||||
if (remains <= 0) {
|
||||
return itemStack.getCount();
|
||||
@@ -314,7 +314,7 @@ public class Inventory implements Container, Nameable {
|
||||
@@ -314,7 +316,7 @@ public class Inventory implements Container, Nameable {
|
||||
this.setItem(slot, item);
|
||||
}
|
||||
|
||||
- int i = this.getMaxStackSize(item) - item.getCount();
|
||||
+ int i = this.getMaxStackLeaves(item) - item.getCount();
|
||||
+ int i = this.getMaxStackLeaves(item) - item.getCount(); // Leaves - item over-stack util
|
||||
int min = Math.min(count, i);
|
||||
if (min == 0) {
|
||||
return count;
|
||||
@@ -420,7 +420,7 @@ public class Inventory implements Container, Nameable {
|
||||
@@ -420,7 +422,7 @@ public class Inventory implements Container, Nameable {
|
||||
break;
|
||||
}
|
||||
|
||||
- int i = stack.getMaxStackSize() - this.getItem(slotWithRemainingSpace).getCount();
|
||||
+ int i = fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(stack) - this.getItem(slotWithRemainingSpace).getCount(); // Lophine - Stackable ShulkerBoxes
|
||||
+ int i = org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(stack) - this.getItem(slotWithRemainingSpace).getCount(); // Leaves - item over-stack util
|
||||
if (this.add(slotWithRemainingSpace, stack.split(i)) && sendPacket && this.player instanceof ServerPlayer serverPlayer) {
|
||||
serverPlayer.connection.send(this.createInventoryUpdatePacket(slotWithRemainingSpace));
|
||||
}
|
||||
diff --git a/net/minecraft/world/entity/player/StackedItemContents.java b/net/minecraft/world/entity/player/StackedItemContents.java
|
||||
index 83ccde54c625d40dc595e000c533f60aa929bd5a..3206b599481eeab29c58bc1ca9cac6935da9963a 100644
|
||||
index 83ccde54c625d40dc595e000c533f60aa929bd5a..6779503888e6d311758cffde582b0efbd66a33de 100644
|
||||
--- a/net/minecraft/world/entity/player/StackedItemContents.java
|
||||
+++ b/net/minecraft/world/entity/player/StackedItemContents.java
|
||||
@@ -23,7 +23,7 @@ public class StackedItemContents {
|
||||
@@ -233,12 +211,12 @@ index 83ccde54c625d40dc595e000c533f60aa929bd5a..3206b599481eeab29c58bc1ca9cac693
|
||||
|
||||
public void accountStack(ItemStack stack) {
|
||||
- this.accountStack(stack, stack.getMaxStackSize());
|
||||
+ this.accountStack(stack, fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(stack)); // Lophine - Stackable Shulker Boxes
|
||||
+ this.accountStack(stack, org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(stack)); // Leaves - item over-stack util
|
||||
}
|
||||
|
||||
public void accountStack(ItemStack stack, int maxStackSize) {
|
||||
diff --git a/net/minecraft/world/entity/vehicle/ContainerEntity.java b/net/minecraft/world/entity/vehicle/ContainerEntity.java
|
||||
index 02d2efef2dc0f0e12eac0c71fa290af706f7694d..d042b3729a615bdabeb08f559ac895a731856545 100644
|
||||
index 02d2efef2dc0f0e12eac0c71fa290af706f7694d..99f109e2653eff10c011f380694bd77a76381cee 100644
|
||||
--- a/net/minecraft/world/entity/vehicle/ContainerEntity.java
|
||||
+++ b/net/minecraft/world/entity/vehicle/ContainerEntity.java
|
||||
@@ -163,7 +163,7 @@ public interface ContainerEntity extends Container, MenuProvider {
|
||||
@@ -246,62 +224,61 @@ index 02d2efef2dc0f0e12eac0c71fa290af706f7694d..d042b3729a615bdabeb08f559ac895a7
|
||||
this.unpackChestVehicleLootTable(null);
|
||||
this.getItemStacks().set(slot, stack);
|
||||
- stack.limitSize(this.getMaxStackSize(stack));
|
||||
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - stackable shulker boxes
|
||||
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - item over-stack util
|
||||
}
|
||||
|
||||
default SlotAccess getChestVehicleSlot(final int index) {
|
||||
diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java
|
||||
index 9b6cd35e4dfa894069238a1365db16050aa5542b..a928c2e50e43245542a8bc8e307acf27259bbf74 100644
|
||||
index fcb2e0a722ed94acf08da06f06b20f42652896bc..9489a50df2de3d641235153c5a0217d96c1f4fa9 100644
|
||||
--- a/net/minecraft/world/inventory/AbstractContainerMenu.java
|
||||
+++ b/net/minecraft/world/inventory/AbstractContainerMenu.java
|
||||
@@ -258,6 +258,14 @@ public abstract class AbstractContainerMenu {
|
||||
return list;
|
||||
@@ -295,6 +295,13 @@ public abstract class AbstractContainerMenu {
|
||||
this.sendAllDataToRemote();
|
||||
}
|
||||
|
||||
+ // Lophine start - Stackable ShulkerBoxes
|
||||
+ public void boardcastChangesSigleSlot(int slot, ItemStack item) {
|
||||
+ // Leaves start - item over-stack util
|
||||
+ public void sendSingleSlot(int slotIndex, ItemStack item) {
|
||||
+ if (this.synchronizer != null) {
|
||||
+ this.synchronizer.sendSlotChange(this, slot, item);
|
||||
+ this.synchronizer.sendSlotChange(this, slotIndex, item);
|
||||
+ }
|
||||
+ }
|
||||
+ // Lophine end - Stackable ShulkerBoxes
|
||||
+
|
||||
public void broadcastChanges() {
|
||||
for (int i = 0; i < this.slots.size(); i++) {
|
||||
ItemStack item = this.slots.get(i).getItem();
|
||||
@@ -451,7 +459,7 @@ public abstract class AbstractContainerMenu {
|
||||
+ // Leaves end - item over-stack util
|
||||
private void updateDataSlotListeners(int slotIndex, int value) {
|
||||
for (ContainerListener containerListener : this.containerListeners) {
|
||||
containerListener.dataChanged(this, slotIndex, value);
|
||||
@@ -451,7 +458,7 @@ public abstract class AbstractContainerMenu {
|
||||
&& (this.quickcraftType == 2 || carried1.getCount() >= this.quickcraftSlots.size())
|
||||
&& this.canDragTo(slot1)) {
|
||||
int i2 = slot1.hasItem() ? slot1.getItem().getCount() : 0;
|
||||
- int min = Math.min(itemStack.getMaxStackSize(), slot1.getMaxStackSize(itemStack));
|
||||
+ int min = Math.min(fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemStack), slot1.getMaxStackSize(itemStack)); // Lophine - Stackable ShulkerBoxes
|
||||
+ int min = Math.min(org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemStack), slot1.getMaxStackSize(itemStack)); // Leaves - item over-stack util
|
||||
int min1 = Math.min(getQuickCraftPlaceCount(this.quickcraftSlots, this.quickcraftType, itemStack) + i2, min);
|
||||
count -= min1 - i2;
|
||||
// slot1.setByPlayer(itemStack.copyWithCount(min1));
|
||||
@@ -563,7 +571,7 @@ public abstract class AbstractContainerMenu {
|
||||
@@ -563,7 +570,7 @@ public abstract class AbstractContainerMenu {
|
||||
slot.setByPlayer(carried2);
|
||||
}
|
||||
} else if (ItemStack.isSameItemSameComponents(carried, carried2)) {
|
||||
- Optional<ItemStack> optional1 = slot.tryRemove(carried.getCount(), carried2.getMaxStackSize() - carried2.getCount(), player);
|
||||
+ Optional<ItemStack> optional1 = slot.tryRemove(carried.getCount(), fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(carried2) - carried2.getCount(), player); // Lophine - Stackable ShulkerBoxes
|
||||
+ Optional<ItemStack> optional1 = slot.tryRemove(carried.getCount(), org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(carried2) - carried2.getCount(), player); // Leaves - item over-stack util
|
||||
optional1.ifPresent(itemStack2 -> {
|
||||
carried2.grow(itemStack2.getCount());
|
||||
slot.onTake(player, itemStack2);
|
||||
@@ -625,7 +633,7 @@ public abstract class AbstractContainerMenu {
|
||||
@@ -625,7 +632,7 @@ public abstract class AbstractContainerMenu {
|
||||
Slot slot2 = this.slots.get(slotId);
|
||||
if (slot2.hasItem()) {
|
||||
ItemStack itemStack = slot2.getItem();
|
||||
- this.setCarried(itemStack.copyWithCount(itemStack.getMaxStackSize()));
|
||||
+ this.setCarried(itemStack.copyWithCount(fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemStack))); // Lophine - Stackable ShulkerBoxes
|
||||
+ this.setCarried(itemStack.copyWithCount(org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemStack))); // Leaves - item over-stack util
|
||||
}
|
||||
} else if (clickType == ClickType.THROW && this.getCarried().isEmpty() && slotId >= 0) {
|
||||
Slot slot2 = this.slots.get(slotId);
|
||||
@@ -656,15 +664,15 @@ public abstract class AbstractContainerMenu {
|
||||
@@ -656,15 +663,15 @@ public abstract class AbstractContainerMenu {
|
||||
int maxStackSize = button == 0 ? 1 : -1;
|
||||
|
||||
for (int i3 = 0; i3 < 2; i3++) {
|
||||
- for (int i4 = count; i4 >= 0 && i4 < this.slots.size() && itemStack.getCount() < itemStack.getMaxStackSize(); i4 += maxStackSize) {
|
||||
+ for (int i4 = count; i4 >= 0 && i4 < this.slots.size() && itemStack.getCount() < fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemStack); i4 += maxStackSize) { // Lophine - Stackable ShulkerBoxes
|
||||
+ for (int i4 = count; i4 >= 0 && i4 < this.slots.size() && itemStack.getCount() < org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemStack); i4 += maxStackSize) { // Leaves - item over-stack util
|
||||
Slot slot3 = this.slots.get(i4);
|
||||
if (slot3.hasItem()
|
||||
&& canItemQuickReplace(slot3, itemStack, true)
|
||||
@@ -310,49 +287,49 @@ index 9b6cd35e4dfa894069238a1365db16050aa5542b..a928c2e50e43245542a8bc8e307acf27
|
||||
ItemStack item1 = slot3.getItem();
|
||||
- if (i3 != 0 || item1.getCount() != item1.getMaxStackSize()) {
|
||||
- ItemStack itemStack1 = slot3.safeTake(item1.getCount(), itemStack.getMaxStackSize() - itemStack.getCount(), player);
|
||||
+ if (i3 != 0 || item1.getCount() != fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(item1)) { // Lophine - stackable shulker boxes
|
||||
+ ItemStack itemStack1 = slot3.safeTake(item1.getCount(), fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemStack) - itemStack.getCount(), player); // Lophine - stackable shulker boxes
|
||||
+ if (i3 != 0 || item1.getCount() != org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(item1)) { // Leaves - item over-stack util
|
||||
+ ItemStack itemStack1 = slot3.safeTake(item1.getCount(), org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemStack) - itemStack.getCount(), player); // Leaves - item over-stack util
|
||||
itemStack.grow(itemStack1.getCount());
|
||||
}
|
||||
}
|
||||
@@ -766,7 +774,7 @@ public abstract class AbstractContainerMenu {
|
||||
@@ -766,7 +773,7 @@ public abstract class AbstractContainerMenu {
|
||||
i = endIndex - 1;
|
||||
}
|
||||
|
||||
- if (stack.isStackable()) {
|
||||
+ if (fun.bm.lophine.utils.ShulkerBoxesUtil.isStackable(stack)) { // Lophine - Stackable ShulkerBoxes
|
||||
+ if (org.leavesmc.leaves.util.ItemOverstackUtils.isStackable(stack)) { // Leaves - item over-stack util
|
||||
while (!stack.isEmpty() && (reverseDirection ? i >= startIndex : i < endIndex)) {
|
||||
Slot slot = this.slots.get(i);
|
||||
ItemStack item = slot.getItem();
|
||||
@@ -867,7 +875,7 @@ public abstract class AbstractContainerMenu {
|
||||
@@ -867,7 +874,7 @@ public abstract class AbstractContainerMenu {
|
||||
public static boolean canItemQuickReplace(@Nullable Slot slot, ItemStack stack, boolean stackSizeMatters) {
|
||||
boolean flag = slot == null || !slot.hasItem();
|
||||
return !flag && ItemStack.isSameItemSameComponents(stack, slot.getItem())
|
||||
- ? slot.getItem().getCount() + (stackSizeMatters ? 0 : stack.getCount()) <= stack.getMaxStackSize()
|
||||
+ ? slot.getItem().getCount() + (stackSizeMatters ? 0 : stack.getCount()) <= fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(stack) // Lophine - Stackable ShulkerBoxes
|
||||
+ ? slot.getItem().getCount() + (stackSizeMatters ? 0 : stack.getCount()) <= org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(stack) // Leaves - item over-stack util
|
||||
: flag;
|
||||
}
|
||||
|
||||
@@ -875,7 +883,7 @@ public abstract class AbstractContainerMenu {
|
||||
@@ -875,7 +882,7 @@ public abstract class AbstractContainerMenu {
|
||||
return switch (type) {
|
||||
case 0 -> Mth.floor((float)stack.getCount() / slots.size());
|
||||
case 1 -> 1;
|
||||
- case 2 -> stack.getMaxStackSize();
|
||||
+ case 2 -> fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(stack); // Lophine - Stackable ShulkerBoxes
|
||||
+ case 2 -> org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(stack); // Leaves - item over-stack util
|
||||
default -> stack.getCount();
|
||||
};
|
||||
}
|
||||
@@ -897,7 +905,7 @@ public abstract class AbstractContainerMenu {
|
||||
@@ -898,7 +905,7 @@ public abstract class AbstractContainerMenu {
|
||||
for (int i = 0; i < container.getContainerSize(); i++) {
|
||||
ItemStack item = container.getItem(i);
|
||||
if (!item.isEmpty()) {
|
||||
- f += (float)item.getCount() / container.getMaxStackSize(item);
|
||||
+ f += (float)item.getCount() / container.getMaxStackLeaves(item); // Leaves - stackable shulker boxes
|
||||
+ f += Math.clamp((float) item.getCount() / container.getMaxStackSize(item), 0f, 1f); // Leaves - item over-stack util
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/net/minecraft/world/inventory/MerchantContainer.java b/net/minecraft/world/inventory/MerchantContainer.java
|
||||
index 1e5dfb1f9e371fa23cdfa9280797aa0e183d4cd2..cf87267130c0aebd38206556261929d6f6383bc9 100644
|
||||
index 1e5dfb1f9e371fa23cdfa9280797aa0e183d4cd2..1a593d5bfc4574fd071604105422cd1d395648b8 100644
|
||||
--- a/net/minecraft/world/inventory/MerchantContainer.java
|
||||
+++ b/net/minecraft/world/inventory/MerchantContainer.java
|
||||
@@ -109,7 +109,7 @@ public class MerchantContainer implements Container {
|
||||
@@ -360,12 +337,12 @@ index 1e5dfb1f9e371fa23cdfa9280797aa0e183d4cd2..cf87267130c0aebd38206556261929d6
|
||||
public void setItem(int index, ItemStack stack) {
|
||||
this.itemStacks.set(index, stack);
|
||||
- stack.limitSize(this.getMaxStackSize(stack));
|
||||
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - stackable shulker boxes
|
||||
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - item over-stack util
|
||||
if (this.isPaymentSlot(index)) {
|
||||
this.updateSellItem();
|
||||
}
|
||||
diff --git a/net/minecraft/world/inventory/Slot.java b/net/minecraft/world/inventory/Slot.java
|
||||
index 5ceb8964476b40db4511bec91ff13c4f522a1357..dd978d1590fb896775a240ed87e38d85744017d3 100644
|
||||
index 5ceb8964476b40db4511bec91ff13c4f522a1357..f416d7976e41a06b5a8be93485eaa233ffceda5c 100644
|
||||
--- a/net/minecraft/world/inventory/Slot.java
|
||||
+++ b/net/minecraft/world/inventory/Slot.java
|
||||
@@ -75,7 +75,7 @@ public class Slot {
|
||||
@@ -373,41 +350,41 @@ index 5ceb8964476b40db4511bec91ff13c4f522a1357..dd978d1590fb896775a240ed87e38d85
|
||||
|
||||
public int getMaxStackSize(ItemStack stack) {
|
||||
- return Math.min(this.getMaxStackSize(), stack.getMaxStackSize());
|
||||
+ return Math.min(this.getMaxStackSize(), fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(stack)); // Lophine - Stackable ShulkerBoxes
|
||||
+ return Math.min(this.getMaxStackSize(), org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(stack)); // Leaves - item over-stack util
|
||||
}
|
||||
|
||||
@Nullable
|
||||
diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java
|
||||
index 6154da1158756cff072d7e8cae10e20eafed1eec..687249e1ad1cb09edbe2939f1db2f90e146cd664 100644
|
||||
index 33c3a5e39e8e1aa023baab76ef94f8aed481480e..a27e1d53abc2d8b8912226add33510067e10ed52 100644
|
||||
--- a/net/minecraft/world/item/ItemStack.java
|
||||
+++ b/net/minecraft/world/item/ItemStack.java
|
||||
@@ -162,7 +162,7 @@ public final class ItemStack implements DataComponentHolder {
|
||||
@@ -166,7 +166,7 @@ public final class ItemStack implements DataComponentHolder, ChangePublisher<net
|
||||
@Deprecated
|
||||
@Nullable
|
||||
private Item item;
|
||||
- PatchedDataComponentMap components;
|
||||
+ public PatchedDataComponentMap components; // Leaves - stackable shulker boxes
|
||||
+ public PatchedDataComponentMap components; // Leaves - item over-stack util
|
||||
@Nullable
|
||||
private Entity entityRepresentation;
|
||||
|
||||
@@ -189,7 +189,8 @@ public final class ItemStack implements DataComponentHolder {
|
||||
@@ -193,7 +193,8 @@ public final class ItemStack implements DataComponentHolder, ChangePublisher<net
|
||||
} else {
|
||||
Holder<Item> holder = Item.STREAM_CODEC.decode(buffer);
|
||||
DataComponentPatch dataComponentPatch = codec.decode(buffer);
|
||||
- return new ItemStack(holder, varInt, dataComponentPatch);
|
||||
+ ItemStack itemStack = new ItemStack(holder, varInt, dataComponentPatch);
|
||||
+ return fun.bm.lophine.utils.ShulkerBoxesUtil.decodeMaxStackSize(itemStack);
|
||||
+ return org.leavesmc.leaves.util.ItemOverstackUtils.decodeMaxStackSize(itemStack);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,13 +199,15 @@ public final class ItemStack implements DataComponentHolder {
|
||||
@@ -202,13 +203,15 @@ public final class ItemStack implements DataComponentHolder, ChangePublisher<net
|
||||
if (value.isEmpty() || value.getItem() == null) { // CraftBukkit - NPE fix itemstack.getItem()
|
||||
buffer.writeVarInt(0);
|
||||
} else {
|
||||
- buffer.writeVarInt(io.papermc.paper.util.sanitizer.ItemComponentSanitizer.sanitizeCount(io.papermc.paper.util.sanitizer.ItemObfuscationSession.currentSession(), value, value.getCount())); // Paper - potentially sanitize count
|
||||
- Item.STREAM_CODEC.encode(buffer, value.getItemHolder());
|
||||
+ // Leaves start - stackable shulker boxes
|
||||
+ final ItemStack itemStack = fun.bm.lophine.utils.ShulkerBoxesUtil.encodeMaxStackSize(value.copy());
|
||||
+ // Leaves start - item over-stack util
|
||||
+ final ItemStack itemStack = org.leavesmc.leaves.util.ItemOverstackUtils.encodeMaxStackSize(value.copy());
|
||||
+ buffer.writeVarInt(io.papermc.paper.util.sanitizer.ItemComponentSanitizer.sanitizeCount(io.papermc.paper.util.sanitizer.ItemObfuscationSession.currentSession(), itemStack, itemStack.getCount())); // Paper - potentially sanitize count
|
||||
+ Item.STREAM_CODEC.encode(buffer, itemStack.getItemHolder());
|
||||
// Paper start - adventure; conditionally render translatable components
|
||||
@@ -420,50 +397,17 @@ index 6154da1158756cff072d7e8cae10e20eafed1eec..687249e1ad1cb09edbe2939f1db2f90e
|
||||
} finally {
|
||||
net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(prev);
|
||||
}
|
||||
@@ -299,7 +302,7 @@ public final class ItemStack implements DataComponentHolder {
|
||||
@@ -303,7 +306,7 @@ public final class ItemStack implements DataComponentHolder, ChangePublisher<net
|
||||
|
||||
for (ItemStack itemStack : itemContainerContents.nonEmptyItems()) {
|
||||
int count = itemStack.getCount();
|
||||
- int maxStackSize = itemStack.getMaxStackSize();
|
||||
+ int maxStackSize = fun.bm.lophine.utils.ShulkerBoxesUtil.getItemMaxCount(itemStack); // Leaves - stackable shulker boxes
|
||||
+ int maxStackSize = org.leavesmc.leaves.util.ItemOverstackUtils.getItemStackMaxCount(itemStack); // Leaves - item over-stack util
|
||||
if (count > maxStackSize) {
|
||||
return DataResult.error(() -> "Item stack with count of " + count + " was larger than maximum: " + maxStackSize);
|
||||
}
|
||||
diff --git a/net/minecraft/world/level/block/AbstractCauldronBlock.java b/net/minecraft/world/level/block/AbstractCauldronBlock.java
|
||||
index 1a927b08f86c0585e62d8c4207954ab36d3316ee..e9899a03b15a2ca3dbd941775f14b041bbcfc85e 100644
|
||||
--- a/net/minecraft/world/level/block/AbstractCauldronBlock.java
|
||||
+++ b/net/minecraft/world/level/block/AbstractCauldronBlock.java
|
||||
@@ -57,9 +57,27 @@ public abstract class AbstractCauldronBlock extends Block {
|
||||
ItemStack stack, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hitResult
|
||||
) {
|
||||
CauldronInteraction cauldronInteraction = this.interactions.map().get(stack.getItem());
|
||||
- return cauldronInteraction.interact(state, level, pos, player, hand, stack, hitResult.getDirection()); // Paper - pass hit direction
|
||||
+ return wrapInteractor(cauldronInteraction, state, level, pos, player, hand, stack, hitResult.getDirection()); // Paper - pass hit direction // Leaves - stackable shulker boxes
|
||||
}
|
||||
|
||||
+ // Leaves start - stackable shulker boxes
|
||||
+ private InteractionResult wrapInteractor(CauldronInteraction cauldronBehavior, BlockState blockState, Level world, BlockPos blockPos, Player playerEntity, InteractionHand hand, ItemStack itemStack, net.minecraft.core.Direction hitDirection) {
|
||||
+ int count = -1;
|
||||
+ if (fun.bm.lophine.utils.ShulkerBoxesUtil.shouldCheck() && itemStack.getItem() instanceof net.minecraft.world.item.BlockItem bi &&
|
||||
+ bi.getBlock() instanceof ShulkerBoxBlock) {
|
||||
+ count = itemStack.getCount();
|
||||
+ }
|
||||
+ InteractionResult result = cauldronBehavior.interact(blockState, world, blockPos, playerEntity, hand, itemStack, hitDirection);
|
||||
+ if (count > 0 && result.consumesAction()) {
|
||||
+ ItemStack current = playerEntity.getItemInHand(hand);
|
||||
+ if (current.getItem() instanceof net.minecraft.world.item.BlockItem bi && bi.getBlock() instanceof ShulkerBoxBlock) {
|
||||
+ current.setCount(count);
|
||||
+ }
|
||||
+ }
|
||||
+ return result;
|
||||
+ }
|
||||
+ // Leaves end - stackable shulker boxes
|
||||
+
|
||||
@Override
|
||||
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
|
||||
return SHAPE;
|
||||
diff --git a/net/minecraft/world/level/block/CrafterBlock.java b/net/minecraft/world/level/block/CrafterBlock.java
|
||||
index 38b03c7b02bdfc579e5e126c12de3d878e26d188..33e24f2c3b63b2d3b55dfae2f2e55869abeed055 100644
|
||||
index 38b03c7b02bdfc579e5e126c12de3d878e26d188..caf31218a1aff84367da126ee1dc46b8c8a1e5d1 100644
|
||||
--- a/net/minecraft/world/level/block/CrafterBlock.java
|
||||
+++ b/net/minecraft/world/level/block/CrafterBlock.java
|
||||
@@ -192,7 +192,7 @@ public class CrafterBlock extends BaseEntityBlock {
|
||||
@@ -471,58 +415,58 @@ index 38b03c7b02bdfc579e5e126c12de3d878e26d188..33e24f2c3b63b2d3b55dfae2f2e55869
|
||||
Container containerAt = HopperBlockEntity.getContainerAt(level, pos.relative(direction));
|
||||
ItemStack itemStack = stack.copy();
|
||||
- if (containerAt != null && (containerAt instanceof CrafterBlockEntity || stack.getCount() > containerAt.getMaxStackSize(stack))) {
|
||||
+ if (containerAt != null && (containerAt instanceof CrafterBlockEntity || stack.getCount() > containerAt.getMaxStackLeaves(stack))) { // Leaves - stackable shulker boxes
|
||||
+ if (containerAt != null && (containerAt instanceof CrafterBlockEntity || stack.getCount() > containerAt.getMaxStackLeaves(stack))) { // Leaves - item over-stack util
|
||||
// CraftBukkit start - InventoryMoveItemEvent
|
||||
org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack);
|
||||
|
||||
diff --git a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
|
||||
index 36a72a11d28f99bfe85868461925b778cc01478e..831900a4ed6ca335707289391a095af7717a01a4 100644
|
||||
index d7a211220d5eba7e34b9083f06f39e9527962599..cef2d6d9a42ae36d6e2a0b31685a7738d2a400cf 100644
|
||||
--- a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
|
||||
+++ b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
|
||||
@@ -409,7 +409,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit
|
||||
@@ -411,7 +411,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit
|
||||
ItemStack itemStack = this.items.get(index);
|
||||
boolean flag = !stack.isEmpty() && ItemStack.isSameItemSameComponents(itemStack, stack);
|
||||
this.items.set(index, stack);
|
||||
- stack.limitSize(this.getMaxStackSize(stack));
|
||||
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - stackable shulker boxes
|
||||
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - item over-stack util
|
||||
if (index == 0 && !flag && this.level instanceof ServerLevel serverLevel) {
|
||||
this.cookingTotalTime = getTotalCookTime(serverLevel, this, this.recipeType, this.cookSpeedMultiplier); // Paper - cook speed multiplier API
|
||||
this.cookingTimer = 0;
|
||||
diff --git a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
|
||||
index 5a094257a31f0500278a706a418e1697f8810ffb..1f132848ae284668c05d6ca17d5890fa341e14cd 100644
|
||||
index 10b2cc20bbf3b70e4e09dbbe14b90506e6366158..1535bb8cec8692e8d26eaff090288f1b8f75c253 100644
|
||||
--- a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
|
||||
+++ b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
|
||||
@@ -145,7 +145,7 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co
|
||||
@@ -155,7 +155,7 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co
|
||||
@Override
|
||||
public void setItem(int slot, ItemStack stack) {
|
||||
this.getItems().set(slot, stack);
|
||||
- stack.limitSize(this.getMaxStackSize(stack));
|
||||
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - stackable shulker boxes
|
||||
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - item over-stack util
|
||||
this.setChanged();
|
||||
}
|
||||
|
||||
diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
|
||||
index 5de769e7bd56bb3355c3c711d46ce5e103ea4409..061501979195cf43c6da4d4b6cf6359251978bd3 100644
|
||||
index 2f8128a11e316a6ee6b283f9a411096e582c03b7..5b93eeba6c03738011551ce18332cebe9e278a8e 100644
|
||||
--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java
|
||||
+++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
|
||||
@@ -123,7 +123,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
|
||||
@@ -145,7 +145,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
|
||||
public void setItem(int index, ItemStack stack) {
|
||||
this.unpackLootTable(null);
|
||||
this.getItems().set(index, stack);
|
||||
- stack.limitSize(this.getMaxStackSize(stack));
|
||||
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - stackable shulker boxes
|
||||
+ stack.limitSize(this.getMaxStackLeaves(stack)); // Leaves - item over-stack util
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -690,9 +690,9 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
|
||||
@@ -742,9 +742,9 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
|
||||
if (item.isEmpty()) {
|
||||
// Spigot start - SPIGOT-6693, SimpleContainer#setItem
|
||||
ItemStack leftover = ItemStack.EMPTY; // Paper - Make hoppers respect inventory max stack size
|
||||
- if (!stack.isEmpty() && stack.getCount() > destination.getMaxStackSize()) {
|
||||
+ if (!stack.isEmpty() && (stack.getCount() > destination.getMaxStackSize() || stack.getCount() > stack.getMaxStackSize())) {
|
||||
+ if (!stack.isEmpty() && (stack.getCount() > destination.getMaxStackSize() || stack.getCount() > stack.getMaxStackSize())) { // Leaves - item over-stack util
|
||||
leftover = stack; // Paper - Make hoppers respect inventory max stack size
|
||||
- stack = stack.split(destination.getMaxStackSize());
|
||||
+ stack = stack.split(Math.min(destination.getMaxStackSize(), stack.getMaxStackSize()));
|
||||
+ stack = stack.split(Math.min(destination.getMaxStackSize(), stack.getMaxStackSize())); // Leaves - item over-stack util
|
||||
}
|
||||
// Spigot end
|
||||
IGNORE_TILE_UPDATES.set(Boolean.TRUE); // Paper - Perf: Optimize Hoppers // Folia - region threading
|
||||
//IGNORE_TILE_UPDATES.set(Boolean.TRUE); // Paper - Perf: Optimize Hoppers // Folia - region threading // Luminol - Use vanilla hopper logic
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
|
||||
Date: Mon, 7 Jul 2025 15:33:57 +0800
|
||||
Subject: [PATCH] Old Explosion Damage Calculator
|
||||
Subject: [PATCH] Leaves: Old Explosion Damage Calculator
|
||||
|
||||
Co-authored by: MC_XiaoHei <xor7xiaohei@gmail.com>
|
||||
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/3e96b237749a960f297f211d439ffc9ea7fd2381/leaves-server/minecraft-patches/features/0134-Old-wet-tnt-explode-behavior.patch)
|
||||
@@ -0,0 +1,216 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
|
||||
Date: Fri, 11 Apr 2025 16:53:57 +0800
|
||||
Subject: [PATCH] Leaves: Old raid behavior
|
||||
|
||||
Co-authored by: huanli233 <392352840@qq.com>
|
||||
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/79d9ef74c2684eb49060f07e1ab31c827326c9a5/leaves-server/minecraft-patches/features/0106-Old-raid-behavior.patch)
|
||||
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
diff --git a/net/minecraft/world/effect/BadOmenMobEffect.java b/net/minecraft/world/effect/BadOmenMobEffect.java
|
||||
index 80f17f33f670018240c854df589cf90cdeab6e70..cbfb4ae2dc30c2ee98ef37f44a95434a7968e320 100644
|
||||
--- a/net/minecraft/world/effect/BadOmenMobEffect.java
|
||||
+++ b/net/minecraft/world/effect/BadOmenMobEffect.java
|
||||
@@ -22,6 +22,11 @@ class BadOmenMobEffect extends MobEffect {
|
||||
&& !serverPlayer.isSpectator()
|
||||
&& level.getDifficulty() != Difficulty.PEACEFUL
|
||||
&& level.isVillage(serverPlayer.blockPosition())) {
|
||||
+ // Leaves start - Revert raid changes
|
||||
+ if (fun.bm.lophine.config.modules.misc.OldFeatureConfig.oldRaidBehavior) {
|
||||
+ return level.getRaids().createOrExtendRaid(serverPlayer, serverPlayer.blockPosition()) == null;
|
||||
+ }
|
||||
+ // Leaves end - Revert raid changes
|
||||
Raid raidAt = level.getRaidAt(serverPlayer.blockPosition());
|
||||
if (raidAt == null || raidAt.getRaidOmenLevel() < raidAt.getMaxRaidOmenLevel()) {
|
||||
serverPlayer.addEffect(new MobEffectInstance(MobEffects.RAID_OMEN, 600, amplifier));
|
||||
diff --git a/net/minecraft/world/entity/raid/Raid.java b/net/minecraft/world/entity/raid/Raid.java
|
||||
index 1910bff3f08147bfd63703097e30b9f88c84e337..5188ae86aff3051d45fe5e7d2fcc51233b7074d4 100644
|
||||
--- a/net/minecraft/world/entity/raid/Raid.java
|
||||
+++ b/net/minecraft/world/entity/raid/Raid.java
|
||||
@@ -263,7 +263,7 @@ public class Raid {
|
||||
}
|
||||
|
||||
public boolean absorbRaidOmen(ServerPlayer player) {
|
||||
- MobEffectInstance effect = player.getEffect(MobEffects.RAID_OMEN);
|
||||
+ MobEffectInstance effect = player.getEffect(fun.bm.lophine.config.modules.misc.OldFeatureConfig.oldRaidBehavior ? MobEffects.BAD_OMEN : MobEffects.RAID_OMEN); // Leaves - old Raid Behavior
|
||||
if (effect == null) {
|
||||
return false;
|
||||
} else {
|
||||
@@ -340,7 +340,13 @@ public class Raid {
|
||||
}
|
||||
|
||||
if (flag1) {
|
||||
- this.waveSpawnPos = this.getValidSpawnPos(level);
|
||||
+ // Leaves Start - old FindSpawnPosition
|
||||
+ if (fun.bm.lophine.config.modules.misc.OldFeatureConfig.oldRaidBehavior) {
|
||||
+ this.waveSpawnPos = this.preCalcRavagerSpawnLocation(level, this.raidCooldownTicks < 100 ? 1 : 0);
|
||||
+ } else {
|
||||
+ this.waveSpawnPos = this.getValidSpawnPos(level);
|
||||
+ }
|
||||
+ // Leaves End - old FindSpawnPosition
|
||||
}
|
||||
|
||||
if (this.raidCooldownTicks == 300 || this.raidCooldownTicks % 20 == 0) {
|
||||
@@ -375,7 +381,14 @@ public class Raid {
|
||||
int i = 0;
|
||||
|
||||
while (this.shouldSpawnGroup()) {
|
||||
- BlockPos blockPos = this.waveSpawnPos.orElseGet(() -> this.findRandomSpawnPos(level, 20));
|
||||
+ // Leaves Start - old FindSpawnPosition
|
||||
+ BlockPos blockPos;
|
||||
+ if (fun.bm.lophine.config.modules.misc.OldFeatureConfig.oldRaidBehavior) {
|
||||
+ blockPos = this.getRavagerSpawnLocation(level, i, 20);
|
||||
+ } else {
|
||||
+ blockPos = this.waveSpawnPos.orElseGet(() -> this.findRandomSpawnPos(level, 20));
|
||||
+ }
|
||||
+ // Leaves End - old FindSpawnPosition
|
||||
if (blockPos != null) {
|
||||
this.started = true;
|
||||
this.spawnGroup(level, blockPos);
|
||||
@@ -387,7 +400,7 @@ public class Raid {
|
||||
i++;
|
||||
}
|
||||
|
||||
- if (i > 5) {
|
||||
+ if (i > (fun.bm.lophine.config.modules.misc.OldFeatureConfig.oldRaidBehavior ? 3 : 5)) { // Leaves - old FindSpawnPosition
|
||||
org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(level, this, org.bukkit.event.raid.RaidStopEvent.Reason.UNSPAWNABLE); // CraftBukkit
|
||||
this.stop();
|
||||
break;
|
||||
@@ -683,7 +696,7 @@ public class Raid {
|
||||
int i2 = this.center.getX() + Mth.floor(Mth.cos(f2) * 32.0F * f) + level.random.nextInt(3) * Mth.floor(f);
|
||||
int i3 = this.center.getZ() + Mth.floor(Mth.sin(f2) * 32.0F * f) + level.random.nextInt(3) * Mth.floor(f);
|
||||
int height = level.getHeight(Heightmap.Types.WORLD_SURFACE, i2, i3);
|
||||
- if (Mth.abs(height - this.center.getY()) <= 96) {
|
||||
+ if (fun.bm.lophine.config.modules.misc.OldFeatureConfig.oldRaidBehavior || Mth.abs(height - this.center.getY()) <= 96) { // Leaves - skippable
|
||||
mutableBlockPos.set(i2, height, i3);
|
||||
if (!level.isVillage(mutableBlockPos) || i <= 7) {
|
||||
int i4 = 10;
|
||||
@@ -702,6 +715,36 @@ public class Raid {
|
||||
return null;
|
||||
}
|
||||
|
||||
+ // Leaves Start - old FindSpawnPosition
|
||||
+ @Nullable
|
||||
+ private BlockPos findRandomSpawnPos(ServerLevel level, int n, int n2) {
|
||||
+ int n3 = 2 - n;
|
||||
+ BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
|
||||
+ SpawnPlacementType spawnPlacementType = SpawnPlacements.getPlacementType(EntityType.RAVAGER);
|
||||
+ for (int i = 0; i < n2; ++i) {
|
||||
+ float f = level.random.nextFloat() * ((float)Math.PI * 2);
|
||||
+ int n4 = this.center.getX() + Mth.floor(Mth.cos(f) * 32.0f * (float)n3) + level.random.nextInt(5);
|
||||
+ int n5 = this.center.getZ() + Mth.floor(Mth.sin(f) * 32.0f * (float)n3) + level.random.nextInt(5);
|
||||
+ int n6 = level.getHeight(Heightmap.Types.WORLD_SURFACE, n4, n5);
|
||||
+ mutableBlockPos.set(n4, n6, n5);
|
||||
+ if (level.isVillage(mutableBlockPos) && n < 2) continue;
|
||||
+ if (!level.hasChunksAt(mutableBlockPos.getX() - 10, mutableBlockPos.getZ() - 10, mutableBlockPos.getX() + 10, mutableBlockPos.getZ() + 10) || !level.isPositionEntityTicking(mutableBlockPos) || !spawnPlacementType.isSpawnPositionOk(level, mutableBlockPos, EntityType.RAVAGER) && (!level.getBlockState((BlockPos)mutableBlockPos.below()).is(Blocks.SNOW) || !level.getBlockState(mutableBlockPos).isAir())) continue;
|
||||
+ return mutableBlockPos;
|
||||
+ }
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ private Optional<BlockPos> getValidSpawnPos(ServerLevel level, int n) {
|
||||
+ for (int i = 0; i < 3; ++i) {
|
||||
+ BlockPos blockPos = this.findRandomSpawnPos(level, n, 1);
|
||||
+ if (blockPos == null) continue;
|
||||
+ return Optional.of(blockPos);
|
||||
+ }
|
||||
+ return Optional.empty();
|
||||
+ }
|
||||
+ // Leaves End - old FindSpawnPosition
|
||||
+
|
||||
+
|
||||
private boolean addWaveMob(ServerLevel level, int wave, Raider raider) {
|
||||
// Folia start - make raids thread-safe
|
||||
if (!this.ownsRaid(level)) {
|
||||
@@ -864,4 +907,43 @@ public class Raid {
|
||||
this.spawnsPerWaveBeforeBonus = spawnsPerWaveBeforeBonus;
|
||||
}
|
||||
}
|
||||
+
|
||||
+ // Leaves start - old FindSpawnPosition
|
||||
+ private BlockPos getRavagerSpawnLocation(ServerLevel serverWorld, int proximity, int tries) {
|
||||
+ int i = proximity == 0 ? 2 : 2 - proximity;
|
||||
+ BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
|
||||
+ SpawnPlacementType spawnLocation = SpawnPlacements.getPlacementType(EntityType.RAVAGER);
|
||||
+
|
||||
+ for (int j = 0; j < tries; j++) {
|
||||
+ float f = serverWorld.random.nextFloat() * (float) (Math.PI * 2);
|
||||
+ int k = this.center.getX() + Mth.floor(Mth.cos(f) * 32.0F * (float) i + serverWorld.random.nextInt(5));
|
||||
+ int l = this.center.getZ() + Mth.floor(Mth.sin(f) * 32.0F * (float) i + serverWorld.random.nextInt(5));
|
||||
+ int m = serverWorld.getHeight(Heightmap.Types.WORLD_SURFACE, k, l);
|
||||
+ mutable.set(k, m, l);
|
||||
+
|
||||
+ if (serverWorld.isVillage(mutable) && proximity < 2) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ if (serverWorld.hasChunksAt(mutable.getX() - 10, mutable.getZ() - 10, mutable.getX() + 10, mutable.getZ() + 10)
|
||||
+ && serverWorld.isPositionEntityTicking(mutable)
|
||||
+ && (spawnLocation.isSpawnPositionOk(serverWorld, mutable, EntityType.RAVAGER)
|
||||
+ || serverWorld.getBlockState(mutable.below()).is(Blocks.SNOW)
|
||||
+ && serverWorld.getBlockState(mutable).isAir())
|
||||
+ ) {
|
||||
+ return mutable;
|
||||
+ }
|
||||
+ }
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ private Optional<BlockPos> preCalcRavagerSpawnLocation(ServerLevel serverWorld, int proximity) {
|
||||
+ for (int i = 0; i < 3; i++) {
|
||||
+ BlockPos blockPos = this.getRavagerSpawnLocation(serverWorld, proximity, 1);
|
||||
+ if (blockPos != null) {
|
||||
+ return Optional.of(blockPos);
|
||||
+ }
|
||||
+ }
|
||||
+ return Optional.empty();
|
||||
+ }
|
||||
+ // Leaves end - old FindSpawnPosition
|
||||
}
|
||||
diff --git a/net/minecraft/world/entity/raid/Raider.java b/net/minecraft/world/entity/raid/Raider.java
|
||||
index 3e30989c99cd11c8c94d6fd194d2969e3a7efda6..b1f1bcf7f5ac002550077d5e1d4efd5c67d18063 100644
|
||||
--- a/net/minecraft/world/entity/raid/Raider.java
|
||||
+++ b/net/minecraft/world/entity/raid/Raider.java
|
||||
@@ -128,6 +128,43 @@ public abstract class Raider extends PatrollingMonster {
|
||||
|
||||
currentRaid.removeFromRaid(serverLevel, this, false);
|
||||
}
|
||||
+
|
||||
+ // Leaves start - Revert raid changes
|
||||
+ if (this.level() instanceof ServerLevel) {
|
||||
+ if (fun.bm.lophine.config.modules.misc.OldFeatureConfig.oldRaidBehavior && !this.hasRaid()) {
|
||||
+ ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD);
|
||||
+ net.minecraft.world.entity.player.Player entityhuman = null;
|
||||
+ if (entity instanceof net.minecraft.world.entity.player.Player player) {
|
||||
+ entityhuman = player;
|
||||
+ } else if (entity instanceof net.minecraft.world.entity.animal.wolf.Wolf wolf) {
|
||||
+ LivingEntity entityliving = wolf.getOwner();
|
||||
+ if (wolf.isTame() && entityliving instanceof net.minecraft.world.entity.player.Player player) {
|
||||
+ entityhuman = player;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (entityhuman != null && !itemstack.isEmpty() && this.isCaptain()) {
|
||||
+ net.minecraft.world.effect.MobEffectInstance mobeffect = entityhuman.getEffect(net.minecraft.world.effect.MobEffects.BAD_OMEN);
|
||||
+ int i = 1;
|
||||
+
|
||||
+ if (mobeffect != null) {
|
||||
+ i += mobeffect.getAmplifier();
|
||||
+ entityhuman.removeEffectNoUpdate(net.minecraft.world.effect.MobEffects.BAD_OMEN);
|
||||
+ } else {
|
||||
+ --i;
|
||||
+ }
|
||||
+
|
||||
+ i = net.minecraft.util.Mth.clamp(i, 0, 4);
|
||||
+ net.minecraft.world.effect.MobEffectInstance mobeffect1 = new net.minecraft.world.effect.MobEffectInstance(net.minecraft.world.effect.MobEffects.BAD_OMEN, 120000, i, false, false, true);
|
||||
+
|
||||
+ if (!serverLevel.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_DISABLE_RAIDS)) {
|
||||
+ entityhuman.addEffect(mobeffect1, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN); // CraftBukkit
|
||||
+ }
|
||||
+ this.setPatrolLeader(false);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ // Leaves end - Revert raid changes
|
||||
}
|
||||
|
||||
super.die(cause);
|
||||
@@ -0,0 +1,44 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
|
||||
Date: Wed, 23 Jul 2025 20:28:40 +0800
|
||||
Subject: [PATCH] Leaves: Redstone Shears Wrench
|
||||
|
||||
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
|
||||
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/79d9ef74c2684eb49060f07e1ab31c827326c9a5/leaves-server/minecraft-patches/features/0009-Redstone-Shears-Wrench.patch)
|
||||
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
diff --git a/net/minecraft/world/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java
|
||||
index 6db566adf2d0df1d26221eda04aa01738df6d3d2..24ebfdf98e343a7892af621d032c35819156deec 100644
|
||||
--- a/net/minecraft/world/item/BlockItem.java
|
||||
+++ b/net/minecraft/world/item/BlockItem.java
|
||||
@@ -56,6 +56,14 @@ public class BlockItem extends Item {
|
||||
if (blockPlaceContext == null) {
|
||||
return InteractionResult.FAIL;
|
||||
} else {
|
||||
+ // Leaves start - shears wrench
|
||||
+ if (org.leavesmc.leaves.util.ShearsWrenchUtil.shouldSkipPlace(context)) {
|
||||
+ if (context.getPlayer() != null) {
|
||||
+ context.getPlayer().containerMenu.forceHeldSlot(blockPlaceContext.getHand());
|
||||
+ }
|
||||
+ return InteractionResult.FAIL;
|
||||
+ }
|
||||
+ // Leaves end - shears wrench
|
||||
BlockState placementState = this.getPlacementState(blockPlaceContext);
|
||||
// CraftBukkit start - special case for handling block placement with water lilies and snow buckets
|
||||
org.bukkit.block.BlockState bukkitState = null;
|
||||
diff --git a/net/minecraft/world/item/ShearsItem.java b/net/minecraft/world/item/ShearsItem.java
|
||||
index 8cf3e51e12f9cf98836657e722edb23943f9e866..5fd5e9fd4e1a79dd1a9d62a5cf0c308805979420 100644
|
||||
--- a/net/minecraft/world/item/ShearsItem.java
|
||||
+++ b/net/minecraft/world/item/ShearsItem.java
|
||||
@@ -80,7 +80,10 @@ public class ShearsItem extends Item {
|
||||
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
- return super.useOn(context);
|
||||
+ // Leaves start - shears wrench
|
||||
+ InteractionResult result = org.leavesmc.leaves.util.ShearsWrenchUtil.tryApplyRotate(context, blockState);
|
||||
+ return result == null ? super.useOn(context) : result;
|
||||
+ // Leaves end - shears wrench
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
|
||||
Date: Sun, 3 Aug 2025 15:19:23 +0800
|
||||
Subject: [PATCH] Leaves: Servux Protocol
|
||||
|
||||
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
|
||||
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/ea91106ae57fc4cc14e2e0225009cf9919072f7f/leaves-server/minecraft-patches/features/0090-Servux-Protocol.patch)
|
||||
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
diff --git a/net/minecraft/server/ServerTickRateManager.java b/net/minecraft/server/ServerTickRateManager.java
|
||||
index d0d90a25a10bbecfffceee1992af88c60d14fd87..187e48e78e69c80b25710f193813335ec643957f 100644
|
||||
--- a/net/minecraft/server/ServerTickRateManager.java
|
||||
+++ b/net/minecraft/server/ServerTickRateManager.java
|
||||
@@ -134,4 +134,10 @@ public class ServerTickRateManager extends TickRateManager {
|
||||
player.connection.send(ClientboundTickingStatePacket.from(this));
|
||||
player.connection.send(ClientboundTickingStepPacket.from(this));
|
||||
}
|
||||
+
|
||||
+ // Leaves start - servux
|
||||
+ public long getRemainingSprintTicks() {
|
||||
+ return remainingSprintTicks;
|
||||
+ }
|
||||
+ // Leaves end - servux
|
||||
}
|
||||
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
|
||||
index eb3aef1f5198592eda5dafe84b91271e861b4135..576dde41a4b48aac9079cca902f4371a31470af3 100644
|
||||
--- a/net/minecraft/server/level/ServerLevel.java
|
||||
+++ b/net/minecraft/server/level/ServerLevel.java
|
||||
@@ -2368,6 +2368,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
|
||||
}
|
||||
|
||||
this.lastSpawnChunkRadius = i;
|
||||
+ org.leavesmc.leaves.protocol.servux.ServuxHudDataProtocol.refreshSpawnMetadata = true; // Leaves - servux
|
||||
}
|
||||
|
||||
public LongSet getForceLoadedChunks() {
|
||||
@@ -1,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);
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -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 a24c332a8f9cb2ebc6f9926c6dabf1238238028d..58c2a6a3304e1bea25980b04046fe7c0017bc90d 100644
|
||||
index 593dbcda4b497291eb1ecdc64b947a8d279f8be6..661f797b39853104b16d75ca7ba0eda156a74af3 100644
|
||||
--- a/net/minecraft/server/players/PlayerList.java
|
||||
+++ b/net/minecraft/server/players/PlayerList.java
|
||||
@@ -1086,6 +1086,10 @@ public abstract class PlayerList {
|
||||
@@ -1087,6 +1087,10 @@ public abstract class PlayerList {
|
||||
player.getBukkitEntity().recalculatePermissions(); // CraftBukkit
|
||||
this.server.getCommands().sendCommands(player);
|
||||
} // Paper - Add sendOpLevel API
|
||||
@@ -115,10 +115,10 @@ index 5077a9ff7b78801bdc53536a37aee07b8d86ee4d..fe374f08b7829b846b2a1c2ac3ca500c
|
||||
// Paper end - Fix InventoryOpenEvent cancellation - moved up;
|
||||
player.awardStat(Stats.OPEN_ENDERCHEST);
|
||||
diff --git a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
|
||||
index d679ab599dfd0bdbdc3ab5530d7fcd1c38baf7fa..6b4e2956222a0ca7f72f73032dc98878fbd257ee 100644
|
||||
index de99652629c7301a4bc0e9ec48bed9a26a338f0d..4383e87560bb05acf9c50758f38a3cb69ee5c5c7 100644
|
||||
--- a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
|
||||
+++ b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
|
||||
@@ -56,7 +56,17 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
|
||||
@@ -56,7 +56,17 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity implemen
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
@@ -137,7 +137,7 @@ index d679ab599dfd0bdbdc3ab5530d7fcd1c38baf7fa..6b4e2956222a0ca7f72f73032dc98878
|
||||
public final ContainerOpenersCounter openersCounter = new ContainerOpenersCounter() {
|
||||
@Override
|
||||
protected void onOpen(Level level, BlockPos pos, BlockState state) {
|
||||
@@ -108,7 +118,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
|
||||
@@ -108,7 +118,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity implemen
|
||||
|
||||
@Override
|
||||
public int getContainerSize() {
|
||||
@@ -155,7 +155,7 @@ index d679ab599dfd0bdbdc3ab5530d7fcd1c38baf7fa..6b4e2956222a0ca7f72f73032dc98878
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -128,7 +147,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
|
||||
@@ -129,7 +148,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity implemen
|
||||
|
||||
@Override
|
||||
protected AbstractContainerMenu createMenu(int id, Inventory player) {
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
|
||||
Date: Fri, 15 Aug 2025 02:29:30 +0800
|
||||
Subject: [PATCH] Compatibility fix for Raid Revert and Cross Region Damage
|
||||
Trace
|
||||
|
||||
|
||||
diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
|
||||
index 3f4b1c54f9207ba1854a8c8da965cbaca5ea517d..a8348a073682eec31990e9ab963b11ad32cf8bf8 100644
|
||||
--- a/net/minecraft/world/entity/LivingEntity.java
|
||||
+++ b/net/minecraft/world/entity/LivingEntity.java
|
||||
@@ -1216,6 +1216,29 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
|
||||
}
|
||||
}
|
||||
|
||||
+ // Lophine Start - raid revert adapt Cross Region Damage trace
|
||||
+ public boolean addEffect(MobEffectInstance effectInstance, EntityPotionEffectEvent.Cause cause, boolean fireEvent, boolean async) {
|
||||
+ if (!async || ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this)) {
|
||||
+ return addEffect(effectInstance, this, cause, fireEvent);
|
||||
+ } else if (fun.bm.lophine.config.modules.experiment.EntityDamageSourceTraceConfig.enabled) {
|
||||
+ postToAddEffect(effectInstance, this, cause, fireEvent);
|
||||
+ return true;
|
||||
+ }
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ private void postToAddEffect(MobEffectInstance effectInstance, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause, boolean fireEvent) {
|
||||
+ if (entity != null)
|
||||
+ entity.getBukkitEntity().taskScheduler.schedule((Entity nmsEntity) -> {
|
||||
+ try {
|
||||
+ addEffect(effectInstance, nmsEntity, cause, fireEvent);
|
||||
+ } catch (Throwable ex) {
|
||||
+ LOGGER.error(ex.getMessage(), ex);
|
||||
+ }
|
||||
+ }, null, 1L );
|
||||
+ }
|
||||
+ // Lophine End - raid revert adapt Cross Region Damage trace
|
||||
+
|
||||
public boolean canBeAffected(MobEffectInstance effectInstance) {
|
||||
if (this.getType().is(EntityTypeTags.IMMUNE_TO_INFESTED)) {
|
||||
return !effectInstance.is(MobEffects.INFESTED);
|
||||
diff --git a/net/minecraft/world/entity/raid/Raider.java b/net/minecraft/world/entity/raid/Raider.java
|
||||
index b1f1bcf7f5ac002550077d5e1d4efd5c67d18063..6120f7b970ff05867524ee9f1433d46843d57483 100644
|
||||
--- a/net/minecraft/world/entity/raid/Raider.java
|
||||
+++ b/net/minecraft/world/entity/raid/Raider.java
|
||||
@@ -158,7 +158,7 @@ public abstract class Raider extends PatrollingMonster {
|
||||
net.minecraft.world.effect.MobEffectInstance mobeffect1 = new net.minecraft.world.effect.MobEffectInstance(net.minecraft.world.effect.MobEffects.BAD_OMEN, 120000, i, false, false, true);
|
||||
|
||||
if (!serverLevel.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_DISABLE_RAIDS)) {
|
||||
- entityhuman.addEffect(mobeffect1, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN); // CraftBukkit
|
||||
+ entityhuman.addEffect(mobeffect1, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN, true, fun.bm.lophine.config.modules.experiment.EntityDamageSourceTraceConfig.enabled); // Lophine - raid revert adapt Cross Region Damage trace
|
||||
}
|
||||
this.setPatrolLeader(false);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
|
||||
Date: Thu, 12 Jun 2025 04:44:24 +0800
|
||||
Subject: [PATCH] I18n support
|
||||
|
||||
|
||||
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
|
||||
index c6c6432c2efeb7b95c2894b188cd966321ae3186..1cd9af71ee66df5f277748c0a5a59a30bd57accc 100644
|
||||
--- a/net/minecraft/server/dedicated/DedicatedServer.java
|
||||
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
|
||||
@@ -172,6 +172,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
|
||||
this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
|
||||
// Paper end - initialize global and world-defaults configuration
|
||||
me.earthme.luminol.config.ConfigManager.loadConfigFiles(); // Luminol - load config file
|
||||
+ fun.bm.lophine.utils.ServerI18nUtil.init(); // Lophine - I18n support
|
||||
if (false) this.server.spark.enableEarlyIfRequested(); // Paper - spark // Luminol - Force disable builtin spark
|
||||
// Paper start - fix converting txt to json file; convert old users earlier after PlayerList creation but before file load/save
|
||||
if (this.convertOldUsers()) {
|
||||
@@ -0,0 +1,55 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
|
||||
Date: Sun, 3 Aug 2025 14:26:37 +0800
|
||||
Subject: [PATCH] Disable some check for operators
|
||||
|
||||
|
||||
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
index d63d8f7523938088bf9efc1c1b8ad9f0583338bb..b40059553089207ab1c6628beefadc674ec43d4e 100644
|
||||
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
@@ -367,7 +367,7 @@ public class ServerGamePacketListenerImpl
|
||||
this.tickCount++;
|
||||
this.knownMovePacketCount = this.receivedMovePacketCount;
|
||||
if (this.clientIsFloating && !this.player.isSleeping() && !this.player.isPassenger() && !this.player.isDeadOrDying()) {
|
||||
- if (++this.aboveGroundTickCount > this.getMaximumFlyingTicks(this.player)) {
|
||||
+ if (++this.aboveGroundTickCount > this.getMaximumFlyingTicks(this.player) && !(fun.bm.lophine.config.modules.misc.DisableCheckConfig.disableOpFlyCheck && this.player.hasPermissions(2))) { // Lophine - disable fly check for operator
|
||||
// LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString()); // Paper - Logging moved to net.minecraft.server.network.ServerCommonPacketListenerImpl.disconnect()
|
||||
this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_PLAYER); // Paper - use configurable kick message & kick event cause
|
||||
return;
|
||||
@@ -386,7 +386,7 @@ public class ServerGamePacketListenerImpl
|
||||
this.vehicleLastGoodY = this.lastVehicle.getY();
|
||||
this.vehicleLastGoodZ = this.lastVehicle.getZ();
|
||||
if (this.clientVehicleIsFloating && this.lastVehicle.getControllingPassenger() == this.player) {
|
||||
- if (++this.aboveGroundVehicleTickCount > this.getMaximumFlyingTicks(this.lastVehicle)) {
|
||||
+ if (++this.aboveGroundVehicleTickCount > this.getMaximumFlyingTicks(this.lastVehicle) && !(fun.bm.lophine.config.modules.misc.DisableCheckConfig.disableOpFlyCheck && this.player.hasPermissions(2))) { // Lophine - disable fly check for operator
|
||||
// LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString()); // Paper - Logging moved to net.minecraft.server.network.ServerCommonPacketListenerImpl.disconnect()
|
||||
this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_VEHICLE); // Paper - use configurable kick message & kick event cause
|
||||
return;
|
||||
@@ -644,7 +644,7 @@ public class ServerGamePacketListenerImpl
|
||||
d5 = d2 - rootVehicle.getZ();
|
||||
d7 = d3 * d3 + d4 * d4 + d5 * d5;
|
||||
boolean flag1 = false;
|
||||
- if (!me.earthme.luminol.config.modules.misc.DisableMovedWronglyThreshold.enabled && d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot
|
||||
+ if (!me.earthme.luminol.config.modules.misc.DisableMovedWronglyThreshold.enabled && d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !(fun.bm.lophine.config.modules.misc.DisableCheckConfig.disableOpMoveCheck && this.player.hasPermissions(2))) { // Spigot // Lophine - disable move check for operator
|
||||
flag1 = true; // Paper - diff on change, this should be moved wrongly
|
||||
LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", rootVehicle.getName().getString(), this.player.getName().getString(), Math.sqrt(d7));
|
||||
}
|
||||
@@ -1540,7 +1540,7 @@ public class ServerGamePacketListenerImpl
|
||||
// Paper start - Add fail move event
|
||||
io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY,
|
||||
toX, toY, toZ, toYaw, toPitch, true);
|
||||
- if (!event.isAllowed()) {
|
||||
+ if (!event.isAllowed() && !(fun.bm.lophine.config.modules.misc.DisableCheckConfig.disableOpMoveCheck && this.player.hasPermissions(2))) { // Lophine - disable move check for operator
|
||||
if (event.getLogWarning()) {
|
||||
LOGGER.warn("{} moved too quickly! {},{},{}", this.player.getName().getString(), d3, d4, d5);
|
||||
}
|
||||
@@ -1618,7 +1618,7 @@ public class ServerGamePacketListenerImpl
|
||||
// Paper start - Add fail move event
|
||||
io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_WRONGLY,
|
||||
toX, toY, toZ, toYaw, toPitch, true);
|
||||
- if (!event.isAllowed()) {
|
||||
+ if (!event.isAllowed() && !(fun.bm.lophine.config.modules.misc.DisableCheckConfig.disableOpMoveCheck && this.player.hasPermissions(2))) { // Lophine - disable move check for operator
|
||||
movedWrongly = true;
|
||||
if (event.getLogWarning())
|
||||
// Paper end
|
||||
@@ -0,0 +1,44 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
|
||||
Date: Thu, 12 Jun 2025 04:44:24 +0800
|
||||
Subject: [PATCH] I18n support
|
||||
|
||||
|
||||
diff --git a/net/minecraft/locale/Language.java b/net/minecraft/locale/Language.java
|
||||
index 7b9e2a1a208b46a69c16e6afd8b502259893574f..db46a31ea8a36562e548eb07f0c4cf9b9e8c3360 100644
|
||||
--- a/net/minecraft/locale/Language.java
|
||||
+++ b/net/minecraft/locale/Language.java
|
||||
@@ -65,7 +65,7 @@ public abstract class Language {
|
||||
};
|
||||
}
|
||||
|
||||
- private static void parseTranslations(BiConsumer<String, String> output, String languagePath) {
|
||||
+ public static void parseTranslations(BiConsumer<String, String> output, String languagePath) { // Lophine - private -> public
|
||||
try (InputStream resourceAsStream = Language.class.getResourceAsStream(languagePath)) {
|
||||
loadFromJson(resourceAsStream, output);
|
||||
} catch (JsonParseException | IOException var7) {
|
||||
diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
|
||||
index cf72cb8b418239b56c755486b03a321672e16bc3..b69c06607ca900beeebf4892a978442dad2db19b 100644
|
||||
--- a/net/minecraft/server/Main.java
|
||||
+++ b/net/minecraft/server/Main.java
|
||||
@@ -155,6 +155,8 @@ public class Main {
|
||||
return;
|
||||
}
|
||||
|
||||
+ fun.bm.lophine.utils.ServerI18nUtil.preInit(); // Lophine - I18n support
|
||||
+
|
||||
// Paper start - Detect headless JRE
|
||||
String awtException = io.papermc.paper.util.ServerEnvironment.awtDependencyCheck();
|
||||
if (awtException != null) {
|
||||
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
|
||||
index c6c6432c2efeb7b95c2894b188cd966321ae3186..1cd9af71ee66df5f277748c0a5a59a30bd57accc 100644
|
||||
--- a/net/minecraft/server/dedicated/DedicatedServer.java
|
||||
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
|
||||
@@ -172,6 +172,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
|
||||
this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
|
||||
// Paper end - initialize global and world-defaults configuration
|
||||
me.earthme.luminol.config.ConfigManager.loadConfigFiles(); // Luminol - load config file
|
||||
+ fun.bm.lophine.utils.ServerI18nUtil.init(); // Lophine - I18n support
|
||||
if (false) this.server.spark.enableEarlyIfRequested(); // Paper - spark // Luminol - Force disable builtin spark
|
||||
// Paper start - fix converting txt to json file; convert old users earlier after PlayerList creation but before file load/save
|
||||
if (this.convertOldUsers()) {
|
||||
+2
-2
@@ -5,7 +5,7 @@ Subject: [PATCH] Spawn invulnerable time
|
||||
|
||||
|
||||
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
|
||||
index b7bc75648634aefc549a69631ab6e401eb902e7d..0e2d575200f3ff3ec1600a0663152e3c37ea67b0 100644
|
||||
index 6b7c92fdf562dfa46eee8e8acbc82b02fc3226c5..144b2c9396b8162f9cce8b343d998907bf7fb5aa 100644
|
||||
--- a/net/minecraft/server/level/ServerPlayer.java
|
||||
+++ b/net/minecraft/server/level/ServerPlayer.java
|
||||
@@ -232,6 +232,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
|
||||
@@ -24,7 +24,7 @@ index b7bc75648634aefc549a69631ab6e401eb902e7d..0e2d575200f3ff3ec1600a0663152e3c
|
||||
if (this.invulnerableTime > 0) {
|
||||
this.invulnerableTime--;
|
||||
}
|
||||
@@ -1431,6 +1433,13 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
|
||||
@@ -1435,6 +1437,13 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
|
||||
if (this.isInvulnerableTo(level, damageSource)) {
|
||||
return false;
|
||||
} else {
|
||||
File diff suppressed because it is too large
Load Diff
+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 4f7077b592f84334483f8edf3656fccd5b90a9ca..26d5a96edd283a8f8fd50222e2671cef2c4b257b 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||||
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||||
@@ -497,6 +497,7 @@ public final class CraftServer implements Server {
|
||||
this.potionBrewer = new io.papermc.paper.potion.PaperPotionBrewer(console); // Paper - custom potion mixes
|
||||
datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper
|
||||
if (false) this.spark = new io.papermc.paper.SparksFly(this); // Paper - spark // Luminol - Force disable builtin spark
|
||||
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.init(); // Leaves - protocol
|
||||
}
|
||||
|
||||
public boolean getCommandBlockOverride(String command) {
|
||||
@@ -1115,6 +1116,7 @@ public final class CraftServer implements Server {
|
||||
if (false) this.spark.registerCommandBeforePlugins(this); // Paper - spark // Luminol - Force disable builtin spark
|
||||
this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*");
|
||||
this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions");
|
||||
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleServerReload(); // Leaves - protocol
|
||||
|
||||
int pollCount = 0;
|
||||
|
||||
+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
|
||||
+3
@@ -20,6 +20,9 @@ public class ContainerExpansionConfig implements IConfigModule {
|
||||
range: 1~64""")
|
||||
public static int shulkerCount = 1;
|
||||
|
||||
@ConfigInfo(baseName = "same_nbt_shulker_stackable")
|
||||
public static boolean nbtShulkerStackable = false;
|
||||
|
||||
@Override
|
||||
public EnumConfigCategory getCategory() {
|
||||
return EnumConfigCategory.MISC;
|
||||
|
||||
+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";
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -3,6 +3,7 @@ package fun.bm.lophine.config.modules.misc;
|
||||
import me.earthme.luminol.config.EnumConfigCategory;
|
||||
import me.earthme.luminol.config.IConfigModule;
|
||||
import me.earthme.luminol.config.flags.ConfigInfo;
|
||||
import me.earthme.luminol.config.flags.TransformedConfig;
|
||||
|
||||
public class OldFeatureConfig implements IConfigModule {
|
||||
@ConfigInfo(baseName = "spawn_invulnerable_time")
|
||||
@@ -11,12 +12,13 @@ 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;
|
||||
|
||||
@TransformedConfig(name = "give_bad_omen_when_kill_raid_captain", category = {"misc", "revert_raid_changes"}, transformComments = false)
|
||||
@ConfigInfo(baseName = "old_raid_behavior")
|
||||
public static boolean oldRaidBehavior = false;
|
||||
|
||||
@Override
|
||||
public EnumConfigCategory getCategory() {
|
||||
return EnumConfigCategory.MISC;
|
||||
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
package fun.bm.lophine.config.modules.misc;
|
||||
|
||||
import me.earthme.luminol.config.EnumConfigCategory;
|
||||
import me.earthme.luminol.config.IConfigModule;
|
||||
import me.earthme.luminol.config.flags.ConfigInfo;
|
||||
|
||||
public class RaidChangesConfig implements IConfigModule {
|
||||
@ConfigInfo(baseName = "allow_skip_cooldown", comments =
|
||||
"""
|
||||
Allow players with bad omen to skip a
|
||||
30-second cooldown and trigger attacks directly""")
|
||||
public static boolean trigger = false;
|
||||
|
||||
@ConfigInfo(baseName = "give_bad_omen_when_kill_raid_captain", comments =
|
||||
"""
|
||||
Enable players to obtain a bad omen
|
||||
effect when killing the raid captain""")
|
||||
public static boolean effect = false;
|
||||
|
||||
@ConfigInfo(baseName = "bad_omen_infinite", comments =
|
||||
"""
|
||||
Enable bad omen effect infinite time
|
||||
--- this config is not old version's function""")
|
||||
public static boolean infinite = false;
|
||||
|
||||
@ConfigInfo(baseName = "skip_height_check", comments =
|
||||
"""
|
||||
Disable y <= 96 check.
|
||||
If you enabled use_old_position_find, this config
|
||||
will useless and always behavior of enabled""")
|
||||
public static boolean heightCheck = false;
|
||||
|
||||
@ConfigInfo(baseName = "skip_self_raid_check", comments =
|
||||
"""
|
||||
Disable raid self check
|
||||
--- this config is not old version's function""")
|
||||
public static boolean selfCheck = false;
|
||||
|
||||
@ConfigInfo(baseName = "use_old_position_find", comments =
|
||||
"""
|
||||
Revert Old raid's find spawn position logic
|
||||
--- This revert MC-274911""")
|
||||
public static boolean posRevert = false;
|
||||
|
||||
@Override
|
||||
public EnumConfigCategory getCategory() {
|
||||
return EnumConfigCategory.MISC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBaseName() {
|
||||
return "revert_raid_changes";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package fun.bm.lophine.config.modules.misc;
|
||||
|
||||
import me.earthme.luminol.config.EnumConfigCategory;
|
||||
import me.earthme.luminol.config.IConfigModule;
|
||||
import me.earthme.luminol.config.flags.ConfigInfo;
|
||||
import me.earthme.luminol.config.flags.TransformedConfig;
|
||||
|
||||
public class RedStoneConfig implements IConfigModule {
|
||||
@TransformedConfig(name = "allow_skip_cooldown", category = {"misc", "redstone"})
|
||||
@ConfigInfo(baseName = "shears_rotate", comments =
|
||||
"""
|
||||
Allows you to use the Shears to right-click to rotate the block.""")
|
||||
public static boolean shears = false;
|
||||
|
||||
@Override
|
||||
public EnumConfigCategory getCategory() {
|
||||
return EnumConfigCategory.MISC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBaseName() {
|
||||
return "redstone";
|
||||
}
|
||||
}
|
||||
+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;
|
||||
|
||||
|
||||
@@ -6,6 +6,13 @@ import me.earthme.luminol.config.flags.ConfigInfo;
|
||||
import me.earthme.luminol.config.flags.TransformedConfig;
|
||||
|
||||
public class RemovedConfig implements IConfigModule {
|
||||
@TransformedConfig(name = "allow_skip_cooldown", category = {"misc", "revert_raid_changes"}, transform = false)
|
||||
@TransformedConfig(name = "bad_omen_infinite", category = {"misc", "revert_raid_changes"}, transform = false)
|
||||
@TransformedConfig(name = "skip_height_check", category = {"misc", "revert_raid_changes"}, transform = false)
|
||||
@TransformedConfig(name = "skip_self_raid_check", category = {"misc", "revert_raid_changes"}, transform = false)
|
||||
@TransformedConfig(name = "use_old_position_find", category = {"misc", "revert_raid_changes"}, transform = false)
|
||||
@TransformedConfig(name = "vanilla_hopper", category = {"misc", "redstone"}, transform = false)
|
||||
@TransformedConfig(name = "old_replaceable_by_mushrooms", category = {"misc", "old-feature"}, transform = false)
|
||||
@TransformedConfig(name = "old_nether_portal_collision", category = {"misc", "old-feature"}, transform = false)
|
||||
@TransformedConfig(name = "better_shulker_box", category = {"misc", "container_expansion"}, transform = false)
|
||||
@ConfigInfo(baseName = "removed", comments =
|
||||
|
||||
@@ -4,7 +4,10 @@ import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import fun.bm.lophine.config.modules.optimizations.LanguageConfig;
|
||||
import me.earthme.luminol.config.ConfigManager;
|
||||
import me.earthme.luminol.config.ConfigsInstance;
|
||||
import net.minecraft.DetectedVersion;
|
||||
import net.minecraft.locale.DeprecatedTranslationsInfo;
|
||||
import net.minecraft.locale.Language;
|
||||
@@ -12,8 +15,8 @@ import net.minecraft.network.chat.FormattedText;
|
||||
import net.minecraft.network.chat.Style;
|
||||
import net.minecraft.util.FormattedCharSequence;
|
||||
import net.minecraft.util.StringDecomposer;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -23,13 +26,13 @@ import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.file.*;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/*
|
||||
* authored by: Helvetica Volubi <suisuroru@blue-millennium.fun>
|
||||
@@ -37,30 +40,62 @@ import java.util.logging.Logger;
|
||||
* Some of diff form Leaves
|
||||
*/
|
||||
public class ServerI18nUtil {
|
||||
|
||||
private static final Logger logger = Logger.getLogger("LangLoader");
|
||||
private static final Logger logger = LogUtils.getClassLogger();
|
||||
private static final String VERSION = DetectedVersion.BUILT_IN.name();
|
||||
private static final String BASE_PATH = "cache/lophine/" + VERSION + "/";
|
||||
private static final String defaultLophineLangPath = "/assets/lophine/lang/en_us.json";
|
||||
private static final String manifestUrl = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
|
||||
private static final String resourceBaseUrl = "https://resources.download.minecraft.net/";
|
||||
|
||||
// pre-load
|
||||
private static CompletableFuture<Void> preloadTask;
|
||||
// paths
|
||||
private static String langPath;
|
||||
private static String assetsPath;
|
||||
private static String versionPath;
|
||||
private static String manifestPath;
|
||||
private static String langJsonPath;
|
||||
private static String lophineLangPath;
|
||||
|
||||
public static void init() {
|
||||
if (Objects.equals(LanguageConfig.lang, "en_us")) {
|
||||
return;
|
||||
}
|
||||
langPath = BASE_PATH + "lang/" + LanguageConfig.lang + ".json";
|
||||
langJsonPath = "minecraft/lang/" + LanguageConfig.lang + ".json";
|
||||
lophineLangPath = "/assets/Lophine/lang/" + LanguageConfig.lang + ".json";
|
||||
logger.info("Starting load language: {}", LanguageConfig.lang);
|
||||
preloadTask.thenAcceptAsync(v -> loadI18n(LanguageConfig.lang, 2));
|
||||
}
|
||||
|
||||
public static void preInit() {
|
||||
assetsPath = BASE_PATH + "assets.json";
|
||||
versionPath = BASE_PATH + VERSION + ".json";
|
||||
manifestPath = BASE_PATH + "manifest.json";
|
||||
langJsonPath = "minecraft/lang/" + LanguageConfig.lang + ".json";
|
||||
logger.info("Starting load language: " + LanguageConfig.lang);
|
||||
CompletableFuture.runAsync(() -> loadI18n(LanguageConfig.lang, 2));
|
||||
preloadTask = CompletableFuture.runAsync(() -> preLoadI18n(2));
|
||||
}
|
||||
|
||||
private static void preLoadI18n(int retryTime) {
|
||||
try {
|
||||
if (!Files.exists(Path.of(assetsPath))) {
|
||||
downloadAssets(true);
|
||||
}
|
||||
} catch (UnsupportedLanguageException e) {
|
||||
logger.warn("Unsupported language: {}", LanguageConfig.lang);
|
||||
// Fallback to English
|
||||
final ConfigsInstance configsInstance = ConfigManager.configfiles.get("lophine");
|
||||
configsInstance.setConfig(new String[]{"optimizations", "lang"}, "en_us");
|
||||
configsInstance.reloadAsync();
|
||||
} catch (Exception e) {
|
||||
if (e instanceof MalformedJsonException malformedJson) {
|
||||
malformedJson.clean();
|
||||
}
|
||||
logger.warn("Failed to download language list file: ", e);
|
||||
if (retryTime > 0) {
|
||||
preLoadI18n(retryTime - 1);
|
||||
} else {
|
||||
logger.error("Failed to download language list file for many times, skip pre-load language.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadI18n(String lang, int retryTime) {
|
||||
@@ -69,15 +104,16 @@ public class ServerI18nUtil {
|
||||
downloadLang(true);
|
||||
}
|
||||
Language.inject(createLangInstance());
|
||||
logger.info("Successfully loaded language: " + lang);
|
||||
logger.info("Successfully loaded language: {}", lang);
|
||||
} catch (Exception e) {
|
||||
logger.warning("Failed to load language file for " + lang + "\n" + e);
|
||||
if (e instanceof MalformedJsonException malformedJson) {
|
||||
malformedJson.clean();
|
||||
}
|
||||
logger.warn("Failed to load language file for {}", lang, e);
|
||||
if (retryTime > 0) {
|
||||
cleanCache();
|
||||
loadI18n(lang, retryTime - 1);
|
||||
} else {
|
||||
logger.severe("Failed to load for many times, use default lang \"en_us\" instead");
|
||||
cleanCache();
|
||||
logger.error("Failed to load for many times, use default lang \"en_us\" instead");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,6 +130,10 @@ public class ServerI18nUtil {
|
||||
|
||||
JsonObject langEntry = json.getAsJsonObject("objects").getAsJsonObject(langJsonPath);
|
||||
|
||||
if (langEntry == null) {
|
||||
throw new UnsupportedLanguageException();
|
||||
}
|
||||
|
||||
String hash = langEntry.get("hash").getAsString();
|
||||
if (hash == null || hash.length() < 2) {
|
||||
throw new IllegalArgumentException("Invalid hash value");
|
||||
@@ -145,37 +185,31 @@ public class ServerI18nUtil {
|
||||
fetchAndSave(versionUrl, versionPath);
|
||||
}
|
||||
|
||||
private static String createHttpResponse(String path) throws IOException, InterruptedException {
|
||||
private static byte[] fetch(String urlString) throws IOException, InterruptedException {
|
||||
try {
|
||||
HttpResponse<String> response;
|
||||
try (HttpClient httpClient = HttpClient.newHttpClient()) {
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(path))
|
||||
.uri(URI.create(urlString))
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
|
||||
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
}
|
||||
|
||||
int responseCode = response.statusCode();
|
||||
if (responseCode != 200) {
|
||||
logger.info("Unexpected response code: " + responseCode);
|
||||
logger.info("Response body: " + response.body());
|
||||
logger.info("Unexpected response code: {}", responseCode);
|
||||
logger.info("Response body: {}", response.body());
|
||||
throw new UnsupportedEncodingException("Unexpected response code");
|
||||
} else {
|
||||
return response.body();
|
||||
return response.body().getBytes();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warning("Error in getting info: " + e.getMessage());
|
||||
logger.warn("Error in getting info!");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] fetch(String urlString) throws IOException, InterruptedException {
|
||||
String ret = createHttpResponse(urlString);
|
||||
return ret.getBytes();
|
||||
}
|
||||
|
||||
private static void fetchAndSave(String url, String savePath) throws IOException, InterruptedException {
|
||||
byte[] data = fetch(url);
|
||||
Path outputPath = Path.of(savePath);
|
||||
@@ -183,23 +217,15 @@ public class ServerI18nUtil {
|
||||
Files.write(outputPath, data, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
|
||||
}
|
||||
|
||||
private static void cleanCache() {
|
||||
try {
|
||||
FileUtils.deleteDirectory(Path.of(BASE_PATH).toFile());
|
||||
} catch (IOException e) {
|
||||
logger.severe("Cache cleanup failed: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonObject loadJson(String path) {
|
||||
try {
|
||||
byte[] data = Files.readAllBytes(Paths.get(path));
|
||||
return JsonParser.parseString(new String(data)).getAsJsonObject();
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.warning("Corrupt json file: " + e);
|
||||
throw e;
|
||||
logger.warn("Corrupt json file!");
|
||||
throw new MalformedJsonException(e, path);
|
||||
} catch (Exception e) {
|
||||
logger.warning("Failed to load local JSON: " + e);
|
||||
logger.warn("Failed to load local JSON!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -208,6 +234,7 @@ public class ServerI18nUtil {
|
||||
DeprecatedTranslationsInfo deprecatedTranslationsInfo = DeprecatedTranslationsInfo.loadFromDefaultResource();
|
||||
Map<String, String> map = new HashMap<>();
|
||||
parseTranslations(map::put);
|
||||
loadLophineI18n(map::put);
|
||||
deprecatedTranslationsInfo.applyToMap(map);
|
||||
final Map<String, String> map1 = Map.copyOf(map);
|
||||
return new Language() {
|
||||
@@ -237,16 +264,52 @@ public class ServerI18nUtil {
|
||||
};
|
||||
}
|
||||
|
||||
private static void loadLophineI18n(BiConsumer<String, String> bi) {
|
||||
if (Language.class.getResource(lophineLangPath) != null) {
|
||||
Language.parseTranslations(bi, lophineLangPath);
|
||||
} else {
|
||||
loadLophineI18nDefault(bi);
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadLophineI18nDefault(BiConsumer<String, String> bi) {
|
||||
if (Language.class.getResource(defaultLophineLangPath) != null) {
|
||||
Language.parseTranslations(bi, defaultLophineLangPath);
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseTranslations(BiConsumer<String, String> output) throws IOException {
|
||||
Path filePath = Path.of(langPath);
|
||||
try (InputStream fileStream = Files.newInputStream(filePath)) {
|
||||
Language.loadFromJson(fileStream, output);
|
||||
} catch (NoSuchFileException noSuchFileException) {
|
||||
logger.warning("Couldn't find language file: " + langPath);
|
||||
throw noSuchFileException;
|
||||
} catch (NoSuchFileException e) {
|
||||
logger.warn("Couldn't find language file: {}", langPath);
|
||||
throw e;
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new MalformedJsonException(e, langPath);
|
||||
} catch (Exception e) {
|
||||
logger.warning("Failed to load language from filesystem " + filePath + "\n" + e);
|
||||
logger.warn("Failed to load language from filesystem {}", filePath);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private static class UnsupportedLanguageException extends Exception {
|
||||
}
|
||||
|
||||
private static class MalformedJsonException extends RuntimeException {
|
||||
private final Path path;
|
||||
|
||||
protected MalformedJsonException(Throwable root, String path) {
|
||||
super(root);
|
||||
this.path = Path.of(path);
|
||||
}
|
||||
|
||||
public void clean() {
|
||||
try {
|
||||
Files.delete(path);
|
||||
} catch (IOException e) {
|
||||
logger.info("Failed to delete malformed JSON file: {}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package fun.bm.lophine.utils;
|
||||
|
||||
import fun.bm.lophine.config.modules.misc.ContainerExpansionConfig;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemContainerContents;
|
||||
import net.minecraft.world.level.block.ShulkerBoxBlock;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ShulkerBoxesUtil {
|
||||
public static boolean shouldCheck() {
|
||||
return ContainerExpansionConfig.shulkerCount > 1 && ContainerExpansionConfig.shulkerCount <= 64;
|
||||
}
|
||||
|
||||
public static boolean checkShulkerBox(ItemStack itemStack) {
|
||||
return shouldCheck() && checkIsShulkerBox(itemStack);
|
||||
}
|
||||
|
||||
public static boolean checkIsShulkerBox(ItemStack itemStack) {
|
||||
return itemStack.getItem() instanceof BlockItem b && b.getBlock() instanceof ShulkerBoxBlock;
|
||||
}
|
||||
|
||||
public static int getItemMaxCount(ItemStack itemStack) {
|
||||
if (checkShulkerBox(itemStack)) {
|
||||
return Math.clamp(ContainerExpansionConfig.shulkerCount, 1, 64);
|
||||
}
|
||||
return itemStack.getMaxStackSize();
|
||||
}
|
||||
|
||||
public static int getShulkerBoxesMaxCountUnsafe() {
|
||||
return Math.clamp(ContainerExpansionConfig.shulkerCount, 1, 64);
|
||||
}
|
||||
|
||||
public static boolean emptyShulkerBoxCheck(@NotNull ItemStack stack) {
|
||||
return stack.getComponents().getOrDefault(DataComponents.CONTAINER, ItemContainerContents.EMPTY).stream().findAny().isEmpty();
|
||||
}
|
||||
|
||||
public static boolean isStackable(ItemStack itemStack) {
|
||||
return getItemMaxCount(itemStack) > 1 && (!itemStack.isDamageableItem() || !itemStack.isDamaged());
|
||||
}
|
||||
|
||||
public static int getItemStackMaxCountReal(ItemStack stack) {
|
||||
CompoundTag nbt = Optional.ofNullable(stack.get(DataComponents.CUSTOM_DATA)).orElse(CustomData.EMPTY).copyTag();
|
||||
return nbt.getInt("Lophine.RealStackSize").orElse(stack.getMaxStackSize());
|
||||
}
|
||||
|
||||
public static ItemStack encodeMaxStackSize(ItemStack itemStack) {
|
||||
int realMaxStackSize = getItemStackMaxCountReal(itemStack);
|
||||
int modifiedMaxStackSize = getItemMaxCount(itemStack);
|
||||
if (itemStack.getMaxStackSize() != modifiedMaxStackSize) {
|
||||
itemStack.set(DataComponents.MAX_STACK_SIZE, modifiedMaxStackSize);
|
||||
CompoundTag nbt = itemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
|
||||
nbt.putInt("Lophine.RealStackSize", realMaxStackSize);
|
||||
itemStack.set(DataComponents.CUSTOM_DATA, CustomData.of(nbt));
|
||||
}
|
||||
return itemStack;
|
||||
}
|
||||
|
||||
public static ItemStack decodeMaxStackSize(ItemStack itemStack) {
|
||||
int realMaxStackSize = getItemStackMaxCountReal(itemStack);
|
||||
if (itemStack.getMaxStackSize() != realMaxStackSize) {
|
||||
itemStack.set(DataComponents.MAX_STACK_SIZE, realMaxStackSize);
|
||||
CompoundTag nbt = itemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
|
||||
nbt.remove("Lophine.RealStackSize");
|
||||
if (nbt.isEmpty()) {
|
||||
itemStack.remove(DataComponents.CUSTOM_DATA);
|
||||
} else {
|
||||
itemStack.set(DataComponents.CUSTOM_DATA, CustomData.of(nbt));
|
||||
}
|
||||
}
|
||||
return itemStack;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
|
||||
*
|
||||
* Leaves is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Leaves is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.leavesmc.leaves.protocol.core;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import net.minecraft.network.Connection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public record Context(@NotNull GameProfile profile, Connection connection) {
|
||||
|
||||
}
|
||||
+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.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;
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
|
||||
*
|
||||
* Leaves is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Leaves is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package org.leavesmc.leaves.protocol.core;
|
||||
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
public interface LeavesCustomPayload extends CustomPacketPayload {
|
||||
|
||||
Type<? extends CustomPacketPayload> LEAVES_TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath("leaves", "custom_payload"));
|
||||
|
||||
@Override
|
||||
default @NotNull Type<? extends CustomPacketPayload> type() {
|
||||
return LEAVES_TYPE;
|
||||
}
|
||||
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface ID {
|
||||
}
|
||||
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Codec {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
|
||||
*
|
||||
* Leaves is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Leaves is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package org.leavesmc.leaves.protocol.core;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
public interface LeavesProtocol {
|
||||
|
||||
boolean isActive();
|
||||
|
||||
default int tickerInterval(String tickerID) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Register {
|
||||
String namespace();
|
||||
}
|
||||
}
|
||||
+401
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
|
||||
*
|
||||
* Leaves is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Leaves is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package org.leavesmc.leaves.protocol.core;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.leavesmc.leaves.protocol.core.invoker.*;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
public class LeavesProtocolManager {
|
||||
private static final Logger LOGGER = LogUtils.getClassLogger();
|
||||
|
||||
private static final Map<Class<? extends LeavesCustomPayload>, PayloadReceiverInvokerHolder> PAYLOAD_RECEIVERS = new HashMap<>();
|
||||
private static final Map<Class<? extends LeavesCustomPayload>, ResourceLocation> IDS = new HashMap<>();
|
||||
private static final Map<Class<? extends LeavesCustomPayload>, StreamCodec<? super RegistryFriendlyByteBuf, LeavesCustomPayload>> CODECS = new HashMap<>();
|
||||
private static final Map<ResourceLocation, StreamCodec<? super RegistryFriendlyByteBuf, LeavesCustomPayload>> ID2CODEC = new HashMap<>();
|
||||
|
||||
private static final Map<String, BytebufReceiverInvokerHolder> STRICT_BYTEBUF_RECEIVERS = new HashMap<>();
|
||||
private static final Map<String, BytebufReceiverInvokerHolder> NAMESPACED_BYTEBUF_RECEIVERS = new HashMap<>();
|
||||
private static final List<BytebufReceiverInvokerHolder> GENERIC_BYTEBUF_RECEIVERS = new ArrayList<>();
|
||||
|
||||
private static final Map<String, MinecraftRegisterInvokerHolder> STRICT_MINECRAFT_REGISTER = new HashMap<>();
|
||||
private static final Map<String, MinecraftRegisterInvokerHolder> NAMESPACED_MINECRAFT_REGISTER = new HashMap<>();
|
||||
private static final List<MinecraftRegisterInvokerHolder> WILD_MINECRAFT_REGISTER = new ArrayList<>();
|
||||
|
||||
private static final List<EmptyInvokerHolder<ProtocolHandler.Ticker>> TICKERS = new ArrayList<>();
|
||||
|
||||
private static final List<PlayerInvokerHolder<ProtocolHandler.PlayerJoin>> PLAYER_JOIN = new ArrayList<>();
|
||||
private static final List<PlayerInvokerHolder<ProtocolHandler.PlayerLeave>> PLAYER_LEAVE = new ArrayList<>();
|
||||
private static final List<EmptyInvokerHolder<ProtocolHandler.ReloadServer>> RELOAD_SERVER = new ArrayList<>();
|
||||
private static final List<EmptyInvokerHolder<ProtocolHandler.ReloadDataPack>> RELOAD_DATAPACK = new ArrayList<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void init() {
|
||||
for (Class<?> clazz : getClasses("org.leavesmc.leaves.protocol")) {
|
||||
if (LeavesCustomPayload.class.isAssignableFrom(clazz) && !clazz.equals(LeavesCustomPayload.class)) {
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
if (!Modifier.isStatic(field.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
final LeavesCustomPayload.ID id = field.getAnnotation(LeavesCustomPayload.ID.class);
|
||||
if (id != null && field.getType().equals(ResourceLocation.class)) {
|
||||
IDS.put((Class<? extends LeavesCustomPayload>) clazz, (ResourceLocation) field.get(null));
|
||||
}
|
||||
final LeavesCustomPayload.Codec codec = field.getAnnotation(LeavesCustomPayload.Codec.class);
|
||||
if (codec != null && field.getType().equals(StreamCodec.class)) {
|
||||
CODECS.put((Class<? extends LeavesCustomPayload>) clazz, (StreamCodec<? super RegistryFriendlyByteBuf, LeavesCustomPayload>) field.get(null));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
final LeavesProtocol.Register register = clazz.getAnnotation(LeavesProtocol.Register.class);
|
||||
if (register == null) {
|
||||
continue;
|
||||
}
|
||||
LeavesProtocol protocol;
|
||||
try {
|
||||
Constructor<?> constructor = clazz.getDeclaredConstructor();
|
||||
constructor.setAccessible(true);
|
||||
protocol = (LeavesProtocol) constructor.newInstance();
|
||||
} catch (Throwable throwable) {
|
||||
LOGGER.error("Failed to load class {}. {}", clazz.getName(), throwable);
|
||||
return;
|
||||
}
|
||||
|
||||
for (final Method method : clazz.getDeclaredMethods()) {
|
||||
if (method.isBridge() || method.isSynthetic()) {
|
||||
continue;
|
||||
}
|
||||
method.setAccessible(true);
|
||||
|
||||
final ProtocolHandler.Init init = method.getAnnotation(ProtocolHandler.Init.class);
|
||||
if (init != null) {
|
||||
InitInvokerHolder holder = new InitInvokerHolder(protocol, method, init);
|
||||
try {
|
||||
holder.invoke();
|
||||
} catch (RuntimeException exception) {
|
||||
LOGGER.error("Failed to invoke init method {} in {}, {}: {}", method.getName(), clazz.getName(), exception.getCause(), exception.getMessage());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
final ProtocolHandler.PayloadReceiver payloadReceiver = method.getAnnotation(ProtocolHandler.PayloadReceiver.class);
|
||||
if (payloadReceiver != null) {
|
||||
PAYLOAD_RECEIVERS.put(payloadReceiver.payload(), new PayloadReceiverInvokerHolder(protocol, method, payloadReceiver));
|
||||
continue;
|
||||
}
|
||||
|
||||
final ProtocolHandler.BytebufReceiver bytebufReceiver = method.getAnnotation(ProtocolHandler.BytebufReceiver.class);
|
||||
if (bytebufReceiver != null) {
|
||||
String key = bytebufReceiver.key();
|
||||
BytebufReceiverInvokerHolder holder = new BytebufReceiverInvokerHolder(protocol, method, bytebufReceiver);
|
||||
if (bytebufReceiver.onlyNamespace()) {
|
||||
NAMESPACED_BYTEBUF_RECEIVERS.put(key.isEmpty() ? register.namespace() : key, holder);
|
||||
} else {
|
||||
if (key.isEmpty()) {
|
||||
GENERIC_BYTEBUF_RECEIVERS.add(holder);
|
||||
} else {
|
||||
if (key.contains(":")) {
|
||||
STRICT_BYTEBUF_RECEIVERS.put(key, holder);
|
||||
} else {
|
||||
STRICT_BYTEBUF_RECEIVERS.put(register.namespace() + ":" + key, holder);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
final ProtocolHandler.Ticker ticker = method.getAnnotation(ProtocolHandler.Ticker.class);
|
||||
if (ticker != null) {
|
||||
TICKERS.add(new EmptyInvokerHolder<>(protocol, method, ticker));
|
||||
continue;
|
||||
}
|
||||
|
||||
final ProtocolHandler.PlayerJoin playerJoin = method.getAnnotation(ProtocolHandler.PlayerJoin.class);
|
||||
if (playerJoin != null) {
|
||||
PLAYER_JOIN.add(new PlayerInvokerHolder<>(protocol, method, playerJoin));
|
||||
continue;
|
||||
}
|
||||
|
||||
final ProtocolHandler.PlayerLeave playerLeave = method.getAnnotation(ProtocolHandler.PlayerLeave.class);
|
||||
if (playerLeave != null) {
|
||||
PLAYER_LEAVE.add(new PlayerInvokerHolder<>(protocol, method, playerLeave));
|
||||
continue;
|
||||
}
|
||||
|
||||
final ProtocolHandler.ReloadServer reloadServer = method.getAnnotation(ProtocolHandler.ReloadServer.class);
|
||||
if (reloadServer != null) {
|
||||
RELOAD_SERVER.add(new EmptyInvokerHolder<>(protocol, method, reloadServer));
|
||||
continue;
|
||||
}
|
||||
|
||||
final ProtocolHandler.ReloadDataPack reloadDataPack = method.getAnnotation(ProtocolHandler.ReloadDataPack.class);
|
||||
if (reloadDataPack != null) {
|
||||
RELOAD_DATAPACK.add(new EmptyInvokerHolder<>(protocol, method, reloadDataPack));
|
||||
continue;
|
||||
}
|
||||
|
||||
final ProtocolHandler.MinecraftRegister minecraftRegister = method.getAnnotation(ProtocolHandler.MinecraftRegister.class);
|
||||
if (minecraftRegister != null) {
|
||||
String key = minecraftRegister.key();
|
||||
MinecraftRegisterInvokerHolder holder = new MinecraftRegisterInvokerHolder(protocol, method, minecraftRegister);
|
||||
if (minecraftRegister.onlyNamespace()) {
|
||||
NAMESPACED_MINECRAFT_REGISTER.put(key.isEmpty() ? register.namespace() : key, holder);
|
||||
} else {
|
||||
if (key.isEmpty()) {
|
||||
WILD_MINECRAFT_REGISTER.add(holder);
|
||||
} else {
|
||||
if (key.contains(":")) {
|
||||
STRICT_MINECRAFT_REGISTER.put(key, holder);
|
||||
} else {
|
||||
STRICT_MINECRAFT_REGISTER.put(register.namespace() + ":" + key, holder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var idInfo : IDS.entrySet()) {
|
||||
var codec = CODECS.get(idInfo.getKey());
|
||||
if (codec == null) {
|
||||
throw new IllegalArgumentException("Payload " + idInfo.getKey() + " is not configured correctly");
|
||||
}
|
||||
ID2CODEC.put(idInfo.getValue(), codec);
|
||||
}
|
||||
}
|
||||
|
||||
public static LeavesCustomPayload decode(ResourceLocation location, FriendlyByteBuf buf) {
|
||||
var codec = ID2CODEC.get(location);
|
||||
if (codec == null) {
|
||||
return null;
|
||||
}
|
||||
return codec.decode(ProtocolUtils.decorate(buf));
|
||||
}
|
||||
|
||||
public static void encode(FriendlyByteBuf buf, LeavesCustomPayload payload) {
|
||||
var location = IDS.get(payload.getClass());
|
||||
var codec = CODECS.get(payload.getClass());
|
||||
if (location == null || codec == null) {
|
||||
throw new IllegalArgumentException("Payload " + payload.getClass() + " is not configured correctly " + location + " " + codec);
|
||||
}
|
||||
buf.writeResourceLocation(location);
|
||||
codec.encode(ProtocolUtils.decorate(buf), payload);
|
||||
}
|
||||
|
||||
public static void handlePayload(IdentifierSelector selector, LeavesCustomPayload payload) {
|
||||
PayloadReceiverInvokerHolder holder;
|
||||
if ((holder = PAYLOAD_RECEIVERS.get(payload.getClass())) != null) {
|
||||
holder.invoke(selector, payload);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean handleBytebuf(IdentifierSelector selector, ResourceLocation location, ByteBuf buf) {
|
||||
RegistryFriendlyByteBuf buf1 = ProtocolUtils.decorate(buf);
|
||||
BytebufReceiverInvokerHolder holder;
|
||||
if ((holder = STRICT_BYTEBUF_RECEIVERS.get(location.toString())) != null) {
|
||||
holder.invoke(selector, buf1);
|
||||
return true;
|
||||
}
|
||||
if ((holder = NAMESPACED_BYTEBUF_RECEIVERS.get(location.getNamespace())) != null) {
|
||||
if (holder.invoke(selector, buf1)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (var holder1 : GENERIC_BYTEBUF_RECEIVERS) {
|
||||
if (holder1.invoke(selector, buf1)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void handleTick(long tickCount) {
|
||||
for (var tickerInfo : TICKERS) {
|
||||
if (tickCount % tickerInfo.owner().tickerInterval(tickerInfo.handler().tickerId()) == 0) {
|
||||
tickerInfo.invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void handlePlayerJoin(ServerPlayer player) {
|
||||
sendKnownId(player);
|
||||
for (var join : PLAYER_JOIN) {
|
||||
join.invoke(player);
|
||||
}
|
||||
}
|
||||
|
||||
public static void handlePlayerLeave(ServerPlayer player) {
|
||||
for (var leave : PLAYER_LEAVE) {
|
||||
leave.invoke(player);
|
||||
}
|
||||
}
|
||||
|
||||
public static void handleServerReload() {
|
||||
for (var reload : RELOAD_SERVER) {
|
||||
reload.invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public static void handleDataPackReload() {
|
||||
for (var reload : RELOAD_DATAPACK) {
|
||||
reload.invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public static void handleMinecraftRegister(String channelId, IdentifierSelector selector) {
|
||||
ResourceLocation location = ResourceLocation.tryParse(channelId);
|
||||
if (location == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var wildHolder : WILD_MINECRAFT_REGISTER) {
|
||||
wildHolder.invoke(selector, location);
|
||||
}
|
||||
|
||||
MinecraftRegisterInvokerHolder holder;
|
||||
if ((holder = STRICT_MINECRAFT_REGISTER.get(location.toString())) != null) {
|
||||
holder.invoke(selector, location);
|
||||
}
|
||||
if ((holder = NAMESPACED_MINECRAFT_REGISTER.get(location.getNamespace())) != null) {
|
||||
holder.invoke(selector, location);
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendKnownId(ServerPlayer player) {
|
||||
Set<String> set = new HashSet<>();
|
||||
PAYLOAD_RECEIVERS.forEach((clazz, holder) -> {
|
||||
if (holder.owner().isActive()) {
|
||||
set.add(IDS.get(clazz).toString());
|
||||
}
|
||||
});
|
||||
STRICT_BYTEBUF_RECEIVERS.forEach((key, holder) -> {
|
||||
if (holder.owner().isActive()) {
|
||||
set.add(key);
|
||||
}
|
||||
});
|
||||
ProtocolUtils.sendBytebufPacket(player, ResourceLocation.fromNamespaceAndPath("minecraft", "register"), buf -> {
|
||||
for (String channel : set) {
|
||||
buf.writeBytes(channel.getBytes(StandardCharsets.US_ASCII));
|
||||
buf.writeByte(0);
|
||||
}
|
||||
buf.writerIndex(Math.max(buf.writerIndex() - 1, 0));
|
||||
});
|
||||
}
|
||||
|
||||
public static Set<Class<?>> getClasses(String pack) {
|
||||
Set<Class<?>> classes = new LinkedHashSet<>();
|
||||
String packageDirName = pack.replace('.', '/');
|
||||
Enumeration<URL> dirs;
|
||||
try {
|
||||
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
|
||||
while (dirs.hasMoreElements()) {
|
||||
URL url = dirs.nextElement();
|
||||
String protocol = url.getProtocol();
|
||||
if ("file".equals(protocol)) {
|
||||
String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
|
||||
findClassesInPackageByFile(pack, filePath, classes);
|
||||
} else if ("jar".equals(protocol)) {
|
||||
JarFile jar;
|
||||
try {
|
||||
jar = ((JarURLConnection) url.openConnection()).getJarFile();
|
||||
Enumeration<JarEntry> entries = jar.entries();
|
||||
findClassesInPackageByJar(pack, entries, packageDirName, classes);
|
||||
} catch (IOException exception) {
|
||||
LOGGER.warn("Failed to load jar file, {}: {}", exception.getCause(), exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
LOGGER.warn("Failed to load classes, {}: {}", exception.getCause(), exception.getMessage());
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
|
||||
private static void findClassesInPackageByFile(String packageName, String packagePath, Set<Class<?>> classes) {
|
||||
File dir = new File(packagePath);
|
||||
if (!dir.exists() || !dir.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
File[] dirfiles = dir.listFiles((file) -> file.isDirectory() || file.getName().endsWith(".class"));
|
||||
if (dirfiles != null) {
|
||||
for (File file : dirfiles) {
|
||||
if (file.isDirectory()) {
|
||||
findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classes);
|
||||
} else {
|
||||
String className = file.getName().substring(0, file.getName().length() - 6);
|
||||
try {
|
||||
classes.add(Class.forName(packageName + '.' + className));
|
||||
} catch (ClassNotFoundException exception) {
|
||||
LOGGER.warn("Failed to load class {}, {}: {}", className, exception.getCause(), exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void findClassesInPackageByJar(String packageName, Enumeration<JarEntry> entries, String packageDirName, Set<Class<?>> classes) {
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
String name = entry.getName();
|
||||
if (name.charAt(0) == '/') {
|
||||
name = name.substring(1);
|
||||
}
|
||||
if (name.startsWith(packageDirName)) {
|
||||
int idx = name.lastIndexOf('/');
|
||||
if (idx != -1) {
|
||||
packageName = name.substring(0, idx).replace('/', '.');
|
||||
}
|
||||
if (name.endsWith(".class") && !entry.isDirectory()) {
|
||||
String className = name.substring(packageName.length() + 1, name.length() - 6);
|
||||
try {
|
||||
classes.add(Class.forName(packageName + '.' + className));
|
||||
} catch (ClassNotFoundException exception) {
|
||||
LOGGER.warn("Failed to load class {}, {}: {}", className, exception.getCause(), exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
|
||||
*
|
||||
* Leaves is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Leaves is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package org.leavesmc.leaves.protocol.core;
|
||||
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
public class ProtocolHandler {
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Init {
|
||||
}
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface PayloadReceiver {
|
||||
Class<? extends LeavesCustomPayload> payload();
|
||||
|
||||
Stage stage() default Stage.GAME;
|
||||
}
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface BytebufReceiver {
|
||||
String key() default "";
|
||||
|
||||
boolean onlyNamespace() default false;
|
||||
|
||||
Stage stage() default Stage.GAME;
|
||||
}
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Ticker {
|
||||
String tickerId() default "";
|
||||
}
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface PlayerJoin {
|
||||
}
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface PlayerLeave {
|
||||
}
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ReloadServer {
|
||||
}
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface MinecraftRegister {
|
||||
String key() default "";
|
||||
|
||||
boolean onlyNamespace() default false;
|
||||
|
||||
Stage stage() default Stage.CONFIGURATION;
|
||||
}
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ReloadDataPack {
|
||||
}
|
||||
|
||||
public enum Stage {
|
||||
CONFIGURATION(Context.class),
|
||||
GAME(ServerPlayer.class);
|
||||
|
||||
private final Class<?> identifier;
|
||||
|
||||
Stage(Class<?> identifier) {
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
public Class<?> identifier() {
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
|
||||
*
|
||||
* Leaves is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Leaves is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package org.leavesmc.leaves.protocol.core;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.papermc.paper.ServerBuildInfo;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.network.protocol.common.custom.DiscardedPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.ServerCommonPacketListenerImpl;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ProtocolUtils {
|
||||
|
||||
private static final Function<ByteBuf, RegistryFriendlyByteBuf> bufDecorator = buf -> buf instanceof RegistryFriendlyByteBuf registry ? registry : new RegistryFriendlyByteBuf(buf, MinecraftServer.getServer().registryAccess());
|
||||
private static final byte[] EMPTY = new byte[0];
|
||||
|
||||
public static String buildProtocolVersion(String protocol) {
|
||||
return protocol + "-lophine-" + ServerBuildInfo.buildInfo().asString(ServerBuildInfo.StringRepresentation.VERSION_SIMPLE);
|
||||
}
|
||||
|
||||
public static void sendEmptyPacket(ServerPlayer player, ResourceLocation id) {
|
||||
player.connection.send(new ClientboundCustomPayloadPacket(new DiscardedPayload(id, EMPTY)));
|
||||
}
|
||||
|
||||
public static void sendBytebufPacket(@NotNull ServerPlayer player, ResourceLocation id, Consumer<? super RegistryFriendlyByteBuf> consumer) {
|
||||
RegistryFriendlyByteBuf buf = decorate(Unpooled.buffer());
|
||||
consumer.accept(buf);
|
||||
player.connection.send(new ClientboundCustomPayloadPacket(new DiscardedPayload(id, ByteBufUtil.getBytes(buf))));
|
||||
}
|
||||
|
||||
public static void sendPayloadPacket(ServerPlayer player, CustomPacketPayload payload) {
|
||||
player.connection.send(new ClientboundCustomPayloadPacket(payload));
|
||||
}
|
||||
|
||||
public static void sendEmptyPacket(Context context, ResourceLocation id) {
|
||||
context.connection().send(new ClientboundCustomPayloadPacket(new DiscardedPayload(id, EMPTY)));
|
||||
}
|
||||
|
||||
public static void sendBytebufPacket(@NotNull Context context, ResourceLocation id, Consumer<? super RegistryFriendlyByteBuf> consumer) {
|
||||
RegistryFriendlyByteBuf buf = decorate(Unpooled.buffer());
|
||||
consumer.accept(buf);
|
||||
context.connection().send(new ClientboundCustomPayloadPacket(new DiscardedPayload(id, ByteBufUtil.getBytes(buf))));
|
||||
}
|
||||
|
||||
public static void sendPayloadPacket(Context context, CustomPacketPayload payload) {
|
||||
context.connection().send(new ClientboundCustomPayloadPacket(payload));
|
||||
}
|
||||
|
||||
public static RegistryFriendlyByteBuf decorate(ByteBuf buf) {
|
||||
return bufDecorator.apply(buf);
|
||||
}
|
||||
|
||||
public static IdentifierSelector createSelector(ServerCommonPacketListenerImpl common) {
|
||||
ServerPlayer player = common instanceof ServerGamePacketListenerImpl game ? game.getPlayer() : null;
|
||||
return new IdentifierSelector(new Context(common.profile, common.connection), player);
|
||||
}
|
||||
|
||||
public static ByteBuf wrapNullable(byte @Nullable [] data) {
|
||||
return data == null ? Unpooled.wrappedBuffer(EMPTY) : Unpooled.wrappedBuffer(data);
|
||||
}
|
||||
}
|
||||
+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.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+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.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;
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
|
||||
*
|
||||
* Leaves is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Leaves is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.leavesmc.leaves.protocol.core.invoker;
|
||||
|
||||
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class EmptyInvokerHolder<T> extends AbstractInvokerHolder<T> {
|
||||
public EmptyInvokerHolder(LeavesProtocol owner, Method invoker, T handler) {
|
||||
super(owner, invoker, handler, null);
|
||||
}
|
||||
|
||||
public void invoke() {
|
||||
invoke0(false);
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
|
||||
*
|
||||
* Leaves is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Leaves is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.leavesmc.leaves.protocol.core.invoker;
|
||||
|
||||
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
|
||||
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class InitInvokerHolder extends AbstractInvokerHolder<ProtocolHandler.Init> {
|
||||
public InitInvokerHolder(LeavesProtocol owner, Method invoker, ProtocolHandler.Init handler) {
|
||||
super(owner, invoker, handler, null);
|
||||
}
|
||||
|
||||
public void invoke() {
|
||||
invoke0(true);
|
||||
}
|
||||
}
|
||||
+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.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);
|
||||
}
|
||||
}
|
||||
+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.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);
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
|
||||
*
|
||||
* Leaves is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Leaves is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.leavesmc.leaves.protocol.core.invoker;
|
||||
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class PlayerInvokerHolder<T> extends AbstractInvokerHolder<T> {
|
||||
public PlayerInvokerHolder(LeavesProtocol owner, Method invoker, T handler) {
|
||||
super(owner, invoker, handler, null, ServerPlayer.class);
|
||||
}
|
||||
|
||||
public void invoke(ServerPlayer player) {
|
||||
invoke0(false, player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
|
||||
*
|
||||
* Leaves is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Leaves is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.leavesmc.leaves.protocol.servux;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
// Powered by Servux(https://github.com/sakura-ryoko/servux)
|
||||
|
||||
/**
|
||||
* Network packet splitter code from QuickCarpet by skyrising
|
||||
*
|
||||
* @author skyrising
|
||||
* <p>
|
||||
* Updated by Sakura to work with newer versions by changing the Reading Session keys,
|
||||
* and using the HANDLER interface to send packets via the Payload system
|
||||
* <p>
|
||||
* Move to Leaves by violetc
|
||||
*/
|
||||
public class PacketSplitter {
|
||||
public static final int MAX_TOTAL_PER_PACKET_S2C = 1048576;
|
||||
public static final int MAX_PAYLOAD_PER_PACKET_S2C = MAX_TOTAL_PER_PACKET_S2C - 5;
|
||||
public static final int MAX_TOTAL_PER_PACKET_C2S = 32767;
|
||||
public static final int MAX_PAYLOAD_PER_PACKET_C2S = MAX_TOTAL_PER_PACKET_C2S - 5;
|
||||
public static final int DEFAULT_MAX_RECEIVE_SIZE_C2S = 1048576;
|
||||
public static final int DEFAULT_MAX_RECEIVE_SIZE_S2C = 67108864;
|
||||
|
||||
private static final Map<Long, ReadingSession> READING_SESSIONS = new HashMap<>();
|
||||
|
||||
public static boolean send(IPacketSplitterHandler handler, FriendlyByteBuf packet, ServerPlayer player) {
|
||||
return send(handler, packet, player, MAX_PAYLOAD_PER_PACKET_S2C);
|
||||
}
|
||||
|
||||
private static boolean send(IPacketSplitterHandler handler, FriendlyByteBuf packet, ServerPlayer player, int payloadLimit) {
|
||||
int len = packet.writerIndex();
|
||||
|
||||
packet.resetReaderIndex();
|
||||
|
||||
for (int offset = 0; offset < len; offset += payloadLimit) {
|
||||
int thisLen = Math.min(len - offset, payloadLimit);
|
||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(thisLen));
|
||||
|
||||
buf.resetWriterIndex();
|
||||
|
||||
if (offset == 0) {
|
||||
buf.writeVarInt(len);
|
||||
}
|
||||
|
||||
buf.writeBytes(packet, thisLen);
|
||||
handler.encode(player, buf);
|
||||
}
|
||||
|
||||
packet.release();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static FriendlyByteBuf receive(long key, FriendlyByteBuf buf) {
|
||||
return receive(key, buf, DEFAULT_MAX_RECEIVE_SIZE_S2C);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static FriendlyByteBuf receive(long key, FriendlyByteBuf buf, int maxLength) {
|
||||
return READING_SESSIONS.computeIfAbsent(key, ReadingSession::new).receive(buf, maxLength);
|
||||
}
|
||||
|
||||
public interface IPacketSplitterHandler {
|
||||
void encode(ServerPlayer player, FriendlyByteBuf buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* I had to fix the `Pair.of` key mappings, because they were removed from MC;
|
||||
* So I made it into a pre-shared random session 'key' between client and server.
|
||||
* Generated using 'long key = Random.create(Util.getMeasuringTimeMs()).nextLong();'
|
||||
* -
|
||||
* It can be shared to the receiving end via a separate packet; or it can just be
|
||||
* generated randomly on the receiving end per an expected Reading Session.
|
||||
* It needs to be stored and changed for every unique session.
|
||||
*/
|
||||
private static class ReadingSession {
|
||||
private final long key;
|
||||
private int expectedSize = -1;
|
||||
private FriendlyByteBuf received;
|
||||
|
||||
private ReadingSession(long key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private FriendlyByteBuf receive(FriendlyByteBuf data, int maxLength) {
|
||||
data.readerIndex(0);
|
||||
// data = PacketUtils.slice(data);
|
||||
|
||||
if (this.expectedSize < 0) {
|
||||
this.expectedSize = data.readVarInt();
|
||||
|
||||
if (this.expectedSize > maxLength) {
|
||||
throw new IllegalArgumentException("Payload too large");
|
||||
}
|
||||
|
||||
this.received = new FriendlyByteBuf(Unpooled.buffer(this.expectedSize));
|
||||
}
|
||||
|
||||
this.received.writeBytes(data.readBytes(data.readableBytes()));
|
||||
|
||||
if (this.received.writerIndex() >= this.expectedSize) {
|
||||
READING_SESSIONS.remove(this.key);
|
||||
return this.received;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
|
||||
*
|
||||
* Leaves is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Leaves is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.leavesmc.leaves.protocol.servux;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
|
||||
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
|
||||
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
|
||||
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
|
||||
import org.leavesmc.leaves.util.TagUtil;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
// Powered by Servux(https://github.com/sakura-ryoko/servux)
|
||||
|
||||
@LeavesProtocol.Register(namespace = "servux")
|
||||
public class ServuxEntityDataProtocol implements LeavesProtocol {
|
||||
private static final Logger LOGGER = LogUtils.getClassLogger();
|
||||
|
||||
public static final int PROTOCOL_VERSION = 1;
|
||||
|
||||
private static final Map<UUID, Long> readingSessionKeys = new HashMap<>();
|
||||
|
||||
@ProtocolHandler.PlayerJoin
|
||||
public static void onPlayerJoin(ServerPlayer player) {
|
||||
sendMetadata(player);
|
||||
}
|
||||
|
||||
@ProtocolHandler.PlayerLeave
|
||||
public static void onPlayerLeave(ServerPlayer player) {
|
||||
readingSessionKeys.remove(player.getUUID());
|
||||
}
|
||||
|
||||
@ProtocolHandler.PayloadReceiver(payload = EntityDataPayload.class)
|
||||
public static void onPacketReceive(ServerPlayer player, EntityDataPayload payload) {
|
||||
switch (payload.packetType) {
|
||||
case PACKET_C2S_METADATA_REQUEST -> sendMetadata(player);
|
||||
case PACKET_C2S_BLOCK_ENTITY_REQUEST -> onBlockEntityRequest(player, payload.pos);
|
||||
case PACKET_C2S_ENTITY_REQUEST -> onEntityRequest(player, payload.entityId);
|
||||
case PACKET_C2S_NBT_RESPONSE_DATA -> {
|
||||
UUID uuid = player.getUUID();
|
||||
long readingSessionKey;
|
||||
|
||||
if (!readingSessionKeys.containsKey(uuid)) {
|
||||
readingSessionKey = RandomSource.create(Util.getMillis()).nextLong();
|
||||
readingSessionKeys.put(uuid, readingSessionKey);
|
||||
} else {
|
||||
readingSessionKey = readingSessionKeys.get(uuid);
|
||||
}
|
||||
|
||||
FriendlyByteBuf fullPacket = PacketSplitter.receive(readingSessionKey, payload.buffer);
|
||||
|
||||
if (fullPacket != null) {
|
||||
readingSessionKeys.remove(uuid);
|
||||
LOGGER.warn("ServuxEntityDataProtocol,PACKET_C2S_NBT_RESPONSE_DATA NOT Implemented!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendMetadata(ServerPlayer player) {
|
||||
CompoundTag metadata = new CompoundTag();
|
||||
metadata.putString("name", "entity_data");
|
||||
metadata.putString("id", EntityDataPayload.CHANNEL.toString());
|
||||
metadata.putInt("version", PROTOCOL_VERSION);
|
||||
metadata.putString("servux", ServuxProtocol.SERVUX_STRING);
|
||||
|
||||
EntityDataPayload payload = new EntityDataPayload(EntityDataPayloadType.PACKET_S2C_METADATA);
|
||||
payload.nbt.merge(metadata);
|
||||
sendPacket(player, payload);
|
||||
}
|
||||
|
||||
public static void onBlockEntityRequest(ServerPlayer player, BlockPos pos) {
|
||||
io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
|
||||
player.level(),
|
||||
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(player.position()),
|
||||
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(player.position()),
|
||||
() -> {
|
||||
BlockEntity be = player.level().getBlockEntity(pos);
|
||||
CompoundTag nbt = be != null ? be.saveWithFullMetadata(player.registryAccess()) : new CompoundTag();
|
||||
|
||||
EntityDataPayload payload = new EntityDataPayload(EntityDataPayloadType.PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE);
|
||||
payload.pos = pos.immutable();
|
||||
payload.nbt.merge(nbt);
|
||||
sendPacket(player, payload);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static void onEntityRequest(ServerPlayer player, int entityId) {
|
||||
io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
|
||||
player.level(),
|
||||
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(player.position()),
|
||||
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(player.position()),
|
||||
() -> {
|
||||
Entity entity = player.level().getEntity(entityId);
|
||||
CompoundTag nbt = TagUtil.saveEntityWithoutId(entity);
|
||||
|
||||
EntityDataPayload payload = new EntityDataPayload(EntityDataPayloadType.PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE);
|
||||
payload.entityId = entityId;
|
||||
payload.nbt.merge(nbt);
|
||||
sendPacket(player, payload);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static void sendPacket(ServerPlayer player, EntityDataPayload payload) {
|
||||
if (payload.packetType == EntityDataPayloadType.PACKET_S2C_NBT_RESPONSE_START) {
|
||||
FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.buffer());
|
||||
buffer.writeNbt(payload.nbt);
|
||||
PacketSplitter.send(ServuxEntityDataProtocol::sendWithSplitter, buffer, player);
|
||||
} else {
|
||||
ProtocolUtils.sendPayloadPacket(player, payload);
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendWithSplitter(ServerPlayer player, FriendlyByteBuf buf) {
|
||||
EntityDataPayload payload = new EntityDataPayload(EntityDataPayloadType.PACKET_S2C_NBT_RESPONSE_DATA);
|
||||
payload.buffer = buf;
|
||||
payload.nbt = new CompoundTag();
|
||||
sendPacket(player, payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.entityProtocol;
|
||||
}
|
||||
|
||||
public enum EntityDataPayloadType {
|
||||
PACKET_S2C_METADATA(1),
|
||||
PACKET_C2S_METADATA_REQUEST(2),
|
||||
PACKET_C2S_BLOCK_ENTITY_REQUEST(3),
|
||||
PACKET_C2S_ENTITY_REQUEST(4),
|
||||
PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE(5),
|
||||
PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE(6),
|
||||
// For Packet Splitter (Oversize Packets, S2C)
|
||||
PACKET_S2C_NBT_RESPONSE_START(10),
|
||||
PACKET_S2C_NBT_RESPONSE_DATA(11),
|
||||
// For Packet Splitter (Oversize Packets, C2S)
|
||||
PACKET_C2S_NBT_RESPONSE_START(12),
|
||||
PACKET_C2S_NBT_RESPONSE_DATA(13);
|
||||
|
||||
public final int type;
|
||||
|
||||
EntityDataPayloadType(int type) {
|
||||
this.type = type;
|
||||
Helper.ID_TO_TYPE.put(type, this);
|
||||
}
|
||||
|
||||
public static EntityDataPayloadType fromId(int id) {
|
||||
return Helper.ID_TO_TYPE.get(id);
|
||||
}
|
||||
|
||||
private static final class Helper {
|
||||
static Map<Integer, EntityDataPayloadType> ID_TO_TYPE = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static class EntityDataPayload implements LeavesCustomPayload {
|
||||
|
||||
@ID
|
||||
public static final ResourceLocation CHANNEL = ServuxProtocol.id("entity_data");
|
||||
|
||||
@Codec
|
||||
public static final StreamCodec<FriendlyByteBuf, EntityDataPayload> CODEC = StreamCodec.of(
|
||||
(buf, payload) -> {
|
||||
buf.writeVarInt(payload.packetType.type);
|
||||
switch (payload.packetType) {
|
||||
case PACKET_C2S_BLOCK_ENTITY_REQUEST -> {
|
||||
buf.writeVarInt(payload.transactionId);
|
||||
buf.writeBlockPos(payload.pos);
|
||||
}
|
||||
case PACKET_C2S_ENTITY_REQUEST -> {
|
||||
buf.writeVarInt(payload.transactionId);
|
||||
buf.writeVarInt(payload.entityId);
|
||||
}
|
||||
case PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE -> {
|
||||
buf.writeBlockPos(payload.pos);
|
||||
buf.writeNbt(payload.nbt);
|
||||
}
|
||||
case PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE -> {
|
||||
buf.writeVarInt(payload.entityId);
|
||||
buf.writeNbt(payload.nbt);
|
||||
}
|
||||
case PACKET_S2C_NBT_RESPONSE_DATA, PACKET_C2S_NBT_RESPONSE_DATA ->
|
||||
buf.writeBytes(payload.buffer.copy());
|
||||
case PACKET_C2S_METADATA_REQUEST, PACKET_S2C_METADATA -> buf.writeNbt(payload.nbt);
|
||||
}
|
||||
},
|
||||
buf -> {
|
||||
EntityDataPayloadType type = EntityDataPayloadType.fromId(buf.readVarInt());
|
||||
if (type == null) {
|
||||
throw new IllegalStateException("invalid packet type received");
|
||||
}
|
||||
EntityDataPayload payload = new EntityDataPayload(type);
|
||||
switch (type) {
|
||||
case PACKET_C2S_BLOCK_ENTITY_REQUEST -> {
|
||||
buf.readVarInt();
|
||||
payload.pos = buf.readBlockPos().immutable();
|
||||
}
|
||||
case PACKET_C2S_ENTITY_REQUEST -> {
|
||||
buf.readVarInt();
|
||||
payload.entityId = buf.readVarInt();
|
||||
}
|
||||
case PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE -> {
|
||||
payload.pos = buf.readBlockPos().immutable();
|
||||
CompoundTag nbt = buf.readNbt();
|
||||
if (nbt != null) {
|
||||
payload.nbt.merge(nbt);
|
||||
}
|
||||
}
|
||||
case PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE -> {
|
||||
payload.entityId = buf.readVarInt();
|
||||
CompoundTag nbt = buf.readNbt();
|
||||
if (nbt != null) {
|
||||
payload.nbt.merge(nbt);
|
||||
}
|
||||
}
|
||||
case PACKET_S2C_NBT_RESPONSE_DATA, PACKET_C2S_NBT_RESPONSE_DATA -> {
|
||||
payload.buffer = new FriendlyByteBuf(buf.readBytes(buf.readableBytes()));
|
||||
payload.nbt = new CompoundTag();
|
||||
}
|
||||
case PACKET_C2S_METADATA_REQUEST, PACKET_S2C_METADATA -> {
|
||||
CompoundTag nbt = buf.readNbt();
|
||||
if (nbt != null) {
|
||||
payload.nbt.merge(nbt);
|
||||
}
|
||||
}
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
);
|
||||
|
||||
private final EntityDataPayloadType packetType;
|
||||
private final int transactionId;
|
||||
private int entityId;
|
||||
private BlockPos pos;
|
||||
private CompoundTag nbt;
|
||||
private FriendlyByteBuf buffer;
|
||||
|
||||
private EntityDataPayload(EntityDataPayloadType type) {
|
||||
this.packetType = type;
|
||||
this.transactionId = -1;
|
||||
this.entityId = -1;
|
||||
this.pos = BlockPos.ZERO;
|
||||
this.nbt = new CompoundTag();
|
||||
this.clearPacket();
|
||||
}
|
||||
|
||||
private void clearPacket() {
|
||||
if (this.buffer != null) {
|
||||
this.buffer.clear();
|
||||
this.buffer = new FriendlyByteBuf(Unpooled.buffer());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+375
@@ -0,0 +1,375 @@
|
||||
/*
|
||||
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
|
||||
*
|
||||
* Leaves is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Leaves is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.leavesmc.leaves.protocol.servux;
|
||||
|
||||
import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.Table;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.NbtOps;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.crafting.Recipe;
|
||||
import net.minecraft.world.item.crafting.RecipeHolder;
|
||||
import net.minecraft.world.level.GameRules;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
|
||||
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
|
||||
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
|
||||
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
|
||||
import org.leavesmc.leaves.protocol.servux.logger.DataLogger;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.*;
|
||||
|
||||
@LeavesProtocol.Register(namespace = "servux")
|
||||
public class ServuxHudDataProtocol implements LeavesProtocol {
|
||||
|
||||
public static final int PROTOCOL_VERSION = 2;
|
||||
|
||||
private static final List<ServerPlayer> players = new ArrayList<>();
|
||||
private static final int updateInterval = 80;
|
||||
|
||||
private static final HashMap<ServerPlayer, List<DataLogger.Type>> loggerPlayers = new HashMap<>();
|
||||
private static final HashMap<DataLogger.Type, DataLogger<?>> LOGGERS = new HashMap<>();
|
||||
private static final Table<DataLogger.Type, ServerPlayer, Tag> DATA = HashBasedTable.create();
|
||||
|
||||
public static boolean refreshSpawnMetadata = false;
|
||||
|
||||
@ProtocolHandler.Init
|
||||
private static void initializeLoggers() {
|
||||
if (!LOGGERS.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (DataLogger.Type type : DataLogger.Type.VALUES) {
|
||||
DataLogger<?> entry = type.init();
|
||||
if (entry != null) {
|
||||
LOGGERS.put(type, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ProtocolHandler.PlayerJoin
|
||||
private static void onPlayerJoin(ServerPlayer player) {
|
||||
sendHudMetadata(player);
|
||||
}
|
||||
|
||||
@ProtocolHandler.PlayerLeave
|
||||
private static void onPlayerLeave(ServerPlayer player) {
|
||||
players.remove(player);
|
||||
loggerPlayers.remove(player);
|
||||
}
|
||||
|
||||
@ProtocolHandler.PayloadReceiver(payload = HudDataPayload.class)
|
||||
public static void onPacketReceive(ServerPlayer player, HudDataPayload payload) {
|
||||
switch (payload.packetType) {
|
||||
case PACKET_C2S_METADATA_REQUEST -> {
|
||||
players.add(player);
|
||||
sendHudMetadata(player);
|
||||
}
|
||||
case PACKET_C2S_SPAWN_DATA_REQUEST -> refreshSpawnMetadata(player);
|
||||
case PACKET_C2S_RECIPE_MANAGER_REQUEST -> refreshRecipeManager(player);
|
||||
case PACKET_C2S_DATA_LOGGER_REQUEST -> refreshLoggers(player, payload.nbt);
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendHudMetadata(ServerPlayer player) {
|
||||
CompoundTag metadata = new CompoundTag();
|
||||
metadata.putString("name", "hud_metadata");
|
||||
metadata.putString("id", HudDataPayload.CHANNEL.toString());
|
||||
metadata.putInt("version", PROTOCOL_VERSION);
|
||||
metadata.putString("servux", ServuxProtocol.SERVUX_STRING);
|
||||
if (fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.hudLoggerProtocol) {
|
||||
CompoundTag nbt = new CompoundTag();
|
||||
for (DataLogger.Type type : DataLogger.Type.VALUES) {
|
||||
nbt.putBoolean(type.getSerializedName(), isLoggerTypeEnabled(type));
|
||||
}
|
||||
metadata.put("Loggers", nbt);
|
||||
}
|
||||
putWorldData(metadata);
|
||||
|
||||
sendPacket(player, new HudDataPayload(HudDataPayloadType.PACKET_S2C_METADATA, metadata));
|
||||
}
|
||||
|
||||
public static void refreshSpawnMetadata(ServerPlayer player) {
|
||||
CompoundTag metadata = new CompoundTag();
|
||||
metadata.putString("id", HudDataPayload.CHANNEL.toString());
|
||||
metadata.putString("servux", ServuxProtocol.SERVUX_STRING);
|
||||
putWorldData(metadata);
|
||||
|
||||
sendPacket(player, new HudDataPayload(HudDataPayloadType.PACKET_S2C_SPAWN_DATA, metadata));
|
||||
}
|
||||
|
||||
public static void refreshRecipeManager(ServerPlayer player) {
|
||||
Collection<RecipeHolder<?>> recipes = MinecraftServer.getServer().getRecipeManager().getRecipes();
|
||||
CompoundTag nbt = new CompoundTag();
|
||||
ListTag list = new ListTag();
|
||||
|
||||
recipes.forEach((recipeEntry -> {
|
||||
DataResult<Tag> dr = Recipe.CODEC.encodeStart(NbtOps.INSTANCE, recipeEntry.value());
|
||||
|
||||
if (dr.result().isPresent()) {
|
||||
CompoundTag entry = new CompoundTag();
|
||||
entry.putString("id_reg", recipeEntry.id().registry().toString());
|
||||
entry.putString("id_value", recipeEntry.id().location().toString());
|
||||
entry.put("recipe", dr.result().get());
|
||||
list.add(entry);
|
||||
}
|
||||
}));
|
||||
|
||||
nbt.put("RecipeManager", list);
|
||||
sendPacket(player, new HudDataPayload(HudDataPayloadType.PACKET_S2C_NBT_RESPONSE_START, nbt));
|
||||
}
|
||||
|
||||
public static void refreshWeatherData(ServerPlayer player) {
|
||||
ServerLevel level = MinecraftServer.getServer().overworld();
|
||||
if (!level.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CompoundTag nbt = new CompoundTag();
|
||||
nbt.putString("id", HudDataPayload.CHANNEL.toString());
|
||||
nbt.putString("servux", ServuxProtocol.SERVUX_STRING);
|
||||
|
||||
if (level.serverLevelData.isRaining() && level.serverLevelData.getRainTime() > -1) {
|
||||
nbt.putInt("SetRaining", level.serverLevelData.getRainTime());
|
||||
nbt.putBoolean("isRaining", true);
|
||||
} else {
|
||||
nbt.putBoolean("isRaining", false);
|
||||
}
|
||||
|
||||
if (level.serverLevelData.isThundering() && level.serverLevelData.getThunderTime() > -1) {
|
||||
nbt.putInt("SetThundering", level.serverLevelData.getThunderTime());
|
||||
nbt.putBoolean("isThundering", true);
|
||||
} else {
|
||||
nbt.putBoolean("isThundering", false);
|
||||
}
|
||||
|
||||
if (level.serverLevelData.getClearWeatherTime() > -1) {
|
||||
nbt.putInt("SetClear", level.serverLevelData.getClearWeatherTime());
|
||||
}
|
||||
|
||||
sendPacket(player, new HudDataPayload(HudDataPayloadType.PACKET_S2C_WEATHER_TICK, nbt));
|
||||
}
|
||||
|
||||
private static void putWorldData(@NotNull CompoundTag metadata) {
|
||||
ServerLevel level = MinecraftServer.getServer().overworld();
|
||||
BlockPos spawnPos = level.levelData.getSpawnPos();
|
||||
metadata.putInt("spawnPosX", spawnPos.getX());
|
||||
metadata.putInt("spawnPosY", spawnPos.getY());
|
||||
metadata.putInt("spawnPosZ", spawnPos.getZ());
|
||||
metadata.putInt("spawnChunkRadius", level.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
|
||||
|
||||
if (fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.hudMetadataShareSeed) {
|
||||
metadata.putLong("worldSeed", level.getSeed());
|
||||
}
|
||||
}
|
||||
|
||||
public static void refreshLoggers(ServerPlayer player, @Nonnull CompoundTag nbt) {
|
||||
if (!player.getBukkitEntity().hasPermission("servux.provider.hud_data.logger")) {
|
||||
player.sendSystemMessage(Component.translatable("servux.hud_data.error.insufficient_for_loggers", "any"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (nbt.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<DataLogger.Type> list = new ArrayList<>();
|
||||
|
||||
for (String key : nbt.keySet()) {
|
||||
DataLogger.Type type = DataLogger.Type.fromStringStatic(key);
|
||||
|
||||
if (type != null && nbt.getBooleanOr(key, false)) {
|
||||
list.add(type);
|
||||
}
|
||||
}
|
||||
if (!list.isEmpty()) {
|
||||
loggerPlayers.put(player, list);
|
||||
} else {
|
||||
loggerPlayers.remove(player);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isLoggerTypeEnabled(DataLogger.Type type) {
|
||||
return fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.hudEnabledLoggers.contains(type.getSerializedName());
|
||||
}
|
||||
|
||||
@ProtocolHandler.Ticker
|
||||
public void protocolTick() {
|
||||
for (ServerPlayer player : players) {
|
||||
if (refreshSpawnMetadata) {
|
||||
refreshSpawnMetadata(player);
|
||||
}
|
||||
refreshWeatherData(player);
|
||||
}
|
||||
refreshSpawnMetadata = false;
|
||||
}
|
||||
|
||||
@ProtocolHandler.Ticker(tickerId = "logger")
|
||||
public void loggerTick() {
|
||||
if (!fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.hudLoggerProtocol) {
|
||||
return;
|
||||
}
|
||||
|
||||
MinecraftServer server = MinecraftServer.getServer();
|
||||
|
||||
if (server.getTickCount() % fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.hudUpdateInterval == 0) {
|
||||
LOGGERS.forEach((type, logger) -> {
|
||||
if (!isLoggerTypeEnabled(type)) {
|
||||
return;
|
||||
}
|
||||
for (ServerPlayer player : players) {
|
||||
Tag ret = logger.getResult(server, this, player);
|
||||
if (ret != null) {
|
||||
DATA.put(type, player, ret);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (ServerPlayer player : loggerPlayers.keySet()) {
|
||||
List<DataLogger.Type> list = loggerPlayers.get(player);
|
||||
if (list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
CompoundTag nbt = new CompoundTag();
|
||||
for (DataLogger.Type type : list) {
|
||||
Tag data = DATA.get(type, player);
|
||||
if (data != null) {
|
||||
nbt.put(type.getSerializedName(), data);
|
||||
DATA.remove(type, player);
|
||||
}
|
||||
}
|
||||
sendPacket(player, new HudDataPayload(HudDataPayloadType.PACKET_S2C_DATA_LOGGER_TICK, nbt));
|
||||
}
|
||||
}
|
||||
|
||||
public void applyData(DataLogger.Type type, ServerPlayer player, Tag tag) {
|
||||
DATA.put(type, player, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.hudMetadataProtocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int tickerInterval(String tickerID) {
|
||||
return tickerID.equals("logger") ? 1 : updateInterval;
|
||||
}
|
||||
|
||||
public static void sendPacket(ServerPlayer player, HudDataPayload payload) {
|
||||
if (payload.packetType == HudDataPayloadType.PACKET_S2C_NBT_RESPONSE_START) {
|
||||
FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.buffer());
|
||||
buffer.writeNbt(payload.nbt);
|
||||
PacketSplitter.send(ServuxHudDataProtocol::sendWithSplitter, buffer, player);
|
||||
} else {
|
||||
ProtocolUtils.sendPayloadPacket(player, payload);
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendWithSplitter(ServerPlayer player, FriendlyByteBuf buf) {
|
||||
sendPacket(player, new HudDataPayload(HudDataPayloadType.PACKET_S2C_NBT_RESPONSE_DATA, buf));
|
||||
}
|
||||
|
||||
public enum HudDataPayloadType {
|
||||
PACKET_S2C_METADATA(1),
|
||||
PACKET_C2S_METADATA_REQUEST(2),
|
||||
PACKET_S2C_SPAWN_DATA(3),
|
||||
PACKET_C2S_SPAWN_DATA_REQUEST(4),
|
||||
PACKET_S2C_WEATHER_TICK(5),
|
||||
PACKET_C2S_RECIPE_MANAGER_REQUEST(6),
|
||||
PACKET_S2C_DATA_LOGGER_TICK(7),
|
||||
PACKET_C2S_DATA_LOGGER_REQUEST(8),
|
||||
// For Packet Splitter (Oversize Packets, S2C)
|
||||
PACKET_S2C_NBT_RESPONSE_START(10),
|
||||
PACKET_S2C_NBT_RESPONSE_DATA(11);
|
||||
|
||||
public final int type;
|
||||
|
||||
HudDataPayloadType(int type) {
|
||||
this.type = type;
|
||||
Helper.ID_TO_TYPE.put(type, this);
|
||||
}
|
||||
|
||||
public static HudDataPayloadType fromId(int id) {
|
||||
return Helper.ID_TO_TYPE.get(id);
|
||||
}
|
||||
|
||||
private static final class Helper {
|
||||
static Map<Integer, HudDataPayloadType> ID_TO_TYPE = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
public record HudDataPayload(HudDataPayloadType packetType, CompoundTag nbt,
|
||||
FriendlyByteBuf buffer) implements LeavesCustomPayload {
|
||||
|
||||
@ID
|
||||
public static final ResourceLocation CHANNEL = ServuxProtocol.id("hud_metadata");
|
||||
|
||||
@Codec
|
||||
public static final StreamCodec<FriendlyByteBuf, HudDataPayload> CODEC = StreamCodec.of(
|
||||
(buf, payload) -> {
|
||||
buf.writeVarInt(payload.packetType().type);
|
||||
switch (payload.packetType()) {
|
||||
case PACKET_S2C_NBT_RESPONSE_DATA -> buf.writeBytes(payload.buffer().copy());
|
||||
case PACKET_C2S_METADATA_REQUEST, PACKET_S2C_METADATA, PACKET_C2S_SPAWN_DATA_REQUEST,
|
||||
PACKET_S2C_SPAWN_DATA, PACKET_S2C_WEATHER_TICK, PACKET_C2S_RECIPE_MANAGER_REQUEST,
|
||||
PACKET_C2S_DATA_LOGGER_REQUEST, PACKET_S2C_DATA_LOGGER_TICK -> buf.writeNbt(payload.nbt());
|
||||
}
|
||||
},
|
||||
buf -> {
|
||||
HudDataPayloadType type = HudDataPayloadType.fromId(buf.readVarInt());
|
||||
if (type == null) {
|
||||
throw new IllegalStateException("invalid packet type received");
|
||||
}
|
||||
switch (type) {
|
||||
case PACKET_S2C_NBT_RESPONSE_DATA -> {
|
||||
return new HudDataPayload(type, new FriendlyByteBuf(buf.readBytes(buf.readableBytes())));
|
||||
}
|
||||
case PACKET_C2S_METADATA_REQUEST, PACKET_S2C_METADATA, PACKET_C2S_SPAWN_DATA_REQUEST,
|
||||
PACKET_S2C_SPAWN_DATA, PACKET_S2C_WEATHER_TICK,
|
||||
PACKET_C2S_RECIPE_MANAGER_REQUEST, PACKET_C2S_DATA_LOGGER_REQUEST,
|
||||
PACKET_S2C_DATA_LOGGER_TICK -> {
|
||||
return new HudDataPayload(type, buf.readNbt());
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("invalid packet type received");
|
||||
}
|
||||
);
|
||||
|
||||
public HudDataPayload(HudDataPayloadType type, CompoundTag nbt) {
|
||||
this(type, nbt, null);
|
||||
}
|
||||
|
||||
public HudDataPayload(HudDataPayloadType type, FriendlyByteBuf buffer) {
|
||||
this(type, new CompoundTag(), buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
|
||||
*
|
||||
* Leaves is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Leaves is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.leavesmc.leaves.protocol.servux;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ServuxProtocol {
|
||||
|
||||
public static final String PROTOCOL_ID = "servux";
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(PROTOCOL_ID.toUpperCase());
|
||||
public static final String SERVUX_STRING = ProtocolUtils.buildProtocolVersion(PROTOCOL_ID);
|
||||
|
||||
@Contract("_ -> new")
|
||||
public static ResourceLocation id(String path) {
|
||||
return ResourceLocation.tryBuild(PROTOCOL_ID, path);
|
||||
}
|
||||
}
|
||||
+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);
|
||||
}
|
||||
}
|
||||
+456
@@ -0,0 +1,456 @@
|
||||
/*
|
||||
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
|
||||
*
|
||||
* Leaves is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Leaves is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.leavesmc.leaves.protocol.servux.litematics;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.NbtAccounter;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
|
||||
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
|
||||
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
|
||||
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
|
||||
import org.leavesmc.leaves.protocol.servux.PacketSplitter;
|
||||
import org.leavesmc.leaves.protocol.servux.ServuxProtocol;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.placement.SchematicPlacement;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.utils.NbtUtils;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.utils.ReplaceBehavior;
|
||||
import org.leavesmc.leaves.util.TagUtil;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@LeavesProtocol.Register(namespace = "servux")
|
||||
public class ServuxLitematicsProtocol implements LeavesProtocol {
|
||||
|
||||
public static final int PROTOCOL_VERSION = 1;
|
||||
|
||||
private static final CompoundTag metadata = new CompoundTag();
|
||||
private static final Map<UUID, Long> playerSession = new HashMap<>();
|
||||
|
||||
@ProtocolHandler.Init
|
||||
public static void init() {
|
||||
metadata.putString("name", "litematic_data");
|
||||
metadata.putString("id", ServuxLitematicaPayload.CHANNEL.toString());
|
||||
metadata.putInt("version", PROTOCOL_VERSION);
|
||||
metadata.putString("servux", ServuxProtocol.SERVUX_STRING);
|
||||
}
|
||||
|
||||
public static boolean hasPermission(ServerPlayer player) {
|
||||
return player.getBukkitEntity().hasPermission("leaves.protocol.litematics");
|
||||
}
|
||||
|
||||
public static void sendMetaData(ServerPlayer player) {
|
||||
ServuxLitematicaPayload send = new ServuxLitematicaPayload(ServuxLitematicaPayloadType.PACKET_S2C_METADATA);
|
||||
send.nbt.merge(metadata);
|
||||
encodeServerData(player, send);
|
||||
}
|
||||
|
||||
public static void encodeServerData(ServerPlayer player, @NotNull ServuxLitematicaPayload packet) {
|
||||
if (packet.packetType.equals(ServuxLitematicaPayloadType.PACKET_S2C_NBT_RESPONSE_START)) {
|
||||
FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.buffer());
|
||||
buffer.writeVarInt(packet.getTransactionId());
|
||||
buffer.writeNbt(packet.getCompound());
|
||||
PacketSplitter.send(ServuxLitematicsProtocol::sendWithSplitter, buffer, player);
|
||||
} else {
|
||||
ProtocolUtils.sendPayloadPacket(player, packet);
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendWithSplitter(ServerPlayer player, FriendlyByteBuf buf) {
|
||||
ServuxLitematicaPayload payload = new ServuxLitematicaPayload(ServuxLitematicaPayloadType.PACKET_S2C_NBT_RESPONSE_DATA);
|
||||
payload.buffer = buf;
|
||||
payload.nbt = new CompoundTag();
|
||||
encodeServerData(player, payload);
|
||||
}
|
||||
|
||||
@ProtocolHandler.PlayerJoin
|
||||
public static void onPlayerJoin(ServerPlayer player) {
|
||||
sendMetaData(player);
|
||||
}
|
||||
|
||||
@ProtocolHandler.PayloadReceiver(payload = ServuxLitematicaPayload.class)
|
||||
public static void onPacketReceive(ServerPlayer player, ServuxLitematicaPayload payload) {
|
||||
if (!hasPermission(player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (payload.packetType) {
|
||||
case PACKET_C2S_METADATA_REQUEST -> sendMetaData(player);
|
||||
|
||||
case PACKET_C2S_BLOCK_ENTITY_REQUEST -> onBlockEntityRequest(player, payload.getPos());
|
||||
|
||||
case PACKET_C2S_ENTITY_REQUEST -> onEntityRequest(player, payload.getEntityId());
|
||||
|
||||
|
||||
case PACKET_C2S_BULK_ENTITY_NBT_REQUEST ->
|
||||
onBulkEntityRequest(player, payload.getChunkPos(), payload.getCompound());
|
||||
|
||||
case PACKET_C2S_NBT_RESPONSE_DATA -> {
|
||||
ServuxProtocol.LOGGER.debug("nbt response data");
|
||||
UUID uuid = player.getUUID();
|
||||
Long session = playerSession.getOrDefault(uuid, new Random().nextLong());
|
||||
playerSession.put(uuid, session);
|
||||
FriendlyByteBuf fullPacket = PacketSplitter.receive(session, payload.getBuffer());
|
||||
if (fullPacket == null) {
|
||||
ServuxProtocol.LOGGER.debug("packet is none");
|
||||
return;
|
||||
}
|
||||
playerSession.remove(uuid);
|
||||
fullPacket.readVarInt();
|
||||
Tag tag = FriendlyByteBuf.readNbt(fullPacket, new NbtAccounter(fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.litematicsMaxNbtSize == -1 ? Long.MAX_VALUE : fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.litematicsMaxNbtSize, 512));
|
||||
if (!(tag instanceof CompoundTag)) {
|
||||
ServuxProtocol.LOGGER.error("cannot read nbt tag from packet");
|
||||
return;
|
||||
}
|
||||
handleClientPasteRequest(player, (CompoundTag) tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void onBlockEntityRequest(ServerPlayer player, BlockPos pos) {
|
||||
if (!hasPermission(player)) {
|
||||
return;
|
||||
}
|
||||
io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
|
||||
player.level(),
|
||||
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(player.position()),
|
||||
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(player.position()),
|
||||
() -> {
|
||||
BlockEntity be = player.level().getBlockEntity(pos);
|
||||
CompoundTag tag = be != null ? be.saveWithFullMetadata(MinecraftServer.getServer().registryAccess()) : new CompoundTag();
|
||||
ServuxLitematicaPayload payload = new ServuxLitematicaPayload(ServuxLitematicaPayloadType.PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE);
|
||||
payload.pos = pos;
|
||||
payload.nbt = tag;
|
||||
encodeServerData(player, payload);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static void onEntityRequest(ServerPlayer player, int entityId) {
|
||||
if (!hasPermission(player)) {
|
||||
return;
|
||||
}
|
||||
io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
|
||||
player.level(),
|
||||
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(player.position()),
|
||||
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(player.position()),
|
||||
() -> {
|
||||
Entity entity = player.level().getEntity(entityId);
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
CompoundTag tag = new CompoundTag();
|
||||
ServuxLitematicaPayload payload = new ServuxLitematicaPayload(ServuxLitematicaPayloadType.PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE);
|
||||
payload.entityId = entityId;
|
||||
if (entity instanceof net.minecraft.world.entity.player.Player) {
|
||||
ResourceLocation loc = EntityType.getKey(entity.getType());
|
||||
tag = TagUtil.saveEntity(entity);
|
||||
tag.putString("id", loc.toString());
|
||||
payload.nbt = tag;
|
||||
encodeServerData(player, payload);
|
||||
} else if (TagUtil.saveEntityAsPassenger(entity, tag)) {
|
||||
payload.nbt = tag;
|
||||
encodeServerData(player, payload);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static void onBulkEntityRequest(ServerPlayer player, ChunkPos chunkPos, CompoundTag req) {
|
||||
if (req == null || req.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerLevel world = player.level();
|
||||
ChunkAccess chunk = world.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.FULL, false);
|
||||
|
||||
if (chunk == null) {
|
||||
return;
|
||||
}
|
||||
if (!req.contains("Task") || req.getStringOr("Task", "").equals("BulkEntityRequest")) {
|
||||
ServuxProtocol.LOGGER.debug("litematic_data: Sending Bulk NBT Data for ChunkPos [{}] to player {}", chunkPos, player.getName().getString());
|
||||
|
||||
long timeStart = System.currentTimeMillis();
|
||||
ListTag tileList = new ListTag();
|
||||
ListTag entityList = new ListTag();
|
||||
int minY = req.getIntOr("minY", -64);
|
||||
int maxY = req.getIntOr("maxY", 319);
|
||||
BlockPos pos1 = new BlockPos(chunkPos.getMinBlockX(), minY, chunkPos.getMinBlockZ());
|
||||
BlockPos pos2 = new BlockPos(chunkPos.getMaxBlockX(), maxY, chunkPos.getMaxBlockZ());
|
||||
AABB bb = AABB.encapsulatingFullBlocks(pos1, pos2);
|
||||
Set<BlockPos> teSet = chunk.getBlockEntitiesPos();
|
||||
List<Entity> entities = world.getEntitiesOfClass(Entity.class, bb, e -> !(e instanceof Player));
|
||||
for (BlockPos tePos : teSet) {
|
||||
if ((tePos.getX() < chunkPos.getMinBlockX() || tePos.getX() > chunkPos.getMaxBlockX()) ||
|
||||
(tePos.getZ() < chunkPos.getMinBlockZ() || tePos.getZ() > chunkPos.getMaxBlockZ()) ||
|
||||
(tePos.getY() < minY || tePos.getY() > maxY)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BlockEntity be = world.getBlockEntity(tePos);
|
||||
CompoundTag beTag = TagUtil.saveTileWithId(be);
|
||||
tileList.add(beTag);
|
||||
}
|
||||
|
||||
for (Entity entity : entities) {
|
||||
CompoundTag entTag = new CompoundTag();
|
||||
|
||||
if (TagUtil.saveEntity(entity, entTag)) {
|
||||
Vec3 posVec = new Vec3(entity.getX() - pos1.getX(), entity.getY() - pos1.getY(), entity.getZ() - pos1.getZ());
|
||||
NbtUtils.writeEntityPositionToTag(posVec, entTag);
|
||||
entTag.putInt("entityId", entity.getId());
|
||||
entityList.add(entTag);
|
||||
}
|
||||
}
|
||||
|
||||
CompoundTag output = new CompoundTag();
|
||||
output.putString("Task", "BulkEntityReply");
|
||||
output.put("TileEntities", tileList);
|
||||
output.put("Entities", entityList);
|
||||
output.putInt("chunkX", chunkPos.x);
|
||||
output.putInt("chunkZ", chunkPos.z);
|
||||
ServuxProtocol.LOGGER.debug("process bulk entity used: {}ms", System.currentTimeMillis() - timeStart);
|
||||
|
||||
ServuxLitematicaPayload send = new ServuxLitematicaPayload(ServuxLitematicaPayloadType.PACKET_S2C_NBT_RESPONSE_START);
|
||||
send.nbt.merge(output);
|
||||
encodeServerData(player, send);
|
||||
}
|
||||
}
|
||||
|
||||
public static void handleClientPasteRequest(ServerPlayer player, @NotNull CompoundTag tags) {
|
||||
if (!hasPermission(player)) {
|
||||
player.getBukkitEntity().sendActionBar(Component.text("Insufficient Permissions for the Litematic paste operation", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
if (!player.isCreative()) {
|
||||
player.getBukkitEntity().sendActionBar(Component.text("Creative Mode is required for the Litematic paste operation", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
if (tags.getStringOr("Task", "").equals("LitematicaPaste")) {
|
||||
ServuxProtocol.LOGGER.debug("litematic_data: Servux Paste request from player {}", player.getName().getString());
|
||||
ServerLevel serverLevel = player.level();
|
||||
long timeStart = System.currentTimeMillis();
|
||||
SchematicPlacement placement = SchematicPlacement.createFromNbt(tags);
|
||||
ReplaceBehavior replaceMode = ReplaceBehavior.fromStringStatic(tags.getStringOr("ReplaceMode", ReplaceBehavior.NONE.name()));
|
||||
io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
|
||||
player.level(),
|
||||
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(player.position()),
|
||||
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(player.position()),
|
||||
() -> {
|
||||
placement.pasteTo(serverLevel, replaceMode);
|
||||
long timeElapsed = System.currentTimeMillis() - timeStart;
|
||||
player.getBukkitEntity().sendActionBar(
|
||||
Component.text("Pasted ")
|
||||
.append(Component.text(placement.getName(), NamedTextColor.AQUA))
|
||||
.append(Component.text(" to world "))
|
||||
.append(Component.text(serverLevel.serverLevelData.getLevelName(), NamedTextColor.LIGHT_PURPLE))
|
||||
.append(Component.text(" in "))
|
||||
.append(Component.text(timeElapsed, NamedTextColor.GREEN))
|
||||
.append(Component.text("ms"))
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return fun.bm.lophine.config.modules.misc.SurvuxProtocolConfig.litematicsEnabled;
|
||||
}
|
||||
|
||||
public enum ServuxLitematicaPayloadType {
|
||||
PACKET_S2C_METADATA(1),
|
||||
PACKET_C2S_METADATA_REQUEST(2),
|
||||
PACKET_C2S_BLOCK_ENTITY_REQUEST(3),
|
||||
PACKET_C2S_ENTITY_REQUEST(4),
|
||||
PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE(5),
|
||||
PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE(6),
|
||||
PACKET_C2S_BULK_ENTITY_NBT_REQUEST(7),
|
||||
// For Packet Splitter (Oversize Packets, S2C)
|
||||
PACKET_S2C_NBT_RESPONSE_START(10),
|
||||
PACKET_S2C_NBT_RESPONSE_DATA(11),
|
||||
// For Packet Splitter (Oversize Packets, C2S)
|
||||
PACKET_C2S_NBT_RESPONSE_START(12),
|
||||
PACKET_C2S_NBT_RESPONSE_DATA(13);
|
||||
|
||||
public final int type;
|
||||
|
||||
ServuxLitematicaPayloadType(int type) {
|
||||
this.type = type;
|
||||
Helper.ID_TO_TYPE.put(type, this);
|
||||
}
|
||||
|
||||
public static ServuxLitematicaPayloadType fromId(int id) {
|
||||
return Helper.ID_TO_TYPE.get(id);
|
||||
}
|
||||
|
||||
private static final class Helper {
|
||||
static Map<Integer, ServuxLitematicaPayloadType> ID_TO_TYPE = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServuxLitematicaPayload implements LeavesCustomPayload {
|
||||
|
||||
@ID
|
||||
public static final ResourceLocation CHANNEL = ServuxProtocol.id("litematics");
|
||||
|
||||
@Codec
|
||||
public static final StreamCodec<FriendlyByteBuf, ServuxLitematicaPayload> CODEC = StreamCodec.of(
|
||||
(buf, payload) -> {
|
||||
buf.writeVarInt(payload.packetType.type);
|
||||
switch (payload.packetType) {
|
||||
case PACKET_C2S_BLOCK_ENTITY_REQUEST -> {
|
||||
buf.writeVarInt(payload.transactionId);
|
||||
buf.writeBlockPos(payload.pos);
|
||||
}
|
||||
case PACKET_C2S_ENTITY_REQUEST -> {
|
||||
buf.writeVarInt(payload.transactionId);
|
||||
buf.writeVarInt(payload.entityId);
|
||||
}
|
||||
case PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE -> {
|
||||
buf.writeBlockPos(payload.pos);
|
||||
buf.writeNbt(payload.nbt);
|
||||
}
|
||||
case PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE -> {
|
||||
buf.writeVarInt(payload.entityId);
|
||||
buf.writeNbt(payload.nbt);
|
||||
}
|
||||
case PACKET_C2S_BULK_ENTITY_NBT_REQUEST -> {
|
||||
buf.writeChunkPos(payload.chunkPos);
|
||||
buf.writeNbt(payload.nbt);
|
||||
}
|
||||
case PACKET_S2C_NBT_RESPONSE_DATA, PACKET_C2S_NBT_RESPONSE_DATA ->
|
||||
buf.writeBytes(payload.buffer.readBytes(payload.buffer.readableBytes()));
|
||||
case PACKET_C2S_METADATA_REQUEST, PACKET_S2C_METADATA -> buf.writeNbt(payload.nbt);
|
||||
default -> ServuxProtocol.LOGGER.error("ServuxLitematicaPacket#toPacket: Unknown packet type!");
|
||||
}
|
||||
},
|
||||
buf -> {
|
||||
ServuxLitematicaPayloadType type = ServuxLitematicaPayloadType.fromId(buf.readVarInt());
|
||||
if (type == null) {
|
||||
throw new IllegalStateException("invalid packet type received");
|
||||
}
|
||||
ServuxLitematicaPayload payload = new ServuxLitematicaPayload(type);
|
||||
switch (type) {
|
||||
case PACKET_C2S_BLOCK_ENTITY_REQUEST -> {
|
||||
buf.readVarInt();
|
||||
payload.pos = buf.readBlockPos().immutable();
|
||||
}
|
||||
case PACKET_C2S_ENTITY_REQUEST -> {
|
||||
buf.readVarInt();
|
||||
payload.entityId = buf.readVarInt();
|
||||
}
|
||||
case PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE -> {
|
||||
payload.pos = buf.readBlockPos().immutable();
|
||||
payload.nbt = buf.readNbt();
|
||||
}
|
||||
case PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE -> {
|
||||
payload.entityId = buf.readVarInt();
|
||||
payload.nbt = buf.readNbt();
|
||||
}
|
||||
case PACKET_C2S_BULK_ENTITY_NBT_REQUEST -> {
|
||||
payload.chunkPos = buf.readChunkPos();
|
||||
payload.nbt = buf.readNbt();
|
||||
}
|
||||
case PACKET_C2S_NBT_RESPONSE_DATA, PACKET_S2C_NBT_RESPONSE_DATA ->
|
||||
payload.buffer = new FriendlyByteBuf(buf.readBytes(buf.readableBytes()));
|
||||
case PACKET_C2S_METADATA_REQUEST, PACKET_S2C_METADATA -> payload.nbt = buf.readNbt();
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
);
|
||||
|
||||
public static final int PROTOCOL_VERSION = 1;
|
||||
private final ServuxLitematicaPayloadType packetType;
|
||||
private final int transactionId;
|
||||
private int entityId;
|
||||
private BlockPos pos;
|
||||
private CompoundTag nbt;
|
||||
private ChunkPos chunkPos;
|
||||
private FriendlyByteBuf buffer;
|
||||
|
||||
private ServuxLitematicaPayload(ServuxLitematicaPayloadType type) {
|
||||
this.packetType = type;
|
||||
this.transactionId = -1;
|
||||
this.entityId = -1;
|
||||
this.pos = BlockPos.ZERO;
|
||||
this.chunkPos = ChunkPos.ZERO;
|
||||
this.nbt = new CompoundTag();
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return PROTOCOL_VERSION;
|
||||
}
|
||||
|
||||
public int getTransactionId() {
|
||||
return this.transactionId;
|
||||
}
|
||||
|
||||
public int getEntityId() {
|
||||
return this.entityId;
|
||||
}
|
||||
|
||||
public BlockPos getPos() {
|
||||
return this.pos;
|
||||
}
|
||||
|
||||
public CompoundTag getCompound() {
|
||||
return this.nbt;
|
||||
}
|
||||
|
||||
public ChunkPos getChunkPos() {
|
||||
return this.chunkPos;
|
||||
}
|
||||
|
||||
public FriendlyByteBuf getBuffer() {
|
||||
return this.buffer;
|
||||
}
|
||||
|
||||
public boolean hasBuffer() {
|
||||
return this.buffer != null && this.buffer.isReadable();
|
||||
}
|
||||
|
||||
public boolean hasNbt() {
|
||||
return this.nbt != null && !this.nbt.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return !this.hasBuffer() && !this.hasNbt();
|
||||
}
|
||||
}
|
||||
}
|
||||
+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());
|
||||
}
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
|
||||
*
|
||||
* Leaves is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Leaves is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.leavesmc.leaves.protocol.servux.logger;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.mojang.serialization.Codec;
|
||||
import io.papermc.paper.threadedregions.TickRegionScheduler;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtOps;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.ServerTickRateManager;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.util.StringRepresentable;
|
||||
import net.minecraft.world.entity.MobCategory;
|
||||
import net.minecraft.world.level.NaturalSpawner;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.leavesmc.leaves.protocol.servux.ServuxHudDataProtocol;
|
||||
import org.leavesmc.leaves.protocol.servux.logger.data.MobCapData;
|
||||
import org.leavesmc.leaves.protocol.servux.logger.data.TickData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Function;
|
||||
|
||||
public abstract class DataLogger<T extends Tag> {
|
||||
|
||||
private final Type type;
|
||||
|
||||
public DataLogger(Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public abstract T getResult(MinecraftServer server, ServuxHudDataProtocol protocol, ServerPlayer player);
|
||||
|
||||
public enum Type implements StringRepresentable {
|
||||
TPS("tps", Tps::new, Tps.CODEC),
|
||||
MOB_CAPS("mob_caps", MobCaps::new, MobCaps.CODEC);
|
||||
|
||||
public static final EnumCodec<Type> CODEC = StringRepresentable.fromEnum(Type::values);
|
||||
public static final ImmutableList<Type> VALUES = ImmutableList.copyOf(values());
|
||||
|
||||
private final String name;
|
||||
private final Function<Type, DataLogger<?>> factory;
|
||||
private final Codec<?> codec;
|
||||
|
||||
Type(String name, Function<Type, DataLogger<?>> factory, Codec<?> codec) {
|
||||
this.name = name;
|
||||
this.factory = factory;
|
||||
this.codec = codec;
|
||||
}
|
||||
|
||||
public static @Nullable Type fromStringStatic(String name) {
|
||||
for (Type type : VALUES) {
|
||||
if (type.name.equalsIgnoreCase(name)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Function<Type, DataLogger<?>> getFactory() {
|
||||
return factory;
|
||||
}
|
||||
|
||||
public @Nullable DataLogger<?> init() {
|
||||
return this.factory.apply(this);
|
||||
}
|
||||
|
||||
public Codec<?> codec() {
|
||||
return this.codec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getSerializedName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Tps extends DataLogger<CompoundTag> {
|
||||
|
||||
public static final Codec<CompoundTag> CODEC = CompoundTag.CODEC;
|
||||
|
||||
public Tps(Type type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getResult(MinecraftServer server, ServuxHudDataProtocol protocol, ServerPlayer player) {
|
||||
this.build(server, protocol, player);
|
||||
return null;
|
||||
}
|
||||
|
||||
private TickData build(MinecraftServer server, ServuxHudDataProtocol protocol, ServerPlayer player) {
|
||||
io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
|
||||
player.level(),
|
||||
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(player.position()),
|
||||
ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(player.position()),
|
||||
() -> {
|
||||
ServerTickRateManager tickManager = server.tickRateManager();
|
||||
boolean frozen = tickManager.isFrozen();
|
||||
boolean sprinting = tickManager.isSprinting();
|
||||
io.papermc.paper.threadedregions.TickData.TickReportData tickData = TickRegionScheduler.getCurrentRegion().getData().getRegionSchedulingHandle().getTickReport5s(System.nanoTime());
|
||||
final double tps = tickData.tpsData().segmentAll().average();
|
||||
final double mspt = tickData.timePerTickData().segmentAll().average() / 1.0E6;
|
||||
|
||||
TickData tk = new TickData(
|
||||
mspt, tps,
|
||||
tickManager.getRemainingSprintTicks(),
|
||||
frozen, sprinting,
|
||||
tickManager.isSteppingForward()
|
||||
);
|
||||
Tag ret = TickData.CODEC.encodeStart(server.registryAccess().createSerializationContext(NbtOps.INSTANCE), tk).getOrThrow();
|
||||
protocol.applyData(Type.TPS, player, ret);
|
||||
}
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MobCaps extends DataLogger<CompoundTag> {
|
||||
|
||||
public static final Codec<CompoundTag> CODEC = CompoundTag.CODEC;
|
||||
|
||||
public MobCaps(Type type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getResult(MinecraftServer server, ServuxHudDataProtocol protocol, ServerPlayer player) {
|
||||
CompoundTag nbt = new CompoundTag();
|
||||
|
||||
for (ServerLevel world : server.getAllLevels()) {
|
||||
NaturalSpawner.SpawnState info = world.getChunkSource().getLastSpawnState();
|
||||
if (info == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int chunks = info.getSpawnableChunkCount();
|
||||
Object2IntMap<MobCategory> counts = info.getMobCategoryCounts();
|
||||
MobCapData mobCapData = new MobCapData(new ArrayList<>(), world.getGameTime());
|
||||
for (MobCategory category : MobCategory.values()) {
|
||||
mobCapData.data().add(new MobCapData.Cap(counts.getOrDefault(category, 0), NaturalSpawner.globalLimitForCategory(world, category, chunks)));
|
||||
}
|
||||
|
||||
try {
|
||||
nbt.put(world.dimension().location().toString(), MobCapData.CODEC.encodeStart(world.registryAccess().createSerializationContext(NbtOps.INSTANCE), mobCapData).getPartialOrThrow());
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
return nbt;
|
||||
}
|
||||
}
|
||||
}
|
||||
+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)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.RedStoneConfig;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.piston.PistonBaseBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.Property;
|
||||
import net.minecraft.world.level.block.state.properties.RailShape;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ShearsWrenchUtil {
|
||||
|
||||
public static InteractionResult tryApplyRotate(UseOnContext context, BlockState blockState) {
|
||||
Level level = context.getLevel();
|
||||
BlockPos clickedPos = context.getClickedPos();
|
||||
Block block = blockState.getBlock();
|
||||
if (!RedStoneConfig.shears || !(block instanceof ObserverBlock || block instanceof DispenserBlock || block instanceof PistonBaseBlock || block instanceof HopperBlock || block instanceof RepeaterBlock || block instanceof ComparatorBlock || block instanceof CrafterBlock || block instanceof LeverBlock || block instanceof CocoaBlock || block instanceof TrapDoorBlock || block instanceof FenceGateBlock || block instanceof LightningRodBlock || block instanceof CalibratedSculkSensorBlock || block instanceof BaseRailBlock)) {
|
||||
return null;
|
||||
}
|
||||
StateDefinition<Block, BlockState> blockstatelist = block.getStateDefinition();
|
||||
Property<?> iblockstate;
|
||||
if (block instanceof CrafterBlock) {
|
||||
iblockstate = blockstatelist.getProperty("orientation");
|
||||
} else if (block instanceof BaseRailBlock) {
|
||||
iblockstate = blockstatelist.getProperty("shape");
|
||||
} else {
|
||||
iblockstate = blockstatelist.getProperty("facing");
|
||||
}
|
||||
Player player = context.getPlayer();
|
||||
|
||||
if (iblockstate == null || player == null) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
if (block instanceof BaseRailBlock) {
|
||||
if (block instanceof RailBlock) {
|
||||
if (blockState.getValue(RailBlock.SHAPE).isSlope()) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
} else {
|
||||
if (getNameHelper(blockState, PoweredRailBlock.POWERED).equals("true")) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
if (blockState.getValue(PoweredRailBlock.SHAPE).isSlope()) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (block instanceof PistonBaseBlock) {
|
||||
if (getNameHelper(blockState, PistonBaseBlock.EXTENDED).equals("true")) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
if (block instanceof RepeaterBlock || block instanceof ComparatorBlock) {
|
||||
if (getNameHelper(blockState, ComparatorBlock.POWERED).equals("true")) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
if (block instanceof RepeaterBlock) {
|
||||
if (getNameHelper(blockState, RepeaterBlock.LOCKED).equals("true")) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (block instanceof CrafterBlock) {
|
||||
if (getNameHelper(blockState, CrafterBlock.CRAFTING).equals("true")) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
BlockState iblockdata1 = cycleState(blockState, iblockstate, player.isSecondaryUseActive());
|
||||
level.setBlock(clickedPos, iblockdata1, 18);
|
||||
message(player, Component.translatable("item.minecraft.debug_stick.update", iblockstate.getName(), getNameHelper(iblockdata1, iblockstate)));
|
||||
return InteractionResult.CONSUME;
|
||||
}
|
||||
|
||||
public static boolean shouldSkipPlace(BlockPlaceContext context) {
|
||||
return RedStoneConfig.shears &&
|
||||
context.getPlayer() != null &&
|
||||
context.getPlayer().getItemInHand(InteractionHand.MAIN_HAND).is(Items.SHEARS);
|
||||
}
|
||||
|
||||
private static <T extends Comparable<T>> BlockState cycleState(BlockState state, Property<T> property, boolean inverse) {
|
||||
List<T> possibleValues = property.getPossibleValues();
|
||||
if (possibleValues.getFirst() instanceof RailShape) {
|
||||
boolean isRailBlock = state.getBlock() instanceof RailBlock;
|
||||
possibleValues = possibleValues.stream().filter(possibleValue -> {
|
||||
RailShape shape = (RailShape) possibleValue;
|
||||
if (isRailBlock) {
|
||||
return !shape.isSlope();
|
||||
}
|
||||
return !shape.isSlope() && shape != RailShape.NORTH_EAST && shape != RailShape.NORTH_WEST && shape != RailShape.SOUTH_EAST && shape != RailShape.SOUTH_WEST;
|
||||
}).toList();
|
||||
}
|
||||
return state.setValue(property, getRelative(possibleValues, state.getValue(property), inverse)); // CraftBukkit - decompile error
|
||||
}
|
||||
|
||||
private static <T> T getRelative(Iterable<T> elements, T current, boolean inverse) {
|
||||
return inverse ? Util.findPreviousInIterable(elements, current) : Util.findNextInIterable(elements, current);
|
||||
}
|
||||
|
||||
private static void message(Player player, Component message) {
|
||||
((ServerPlayer) player).sendSystemMessage(message, true);
|
||||
}
|
||||
|
||||
private static <T extends Comparable<T>> String getNameHelper(BlockState state, Property<T> property) {
|
||||
return property.getName(state.getValue(property));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
|
||||
*
|
||||
* Leaves is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Leaves is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.leavesmc.leaves.util;
|
||||
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.ProblemReporter;
|
||||
import net.minecraft.world.level.storage.TagValueInput;
|
||||
import net.minecraft.world.level.storage.TagValueOutput;
|
||||
|
||||
public class TagFactory {
|
||||
|
||||
private static final RegistryAccess.Frozen registryAccess = MinecraftServer.getServer().registryAccess();
|
||||
|
||||
public static TagValueOutput output() {
|
||||
return TagValueOutput.createWithContext(ProblemReporter.DISCARDING, registryAccess);
|
||||
}
|
||||
|
||||
public static TagValueOutput output(CompoundTag tag) {
|
||||
return TagValueOutput.createWrappingWithContext(ProblemReporter.DISCARDING, registryAccess, tag);
|
||||
}
|
||||
|
||||
public static TagValueInput input() {
|
||||
return input(new CompoundTag());
|
||||
}
|
||||
|
||||
public static TagValueInput input(CompoundTag tag) {
|
||||
return (TagValueInput) TagValueInput.create(ProblemReporter.DISCARDING, registryAccess, tag);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
|
||||
*
|
||||
* Leaves is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Leaves is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.leavesmc.leaves.util;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.storage.TagValueInput;
|
||||
import net.minecraft.world.level.storage.TagValueOutput;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class TagUtil {
|
||||
|
||||
public static CompoundTag saveEntity(@Nullable Entity entity) {
|
||||
if (entity == null) {
|
||||
return new CompoundTag();
|
||||
}
|
||||
TagValueOutput output = TagFactory.output();
|
||||
entity.save(output);
|
||||
return output.buildResult();
|
||||
}
|
||||
|
||||
public static boolean saveEntity(Entity entity, CompoundTag tag) {
|
||||
if (entity == null) {
|
||||
return false;
|
||||
}
|
||||
TagValueOutput output = TagFactory.output(tag);
|
||||
return entity.save(output);
|
||||
}
|
||||
|
||||
public static CompoundTag saveEntityWithoutId(Entity entity) {
|
||||
if (entity == null) {
|
||||
return new CompoundTag();
|
||||
}
|
||||
TagValueOutput output = TagFactory.output();
|
||||
entity.saveWithoutId(output);
|
||||
return output.buildResult();
|
||||
}
|
||||
|
||||
public static CompoundTag saveTileWithId(@Nullable BlockEntity entity) {
|
||||
if (entity == null) {
|
||||
return new CompoundTag();
|
||||
}
|
||||
TagValueOutput output = TagFactory.output();
|
||||
entity.saveWithId(output);
|
||||
return output.buildResult();
|
||||
}
|
||||
|
||||
public static boolean saveEntityAsPassenger(@Nullable Entity entity, CompoundTag tag) {
|
||||
if (entity == null) {
|
||||
return false;
|
||||
}
|
||||
TagValueOutput output = TagFactory.output(tag);
|
||||
return entity.saveAsPassenger(output);
|
||||
}
|
||||
|
||||
public static void loadEntity(Entity entity, CompoundTag tag) {
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
TagValueInput input = TagFactory.input(tag);
|
||||
entity.load(input);
|
||||
}
|
||||
|
||||
public static void loadTileWithComponents(BlockEntity entity, CompoundTag tag) {
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
TagValueInput input = TagFactory.input(tag);
|
||||
entity.loadWithComponents(input);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
Special thanks (Logo author):
|
||||
Helvetica Volubi <suisuroru@blue-millennium.fun>
|
||||
bdlvl <bdlvl@mc-mygo.top>
|
||||
bdlvl <bdlvl@blendreams.cn>
|
||||
+15
-3
@@ -8,11 +8,22 @@ project_id_b="Lophine"
|
||||
commitid=$(git log --pretty='%h' -1)
|
||||
mcversion=$(prop mcVersion)
|
||||
grdversion=$(prop version)
|
||||
preVersion=$(prop preVersion)
|
||||
release=$(prop release)
|
||||
release_tag="$mcversion-$commitid"
|
||||
jarName="$project_id-$mcversion-paperclip.jar"
|
||||
jarName_dir="lophine-server/build/libs/$jarName"
|
||||
make_latest=$([ $preVersion = "true" ] && echo "false" || echo "true")
|
||||
|
||||
flag_release=false
|
||||
pre=false
|
||||
|
||||
if [ "$release" = "1" ]; then
|
||||
pre=true
|
||||
flag_release=true
|
||||
make_latest=true
|
||||
elif [ "$release" = "2" ]; then
|
||||
flag_release=true
|
||||
make_latest=true
|
||||
fi
|
||||
|
||||
mv lophine-server/build/libs/$project_id-paperclip-$grdversion-mojmap.jar $jarName_dir
|
||||
|
||||
@@ -21,8 +32,9 @@ echo "project_id_b=$project_id_b" >> $GITHUB_ENV
|
||||
echo "commit_id=$commitid" >> $GITHUB_ENV
|
||||
echo "commit_msg=$(git log --pretty='> [%h] %s' -1)" >> $GITHUB_ENV
|
||||
echo "mcversion=$mcversion" >> $GITHUB_ENV
|
||||
echo "pre=$preVersion" >> $GITHUB_ENV
|
||||
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