Compare commits

..

13 Commits

Author SHA1 Message Date
Helvetica Volubi da005b117a readd null check in RegionizedWorldData >> conections 2026-07-26 01:55:03 +08:00
Helvetica Volubi 41e24e8f91 Fakeplayer Action Gui - Stage 1 (#161)
Still needs some tweaks to fit Leaves' changes

* pre add gui (not implemented)

* fallback one change in Actions

* pre for new structure of gui register

* Enhance GUI node structure with item support and command handling

* feat: update GUI node handling in bot actions

* feat: enhance command building logic in GUI nodes

* fix a bug

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

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

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

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

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

* feat: enhance ActionType handling and selection in BotAction GUI

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

* fix up

* because of tmp move to new org, rebrand

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

* ci updates

* replace boarder item

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

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

* fix index

* replace some items

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

* remove 2 todos, we wouldn't provide them

* refactor: simplify rendering methods in BotActionGuiContainer

* feat: add empty action placeholder and simplify item rendering

* fix: remove extra suggestions

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

* fix: update comment in open action gui

* set release mode to true

---------

Co-authored-by: xiaoxijun <A3167717663@hotmail.com>
2026-07-22 13:12:47 +00:00
Helvetica Volubi feec643a0e [ci skip] update some information 2026-07-22 01:00:09 +08:00
Helvetica Volubi 1861578557 [ci skip] fix docs 2026-07-20 03:14:23 +08:00
Helvetica Volubi 3f3618850b fix: fallback night-config version to fix config save problem 2026-07-19 17:33:23 +08:00
Helvetica Volubi 46771456de patches from 26.2
origin patches from Camellia
2026-07-19 16:50:46 +08:00
Helvetica Volubi f67b68e4b7 [ci skip] some info update 2026-07-12 21:35:03 +08:00
Helvetica Volubi 4bb5b6c3da [ci skip] fix ci for update 2026-07-12 16:23:14 +08:00
Helvetica Volubi 40aaa5f824 [ci skip] ci updates 2026-07-11 01:21:37 +08:00
Helvetica Volubi cefaa4a712 fix: some fix for bot action
fix stop command could stop all the same type of action when a stop action called

fix some typo in command reply
2026-07-11 01:03:01 +08:00
Helvetica Volubi 322bc18e9b [ci skip] because of tmp move to new org, rebrand 2026-07-10 00:17:39 +08:00
Helvetica Volubi fffb3115c0 fix bot command register 2026-07-06 20:23:43 +08:00
Helvetica Volubi a0da7e4b51 Update Luminol to fix some bugs 2026-07-06 01:37:36 +08:00
57 changed files with 1765 additions and 165 deletions
+1 -1
View File
@@ -11,4 +11,4 @@ issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
custom: https://afdian.com/a/Luminol
custom: [https://afdian.com/a/bm0721, https://afdian.com/a/Luminol] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+3 -3
View File
@@ -47,7 +47,7 @@ jobs:
run: chmod +x gradlew
- name: Configure Git User Details
run: git config --global user.email "ci@luminolmc.com" && git config --global user.name "LuminolMC CI"
run: git config --global user.email "ci@lophinelabs.com" && git config --global user.name "LophineLabsCI"
- name: Apply All Patches
run: ./gradlew --refresh-dependencies applyAllPatches
@@ -124,7 +124,7 @@ jobs:
tag: ${{ env.tag }}
name: ${{ env.project_id_b }} ${{ env.mcversion }} - ${{ env.commit_id }}${{ env.release_count }}
body: |
📦Version: `${{ env.mcversion }}` | Commit ${{ env.commit_id }} [![download](https://img.shields.io/github/downloads/LuminolMC/${{ env.project_id }}/${{ env.tag }}/total?color=red&style=flat-square)](https://github.com/LuminolMC/${{ env.project_id }}/releases/download/${{ env.tag }}/${{ env.jar }})
📦Version: `${{ env.mcversion }}` | Commit ${{ env.commit_id }} [![download](https://img.shields.io/github/downloads/LophineLabs/${{ env.project_id }}/${{ env.tag }}/total?color=red&style=flat-square)](https://github.com/LophineLabs/${{ env.project_id }}/releases/download/${{ env.tag }}/${{ env.jar }})
This release is automatically compiled by GitHub Actions
### Branch Info
> ${{ github.ref_name }}
@@ -143,7 +143,7 @@ jobs:
tag: ${{ env.tag }}
name: ${{ env.project_id_b }} ${{ env.mcversion }} - ${{ env.commit_id }}${{ env.release_count }}
body: |
📦Version: `${{ env.mcversion }}` | Commit ${{ env.commit_id }} [![download](https://img.shields.io/github/downloads/LuminolMC/${{ env.project_id }}/${{ env.tag }}/total?color=red&style=flat-square)](https://github.com/LuminolMC/${{ env.project_id }}/releases/download/${{ env.tag }}/${{ env.jar }})
📦Version: `${{ env.mcversion }}` | Commit ${{ env.commit_id }} [![download](https://img.shields.io/github/downloads/LophineLabs/${{ env.project_id }}/${{ env.tag }}/total?color=red&style=flat-square)](https://github.com/LophineLabs/${{ env.project_id }}/releases/download/${{ env.tag }}/${{ env.jar }})
This release is automatically compiled by GitHub Actions
### Comments
> ${{ inputs.comments }}
+13 -43
View File
@@ -5,15 +5,15 @@
*Lophine 是一个基于Luminol的分支,具有许多有用的优化和可配置的原版特性,目标是在Folia上实现更多生电的内容(请注意,完整生电请使用Fabric)*
![Created At](https://img.shields.io/github/created-at/LuminolMC/Lophine?style=for-the-badge&color=blue)
[![License](https://img.shields.io/github/license/LuminolMC/Lophine?style=for-the-badge&color=green)](LICENSE.md)
[![Issues](https://img.shields.io/github/issues/LuminolMC/Lophine?style=for-the-badge&color=orange)](https://github.com/LuminolMC/Lophine/issues)
![Created At](https://img.shields.io/github/created-at/LophineLabs/Lophine?style=for-the-badge&color=blue)
[![License](https://img.shields.io/github/license/LophineLabs/Lophine?style=for-the-badge&color=green)](LICENSE.md)
[![Issues](https://img.shields.io/github/issues/LophineLabs/Lophine?style=for-the-badge&color=orange)](https://github.com/LophineLabs/Lophine/issues)
![Commit Activity](https://img.shields.io/github/commit-activity/w/LuminolMC/Lophine?style=for-the-badge&color=purple)
![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/LuminolMC/Lophine?style=for-the-badge&color=yellow)
![GitHub all releases](https://img.shields.io/github/downloads/LuminolMC/Lophine/total?style=for-the-badge&color=red)
![Commit Activity](https://img.shields.io/github/commit-activity/w/LophineLabs/Lophine?style=for-the-badge&color=purple)
![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/LophineLabs/Lophine?style=for-the-badge&color=yellow)
![GitHub all releases](https://img.shields.io/github/downloads/LophineLabs/Lophine/total?style=for-the-badge&color=red)
![Repo contributors](https://img.shields.io/github/contributors/LuminolMC/Lophine?style=for-the-badge&color=brightgreen)
![Repo contributors](https://img.shields.io/github/contributors/LophineLabs/Lophine?style=for-the-badge&color=brightgreen)
[English](./README_EN.md) | **中文**
</div>
@@ -32,7 +32,7 @@
## 📥 下载
### 稳定版本
所有发布版本都可以在 [Releases](https://github.com/LuminolMC/Lophine/releases) 页面找到。
所有发布版本都可以在 [Releases](https://github.com/LophineLabs/Lophine/releases) 页面找到。
### 开发版本
如果您想体验最新功能,可以通过以下步骤自行构建。
@@ -41,7 +41,7 @@
```bash
# 克隆项目
git clone https://github.com/LuminolMC/Lophine.git
git clone https://github.com/LophineLabs/Lophine.git
cd Lophine
# 应用补丁并构建 Paperclip JAR
@@ -91,15 +91,13 @@ dependencies {
### 加入我们的社区
- **QQ群**: [1015048616](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=QML5kIVsniPi1PlZvnjHQT_02EHsZ5Jc&authKey=%2FTCJsZC7JFQ9sxAroPCKuYnlV57Z5fyqp36ewXZk3Sn4iJ9p4MB1JKdc%2FFcX3HOM&noverify=0&group_code=1015048616)
- **QQ频道**: [点击加入](https://pd.qq.com/s/eq9krf9j)
- **Telegram**: [点击加入](https://t.me/LuminolMinecraft)
- **Discord**: [点击加入](https://discord.gg/Qd7m3V6eDx)
- **QQ群**: [1020403749](https://qm.qq.com/cgi-bin/qm/qr?k=y_MA9UaN7PM9e9J1LIs9Eea3LK8C0h6J&jump_from=webapi&authKey=ap5f8MlbeezXYtnmpnT5ZOFljDuOyV6OAb2PIcViQ+Ilr60Ycq63FDDTsJOZDYtj)
- **Discord**: [点击加入](https://discord.gg/UXSgPZczcy)
### 获取帮助
- 📋 [提交 Issue](https://github.com/LuminolMC/Lophine/issues)
- 💬 [GitHub Discussions](https://github.com/LuminolMC/Lophine/discussions)
- 📋 [提交 Issue](https://github.com/LophineLabs/Lophine/issues)
- 💬 [GitHub Discussions](https://github.com/LophineLabs/Lophine/discussions)
- 📖 [项目文档](./docs/)
## 🐛 问题反馈
@@ -124,34 +122,6 @@ dependencies {
![bStats](https://bstats.org/signatures/server-implementation/Lophine.svg "bStats")
## 🎉 特别感谢
### 项目赞助商
<div align="center">
<b>感谢 <a href="https://github.com/LegacyLands">LegacyLands</a> 对本项目的赞助</b>
<br>
<i>如果你想开发跨 Folia/非 Folia 平台的插件,<a href="https://github.com/LegacyLands/legacy-lands-library/">legacy-lands-library</a> 将会是个不错的选择</i>
<br><br>
<img src="public/image/legacy-lands-logo.png" alt="LegacyLands Logo" width="200">
</div>
---
## ⭐ 请给我们一个 Star
> 你的每一个免费的 ⭐Star 就是我们每一个前进的动力。
### Star 历史
<a href="https://star-history.com/#LuminolMC/Luminol&LuminolMC/LightingLuminol&LuminolMC/Lophine&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=LuminolMC/Luminol%2CLuminolMC/LightingLuminol%2CLuminolMC/Lophine&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=LuminolMC/Luminol%2CLuminolMC/LightingLuminol%2CLuminolMC/Lophine&type=Date" />
<img alt="Star历史表" src="https://api.star-history.com/svg?repos=LuminolMC/Luminol%2CLuminolMC/LightingLuminol%2CLuminolMC/Lophine&type=Date" />
</picture>
</a>
<div align="center">
<b>如果这个项目对您有帮助,请不要忘记给我们一个 ⭐Star!</b>
</div>
+13 -43
View File
@@ -5,15 +5,15 @@
*Lophine is a Luminol fork with many useful optimizations and configurable vanilla features, aims to provide more function for survival-usable circuit on folia (Please note that Fabric should be used for complete survival-usable)*
![Created At](https://img.shields.io/github/created-at/LuminolMC/Lophine?style=for-the-badge&color=blue)
[![License](https://img.shields.io/github/license/LuminolMC/Lophine?style=for-the-badge&color=green)](LICENSE.md)
[![Issues](https://img.shields.io/github/issues/LuminolMC/Lophine?style=for-the-badge&color=orange)](https://github.com/LuminolMC/Lophine/issues)
![Created At](https://img.shields.io/github/created-at/LophineLabs/Lophine?style=for-the-badge&color=blue)
[![License](https://img.shields.io/github/license/LophineLabs/Lophine?style=for-the-badge&color=green)](LICENSE.md)
[![Issues](https://img.shields.io/github/issues/LophineLabs/Lophine?style=for-the-badge&color=orange)](https://github.com/LophineLabs/Lophine/issues)
![Commit Activity](https://img.shields.io/github/commit-activity/w/LuminolMC/Lophine?style=for-the-badge&color=purple)
![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/LuminolMC/Lophine?style=for-the-badge&color=yellow)
![GitHub all releases](https://img.shields.io/github/downloads/LuminolMC/Lophine/total?style=for-the-badge&color=red)
![Commit Activity](https://img.shields.io/github/commit-activity/w/LophineLabs/Lophine?style=for-the-badge&color=purple)
![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/LophineLabs/Lophine?style=for-the-badge&color=yellow)
![GitHub all releases](https://img.shields.io/github/downloads/LophineLabs/Lophine/total?style=for-the-badge&color=red)
![Repo contributors](https://img.shields.io/github/contributors/LuminolMC/Lophine?style=for-the-badge&color=brightgreen)
![Repo contributors](https://img.shields.io/github/contributors/LophineLabs/Lophine?style=for-the-badge&color=brightgreen)
**English** | [中文](./README.md)
</div>
@@ -32,7 +32,7 @@
## 📥 Download
### Stable Releases
All release versions can be found on the [Releases](https://github.com/LuminolMC/Lophine/releases) page.
All release versions can be found on the [Releases](https://github.com/LophineLabs/Lophine/releases) page.
### Development Builds
If you want to experience the latest features, you can build it yourself following the steps below.
@@ -41,7 +41,7 @@ If you want to experience the latest features, you can build it yourself followi
```bash
# Clone the project
git clone https://github.com/LuminolMC/Lophine.git
git clone https://github.com/LophineLabs/Lophine.git
cd Lophine
# Apply patches and build Paperclip JAR
@@ -92,15 +92,13 @@ dependencies {
### Join Our Community
- **QQ Group**: [1015048616](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=QML5kIVsniPi1PlZvnjHQT_02EHsZ5Jc&authKey=%2FTCJsZC7JFQ9sxAroPCKuYnlV57Z5fyqp36ewXZk3Sn4iJ9p4MB1JKdc%2FFcX3HOM&noverify=0&group_code=1015048616)
- **QQ Channel**: [Join Here](https://pd.qq.com/s/eq9krf9j)
- **Telegram**: [Join Here](https://t.me/LuminolMinecraft)
- **Discord**: [Join Here](https://discord.gg/Qd7m3V6eDx)
- **QQ Group**: [1020403749](https://qm.qq.com/cgi-bin/qm/qr?k=y_MA9UaN7PM9e9J1LIs9Eea3LK8C0h6J&jump_from=webapi&authKey=ap5f8MlbeezXYtnmpnT5ZOFljDuOyV6OAb2PIcViQ+Ilr60Ycq63FDDTsJOZDYtj)
- **Discord**: [Join Here](https://discord.gg/UXSgPZczcy)
### Get Help
- 📋 [Submit Issues](https://github.com/LuminolMC/Lophine/issues)
- 💬 [GitHub Discussions](https://github.com/LuminolMC/Lophine/discussions)
- 📋 [Submit Issues](https://github.com/LophineLabs/Lophine/issues)
- 💬 [GitHub Discussions](https://github.com/LophineLabs/Lophine/discussions)
- 📖 [Project Documentation](./docs/)
## 🐛 Bug Reports
@@ -125,34 +123,6 @@ We welcome community contributions! For detailed contribution guidelines, please
![bStats](https://bstats.org/signatures/server-implementation/Lophine.svg "bStats")
## 🎉 Special Thanks
### Project Sponsors
<div align="center">
<b>Thanks to <a href="https://github.com/LegacyLands">LegacyLands</a> for sponsoring this project</b>
<br>
<i>If you want to develop cross-Folia/non-Folia platform plugins, <a href="https://github.com/LegacyLands/legacy-lands-library/">legacy-lands-library</a> would be a great choice</i>
<br><br>
<img src="public/image/legacy-lands-logo.png" alt="LegacyLands Logo" width="200">
</div>
---
## ⭐ Give Us a Star!
> Every free ⭐Star you give is the motivation for our every step forward.
### Star History
<a href="https://star-history.com/#LuminolMC/Luminol&LuminolMC/LightingLuminol&LuminolMC/Lophine&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=LuminolMC/Luminol%2CLuminolMC/LightingLuminol%2CLuminolMC/Lophine&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=LuminolMC/Luminol%2CLuminolMC/LightingLuminol%2CLuminolMC/Lophine&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=LuminolMC/Luminol%2CLuminolMC/LightingLuminol%2CLuminolMC/Lophine&type=Date" />
</picture>
</a>
<div align="center">
<b>If this project helps you, please don't forget to give us a ⭐Star!</b>
</div>
+1 -1
View File
@@ -9,7 +9,7 @@ plugins {
paperweight {
filterPatches = false
upstreams.register("luminol") {
repo = github("LuminolMC", "Luminol")
repo = github("LuminolCustomArchive", "Luminol")
ref = providers.gradleProperty("luminolRef")
println("Upstream commit ref: " + ref.get())
+1 -1
View File
@@ -9,7 +9,7 @@ release=true
# true for push to repo, false for skip push repo, auto for detect by release value
pushRepo=auto
luminolRef=ba28403ff6d15da3cf8b1af9828bbd1f997a7b9e
luminolRef=b796645fdaae7d6b3d3c84fd79f9c090a6458a48
org.gradle.configuration-cache=true
org.gradle.caching=true
@@ -0,0 +1,46 @@
package org.leavesmc.leaves.event.bot;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.entity.bot.Bot;
public class BotActionGuiOpenEvent extends BotEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private final Player player;
private boolean cancel = false;
public BotActionGuiOpenEvent(@NotNull Bot who, @Nullable Player player) {
super(who);
this.player = player;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
@Override
public boolean isCancelled() {
return cancel;
}
@Override
public void setCancelled(boolean cancel) {
this.cancel = cancel;
}
@Nullable
public Player getOpenPlayer() {
return player;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlers;
}
}
+6 -2
View File
@@ -42,7 +42,7 @@
}
}
val log4jPlugins = sourceSets.create("log4jPlugins") {
@@ -158,7 +_,7 @@
@@ -158,10 +_,10 @@
}
dependencies {
@@ -50,7 +50,11 @@
+ implementation(project(":lophine-api"))
// Luminol start - Dependenices insert
implementation("net.objecthunter:exp4j:0.4.8")
implementation("com.electronwill.night-config:toml:3.9.0") // Night config
- implementation("com.electronwill.night-config:toml:3.9.0") // Night config
+ implementation("com.electronwill.night-config:toml:3.8.4") // Night config
implementation("com.github.luben:zstd-jni:1.5.4-1")
implementation("net.openhft:zero-allocation-hashing:0.16")
implementation("io.github.classgraph:classgraph:4.8.158")
@@ -234,14 +_,14 @@
val gitBranch = git.exec(providers, "rev-parse", "--abbrev-ref", "HEAD").get().trim()
attributes(
@@ -5,14 +5,14 @@ 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 a2f5a99ee45a0810fd338ccf6f71df3a077ca62b..05e1aa84586284ac0389e7023f975bfb08438535 100644
index 3b700b214ac6818cd75c34b207aa1436f52231e7..315c137cb9dbac51a8ac0d63f0ae0127e57c6c9f 100644
--- a/src/main/java/me/earthme/luminol/config/ConfigManager.java
+++ b/src/main/java/me/earthme/luminol/config/ConfigManager.java
@@ -24,6 +24,7 @@ public class ConfigManager {
@@ -25,6 +25,7 @@ public class ConfigManager {
public static void initConfigs() {
registerConfig("luminol", ConfigsInstance.of("luminol", "me.earthme.luminol.config.modules"));
+ registerConfig("lophine", ConfigsInstance.of("lophine", "fun.bm.lophine.config.modules")); // add lophine global config
registerConfig("luminol", builder.of("luminol", "me.earthme.luminol.config.modules"));
+ registerConfig("lophine", builder.of("lophine", "fun.bm.lophine.config.modules")); // add lophine global config
preLoad();
}
@@ -5,15 +5,15 @@ Subject: [PATCH] Carpet features
diff --git a/src/main/java/me/earthme/luminol/config/ConfigManager.java b/src/main/java/me/earthme/luminol/config/ConfigManager.java
index 05e1aa84586284ac0389e7023f975bfb08438535..5a8c94337e42584ddaf25ef289327777e7ac675e 100644
index 315c137cb9dbac51a8ac0d63f0ae0127e57c6c9f..2a2d6aff636ad960638b357d224a631c89d23d33 100644
--- a/src/main/java/me/earthme/luminol/config/ConfigManager.java
+++ b/src/main/java/me/earthme/luminol/config/ConfigManager.java
@@ -25,6 +25,15 @@ public class ConfigManager {
@@ -26,6 +26,15 @@ public class ConfigManager {
public static void initConfigs() {
registerConfig("luminol", ConfigsInstance.of("luminol", "me.earthme.luminol.config.modules"));
registerConfig("lophine", ConfigsInstance.of("lophine", "fun.bm.lophine.config.modules")); // add lophine global config
registerConfig("luminol", builder.of("luminol", "me.earthme.luminol.config.modules"));
registerConfig("lophine", builder.of("lophine", "fun.bm.lophine.config.modules")); // add lophine global config
+ // Lophine start - add carpet config
+ registerConfig("lophine_carpet", ConfigsInstance.of(
+ registerConfig("lophine_carpet", builder.of(
+ new java.io.File("lophine_config"),
+ "lophine_carpet",
+ "lophine_carpet_config.toml",
@@ -7,10 +7,10 @@ Subject: [PATCH] TEMP: Temporarily transform config between different config
Should be removed in Minecraft 26.2 Update
diff --git a/src/main/java/me/earthme/luminol/config/ConfigManager.java b/src/main/java/me/earthme/luminol/config/ConfigManager.java
index 5a8c94337e42584ddaf25ef289327777e7ac675e..b652525f243c23e09c4e13f67617e99a3ce0db04 100644
index 2a2d6aff636ad960638b357d224a631c89d23d33..536e8d7df4ce9de1dbe587c1af6b2474a530a165 100644
--- a/src/main/java/me/earthme/luminol/config/ConfigManager.java
+++ b/src/main/java/me/earthme/luminol/config/ConfigManager.java
@@ -64,6 +64,12 @@ public class ConfigManager {
@@ -65,6 +65,12 @@ public class ConfigManager {
CompletableFuture.allOf(futures).join();
CommandRegister.register(); // register command after config loaded to enable some command didn't depend on config files
initialized = true;
@@ -5,10 +5,10 @@ Subject: [PATCH] Fix datapack command save function
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 28384f9ebfcdbfbd07fc7fe3c929786eb9e2ccd3..d1cc2f956213add0e04d51db7b9da7076b521d8b 100644
index e7eec08a4d8943b35a76173e39529498c348140c..61983a80191e64942bfdac74c6d2f23cf781484d 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -2472,6 +2472,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -2475,6 +2475,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
return this.reloadResources(packsToEnable, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN);
}
@@ -48,10 +48,10 @@ index 89f61f5715942d2b521e04b9fdc8b1b379c0c957..2c2487e1c1c65438119a13bd705a9e44
SaveOnCommand.register(this.dispatcher);
SetPlayerIdleTimeoutCommand.register(this.dispatcher);
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index d1cc2f956213add0e04d51db7b9da7076b521d8b..82013a8f7cf07b6f0645ca6b51ee75fcb0a56298 100644
index 61983a80191e64942bfdac74c6d2f23cf781484d..99edebf474277e680df63930b51fef579b712def 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1683,6 +1683,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1686,6 +1686,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// Folia start - region threading
public void tickServer(final long startTime, final long scheduledEnd, final long targetBuffer,
final io.papermc.paper.threadedregions.TickRegions.TickRegionData region) {
@@ -36,10 +36,10 @@ index e9f86409bf9351e85ed31e747d9350dbf0cc441f..1f8f4d979d8766d6ba385f74cf8fcf8e
};
}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 82013a8f7cf07b6f0645ca6b51ee75fcb0a56298..54ba8a2bc70f3a7f9754a872f6b9c22b40887d8e 100644
index 99edebf474277e680df63930b51fef579b712def..3cd1a0de188db35ac9c48996a4dbe6186b69e348 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -2037,6 +2037,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -2040,6 +2040,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
profiler.popPush("server gui refresh");
@@ -33,7 +33,7 @@ index efbbc1a40d203a2818eeda2a4e938b9a6117f1c3..6b85a5ad9af0c57a0d4d5b55f62b82c5
private boolean encrypted;
private final java.util.concurrent.atomic.AtomicBoolean disconnectionHandled = new java.util.concurrent.atomic.AtomicBoolean(false); // Folia - region threading - may be called concurrently during configuration stage
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 54ba8a2bc70f3a7f9754a872f6b9c22b40887d8e..c4685bb540e95d889dfbc107923876045be29848 100644
index 3cd1a0de188db35ac9c48996a4dbe6186b69e348..9837d8388e6fe73ef87d021c7370c2a7c236c634 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -297,6 +297,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -45,7 +45,7 @@ index 54ba8a2bc70f3a7f9754a872f6b9c22b40887d8e..c4685bb540e95d889dfbc10792387604
public static <S extends MinecraftServer> S spin(final Function<Thread, S> factory) {
ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
AtomicReference<S> serverReference = new AtomicReference<>();
@@ -1226,6 +1228,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1229,6 +1231,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.safeShutdown(wait, false);
}
public void safeShutdown(final boolean wait, final boolean isRestarting) {
@@ -62,7 +62,7 @@ index 54ba8a2bc70f3a7f9754a872f6b9c22b40887d8e..c4685bb540e95d889dfbc10792387604
this.isRestarting = isRestarting;
this.hasLoggedStop = true; // Paper - Debugging
if (this.isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging
@@ -1695,7 +1707,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1698,7 +1710,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
int emptyTickThreshold = this.pauseWhenEmptySeconds() * 20;
this.removeDisabledPluginsBlockingSleep(); // Paper - API to allow/disallow tick sleeping
if (false && emptyTickThreshold > 0) { // Folia - region threading - this is complicated to implement, and even if done correctly is messy
@@ -71,7 +71,7 @@ index 54ba8a2bc70f3a7f9754a872f6b9c22b40887d8e..c4685bb540e95d889dfbc10792387604
this.emptyTicks++;
} else {
this.emptyTicks = 0;
@@ -1827,6 +1839,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1830,6 +1842,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.isSaving = true;
if (playerSaveInterval > 0) {
this.playerList.saveAll(playerSaveInterval);
@@ -79,7 +79,7 @@ index 54ba8a2bc70f3a7f9754a872f6b9c22b40887d8e..c4685bb540e95d889dfbc10792387604
}
if (region == null && fullSave) { // Folia - region threading - don't auto save level data
this.saveGlobalData(false);
@@ -2065,6 +2078,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -2068,6 +2081,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
protected void tickConnection() {
this.getConnection().tick();
@@ -87,7 +87,7 @@ index 54ba8a2bc70f3a7f9754a872f6b9c22b40887d8e..c4685bb540e95d889dfbc10792387604
}
public void forceGameTimeSynchronization() {
@@ -3270,6 +3284,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -3273,6 +3287,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
return this.debugSubscribers;
}
@@ -84,10 +84,10 @@ index 7e3912b8b29e40eb3347f1451b0fd3ebee2af4f1..0834e402e6af7028e08851378e15390e
result.add(player);
if (result.size() >= limit) {
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index c4685bb540e95d889dfbc107923876045be29848..f99c02b4d88cea924e20ca8dced67873c83c093f 100644
index 9837d8388e6fe73ef87d021c7370c2a7c236c634..5889aa815ab02261f26048c5ef43f5bdfa2939fc 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1930,7 +1930,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1933,7 +1933,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
private ServerStatus.Players buildPlayerStatus() {
@@ -96,7 +96,7 @@ index c4685bb540e95d889dfbc107923876045be29848..f99c02b4d88cea924e20ca8dced67873
int maxPlayers = this.getMaxPlayers();
if (this.hidesOnlinePlayers()) {
return new ServerStatus.Players(maxPlayers, players.size(), List.of());
@@ -2151,7 +2151,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -2154,7 +2154,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@Override
public int getPlayerCount() {
@@ -105,7 +105,7 @@ index c4685bb540e95d889dfbc107923876045be29848..f99c02b4d88cea924e20ca8dced67873
}
public String[] getPlayerNames() {
@@ -2363,7 +2363,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -2366,7 +2366,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
} else {
int count = 0;
@@ -114,7 +114,7 @@ index c4685bb540e95d889dfbc107923876045be29848..f99c02b4d88cea924e20ca8dced67873
player.getBukkitEntity().taskScheduler.schedule((ServerPlayer updatedPlayer) -> { // Folia - region threading
// Paper start - Expand PlayerGameModeChangeEvent
org.bukkit.event.player.PlayerGameModeChangeEvent event = updatedPlayer.setGameMode(gameType, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, null); // Folia - region threading
@@ -2550,7 +2550,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -2553,7 +2553,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
if (Thread.currentThread() != this.serverThread) return; // Paper
// Paper start - we don't need to save everything, just advancements
// this.getPlayerList().saveAll();
@@ -123,7 +123,7 @@ index c4685bb540e95d889dfbc107923876045be29848..f99c02b4d88cea924e20ca8dced67873
player.getAdvancements().save();
}
// Paper end - we don't need to save everything, just advancements
@@ -2686,7 +2686,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -2689,7 +2689,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
PlayerList playerList = this.getPlayerList();
UserWhiteList whiteList = playerList.getWhiteList();
@@ -36,10 +36,10 @@ index 24f5b890b0126fc817863f99b676da1a548f7608..a25bc31c04778bf12e3b6301ffcfb227
throw PacketUtils.makeReportedException(var3, this.packet, this.listener);
}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index f99c02b4d88cea924e20ca8dced67873c83c093f..0c042c4af5d52c08e9fbc03a04c4b696d8a0a7ed 100644
index 5889aa815ab02261f26048c5ef43f5bdfa2939fc..af245af0fba14892095961a71925f3a53ce3d477 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -2019,11 +2019,30 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -2022,11 +2022,30 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
try {
foliaProfiler.startTimer(level.tickTimerId); try { // Folia - profiler
level.tick(haveTime, region); // Folia - region threading
@@ -0,0 +1,49 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Wed, 8 Jul 2026 21:37:19 +0800
Subject: [PATCH] Camellia: Fix teleport missing original rotation and velocity
diff --git a/io/papermc/paper/threadedregions/TeleportUtils.java b/io/papermc/paper/threadedregions/TeleportUtils.java
index 2a64a5b2cf049661fe3f5a22ddfa39979624f5ec..335e9775e0d9a61f0e9641aba691a3bdf3ac7f2b 100644
--- a/io/papermc/paper/threadedregions/TeleportUtils.java
+++ b/io/papermc/paper/threadedregions/TeleportUtils.java
@@ -41,7 +41,7 @@ public final class TeleportUtils {
return;
}
(useFromRootVehicle ? realFrom.getRootVehicle() : realFrom).teleportAsync(
- ((CraftWorld)loc.getWorld()).getHandle(), pos, null, null, null,
+ ((CraftWorld)loc.getWorld()).getHandle(), pos, yaw, pitch, null, // Camellia - Fix teleport missing original rotation and velocity
cause, teleportFlags, onComplete
);
},
diff --git a/net/minecraft/server/commands/TeleportCommand.java b/net/minecraft/server/commands/TeleportCommand.java
index f3b697eda0f47a2d1c64d7978ccdfb77fa226929..f657cda7c3856623f60f00f9fa44367ea771133c 100644
--- a/net/minecraft/server/commands/TeleportCommand.java
+++ b/net/minecraft/server/commands/TeleportCommand.java
@@ -247,10 +247,8 @@ public class TeleportCommand {
// Folia start - region threading
if (true) {
Vec3 posFinal = new Vec3(x, y, z);
- //Float yawFinal = Float.valueOf(newYRot); // Luminol - fix teleport yaw issue
- //Float pitchFinal = Float.valueOf(newXRot); // Luminol - fix teleport yaw issue
- Float yawFinal = Float.valueOf(newYRot + victim.getYRot()); // Luminol - fix teleport yaw issue
- Float pitchFinal = Float.valueOf(newXRot + victim.getXRot()); // Luminol - fix teleport pitch issue
+ Float yawFinal = Float.valueOf(newYRot + victim.getYRot()); // Camellia - Fix teleport missing original rotation and velocity
+ Float pitchFinal = Float.valueOf(newXRot + victim.getXRot()); // Camellia - Fix teleport missing original rotation and velocity
victim.getBukkitEntity().taskScheduler.schedule((Entity nmsEntity) -> {
nmsEntity.stopRiding();
nmsEntity.teleportAsync(
diff --git a/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java b/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java
index bc9ca4680c18f28b97c4d1afe202cc2fff320daf..08c3b8ee0ed015572823bb0218ac7522ec8945b3 100644
--- a/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java
+++ b/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java
@@ -156,7 +156,7 @@ public class ThrownEnderpearl extends ThrowableItemProjectile {
}
entity.teleportAsync(
- checkWorld, to, null, null, null,
+ checkWorld, to, null, null, entity.getDeltaMovement(), // Camellia - Fix teleport missing original rotation and velocity
org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.ENDER_PEARL,
// chunk could have been unloaded
Entity.TELEPORT_FLAG_TELEPORT_PASSENGERS | Entity.TELEPORT_FLAG_LOAD_CHUNK,
@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: NanaChiyo0721 <nanachiyo0721@163.com>
Date: Mon, 13 Jul 2026 23:10:31 +0800
Subject: [PATCH] Camellia: Replace packet processing queue with mt-queue
diff --git a/net/minecraft/network/PacketProcessor.java b/net/minecraft/network/PacketProcessor.java
index a25bc31c04778bf12e3b6301ffcfb2275fcc2d90..b91c669438dc4692144143c8eef0d3450a824969 100644
--- a/net/minecraft/network/PacketProcessor.java
+++ b/net/minecraft/network/PacketProcessor.java
@@ -11,7 +11,7 @@ import org.slf4j.Logger;
public class PacketProcessor implements AutoCloseable {
private static final Logger LOGGER = LogUtils.getLogger();
- private final Queue<PacketProcessor.ListenerAndPacket<?>> packetsToBeHandled = Queues.newConcurrentLinkedQueue();
+ private final Queue<PacketProcessor.ListenerAndPacket<?>> packetsToBeHandled = new ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue<>(); // Camellia - Replace packet processing queue with mt-queue
private final Thread runningThread;
private boolean closed;
@@ -0,0 +1,51 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Wed, 17 Dec 2025 00:20:57 +0800
Subject: [PATCH] Add null check in RegionizedWorldData >> conections
some of our function patches will crash because of get null in conn.getPlayer(), then when we process player.chunkPosition() will get NPE, so we need to fix it.
diff --git a/io/papermc/paper/threadedregions/RegionizedWorldData.java b/io/papermc/paper/threadedregions/RegionizedWorldData.java
index 491d108cc8e440653746f20fc5b5bca3b791eb88..5c6f40f82370b5a6f329dad6109bd4b7209c2e4c 100644
--- a/io/papermc/paper/threadedregions/RegionizedWorldData.java
+++ b/io/papermc/paper/threadedregions/RegionizedWorldData.java
@@ -231,14 +231,17 @@ public final class RegionizedWorldData {
regionToData.get(CoordinateUtils.getChunkKey(pos.x() >> chunkToRegionShift, pos.z() >> chunkToRegionShift)).itemEntityMovementTrackerMap.put(key, tracker);
}
// Luminol end
+ Set<Connection> cons = new java.util.HashSet<>(from.connections.size()); // Lophine - save all connections we have processed in connection process
// connections
for (final Connection conn : from.connections) {
final ServerPlayer player = conn.getPlayer();
+ if (player == null) continue; // Lophine - skip null player connections
final ChunkPos pos = player.chunkPosition();
// Note: It is impossible for an entity in the world to _not_ be in an entity chunk, which means
// the chunk holder must _exist_, and so the region section exists.
regionToData.get(CoordinateUtils.getChunkKey(pos.x() >> chunkToRegionShift, pos.z() >> chunkToRegionShift))
.connections.add(conn);
+ cons.add(conn); // Lophine - save all connections we have processed in connection process
}
// entities
for (final ServerPlayer player : from.localPlayers) {
@@ -249,6 +252,13 @@ public final class RegionizedWorldData {
into.localPlayers.add(player);
player.getBukkitEntity().updateRegion(into);
into.nearbyPlayers.addPlayer(player);
+ // Lophine start - we need to process the connection if player's connection is not processed, if connection.player is null, next tick will clean up it
+ final Connection conn = player.connection.connection;
+ if (!cons.contains(conn)) {
+ regionToData.get(CoordinateUtils.getChunkKey(pos.x() >> chunkToRegionShift, pos.z() >> chunkToRegionShift))
+ .connections.add(conn);
+ }
+ // Lophine end
}
for (final Entity entity : from.allEntities) {
final ChunkPos pos = entity.chunkPosition();
@@ -622,6 +632,7 @@ public final class RegionizedWorldData {
private static void cleanUpConnection(final Connection conn) {
// note: ALL connections HERE have a player
final ServerPlayer player = conn.getPlayer();
+ if (player == null) return; // Lophine - skip null player connections
// now that the connection is removed, we can allow this region to die
player.level().moonrise$getChunkTaskScheduler().chunkHolderManager.removeTicketAtLevel(
ServerGamePacketListenerImpl.DISCONNECT_TICKET, player.connection.disconnectPos,
@@ -35,7 +35,7 @@ index fcdfd4f6de5e1c2d5e80c7f3d8cb0b33a62d257d..6eaa4e9bd1ecbfa531313ce1f470d55e
metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> {
Map<String, Map<String, Integer>> map = new HashMap<>();
diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
index 45611868dfdfee05380c390a11e8041685a76a19..f6df01c381c7f8bb3c2da6d8833adc9c9f24bd37 100644
index 45611868dfdfee05380c390a11e8041685a76a19..0219692014bd5d7b52afcb847c5cd39c4dc63ff2 100644
--- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
+++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
@@ -40,8 +40,8 @@ public class PaperVersionFetcher implements VersionFetcher {
@@ -44,8 +44,8 @@ index 45611868dfdfee05380c390a11e8041685a76a19..f6df01c381c7f8bb3c2da6d8833adc9c
private static final int DISTANCE_UNKNOWN = -2;
- private static final String DOWNLOAD_PAGE = "https://github.com/LuminolMC/Luminol"; // Luminol
- private static final String REPOSITORY = "LuminolMC/Luminol"; // Luminol
+ private static final String DOWNLOAD_PAGE = "https://github.com/LuminolMC/Lophine"; // Luminol // Lophine
+ private static final String REPOSITORY = "LuminolMC/Lophine"; // Luminol // Lophine
+ private static final String DOWNLOAD_PAGE = "https://github.com/LophineLabs/Lophine"; // Luminol // Lophine
+ private static final String REPOSITORY = "LophineLabs/Lophine"; // Luminol // Lophine
private static final ServerBuildInfo BUILD_INFO = ServerBuildInfo.buildInfo();
private static final String USER_AGENT = BUILD_INFO.brandName() + "/" + BUILD_INFO.asString(VERSION_SIMPLE) + " (https://papermc.io)";
private static final Gson GSON = new Gson();
@@ -8,7 +8,7 @@ As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/3e7f4a313ff0d2469
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 ba0779211405b8b83f7c7e7b1c55b3e4846029a3..5f685b07878443bfe5f63147a0728df0708d79ad 100644
index f19ad4f317f911eb50802c325bcfce829d98524e..75ef8eab6665401bb2a9603de89172e341f8a2c4 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -483,6 +483,7 @@ public final class CraftServer implements Server {
@@ -53,7 +53,7 @@ index 5339ff469a38d35e72ea7227503a84395462f84a..d75b4ecb24f2ea42afc82ac255775d8f
if (nmsEntity.level() != this.getHandle().getLevel()) {
throw new IllegalArgumentException(entity + " wasn't created with this world, you must create the entity with the world you want to add it to.");
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 5f685b07878443bfe5f63147a0728df0708d79ad..0eb89fc127738e02e8d4d49418b83baaffe00cbd 100644
index 75ef8eab6665401bb2a9603de89172e341f8a2c4..42df6c661ec5ab18387b12743771bdb156a98e93 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -303,6 +303,7 @@ public final class CraftServer implements Server {
@@ -81,7 +81,7 @@ index 5f685b07878443bfe5f63147a0728df0708d79ad..0eb89fc127738e02e8d4d49418b83baa
return false;
}
@@ -3113,6 +3115,13 @@ public final class CraftServer implements Server {
@@ -3117,6 +3119,13 @@ public final class CraftServer implements Server {
}
// Leaves end - Feature API
@@ -21,7 +21,7 @@ index d0b763f07aee56f69a67c87d7dd15a44b6f44db7..bd301425f4c3cdf2b2bd22c1775b7ca9
}
// Leaves end - skip bot
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 0eb89fc127738e02e8d4d49418b83baaffe00cbd..53e49df883ec0fab984f2329d044d12ed6b1edff 100644
index 42df6c661ec5ab18387b12743771bdb156a98e93..feccf57b61200aead73fe97b897c1ad0fad563fb 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -304,6 +304,7 @@ public final class CraftServer implements Server {
@@ -41,7 +41,7 @@ index 0eb89fc127738e02e8d4d49418b83baaffe00cbd..53e49df883ec0fab984f2329d044d12e
@Override
public CraftPlayer apply(ServerPlayer player) {
return player.getBukkitEntity();
@@ -3186,4 +3187,11 @@ public final class CraftServer implements Server {
@@ -3190,4 +3191,11 @@ public final class CraftServer implements Server {
}
}
// Folia end - region TPS API
@@ -0,0 +1,759 @@
package fun.bm.lophine.bot;
import fun.bm.lophine.bot.action.gui.ActionType;
import fun.bm.lophine.bot.action.gui.GuiNode;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.item.component.ItemLore;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.agent.actions.AbstractBotAction;
import org.leavesmc.leaves.entity.bot.CraftBot;
import org.leavesmc.leaves.entity.bot.action.BotAction;
import org.leavesmc.leaves.entity.bot.actions.CraftBotAction;
import java.util.*;
public class BotActionGuiContainer extends SimpleContainer {
private static final Map<String, GuiRootNode> GUI_ROOT_NODE_MAP = new LinkedHashMap<>();
private static final int CONTAINER_SIZE = 54;
private static final int[] BORDER_SLOTS = {
0, 1, 2, 3, 4, 5, 6, 7, 8,
9, 17,
18, 26,
27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44,
45, 46, 47, 48, 49, 50, 51, 52, 53
};
public static final int[] CONTENT_SLOTS = {
10, 11, 12, 13, 14, 15, 16,
19, 20, 21, 22, 23, 24, 25
};
private static final int BACK_BUTTON_SLOT = 38;
private static final int COMMAND_BUILDER_SLOT = 40;
private static final int HOME_BUTTON_SLOT = 42;
private static final int PREV_PAGE_SLOT = 39;
private static final int NEXT_PAGE_SLOT = 41;
private static final ItemStack boarder = new ItemStack(Items.WHITE_STAINED_GLASS_PANE);
private static final ItemStack emptyActionPlaceholder = createEmptyActionPlaceholder();
private final CraftBot bot;
private final CraftPlayer player;
private final Deque<GuiNode> navigationStack = new ArrayDeque<>();
private final Deque<Boolean> fromRootLevelStack = new ArrayDeque<>(); // Track if we came from root level
private GuiNode currentNode = null;
// New state for action type selection flow
private ActionType selectedActionType = null;
private boolean isSelectingActionType = false;
// Pagination state
private int currentPage = 0;
public static void registerGuiRootNode(GuiRootNode guiRootNode) {
if (!GUI_ROOT_NODE_MAP.containsKey(guiRootNode.getName())) {
GUI_ROOT_NODE_MAP.put(guiRootNode.getName(), guiRootNode);
}
}
public static void unregisterGuiRootNode(String name) {
GUI_ROOT_NODE_MAP.remove(name);
}
public static GuiRootNode getGuiRootNode(String name) {
return GUI_ROOT_NODE_MAP.get(name);
}
public static Map<String, GuiRootNode> getGuiRootNodes() {
return new LinkedHashMap<>(GUI_ROOT_NODE_MAP);
}
public BotActionGuiContainer(@NotNull CraftBot bot, CraftPlayer player) {
super(CONTAINER_SIZE, player);
this.bot = bot;
this.player = player;
this.showActionTypes();
}
public boolean isCommandBuilderSlot(int id) {
return id == COMMAND_BUILDER_SLOT;
}
private void fillBorder() {
for (int slot : BORDER_SLOTS) {
this.setItem(slot, boarder);
}
}
private void showActionTypes() {
this.clearContent();
this.fillBorder();
this.currentNode = null;
this.navigationStack.clear();
this.fromRootLevelStack.clear();
this.selectedActionType = null;
this.isSelectingActionType = true;
this.currentPage = 0;
this.renderActionTypes();
this.addNavigationButtons();
}
private void showRootNodes() {
this.clearContent();
this.fillBorder();
this.currentNode = null;
this.navigationStack.clear();
this.fromRootLevelStack.clear();
this.isSelectingActionType = false;
this.currentPage = 0;
List<GuiRootNode> allNodes = new ArrayList<>(GUI_ROOT_NODE_MAP.values());
if (allNodes.isEmpty()) {
// No root nodes, go back to action type selection
this.showActionTypes();
return;
}
this.renderRootNodes();
this.addNavigationButtons();
}
public void navigateToChild(GuiNode node) {
if (node == null) {
return;
}
// If we're at root level (currentNode is null but not selecting action type),
// we need to track that we came from root nodes level
if (this.currentNode != null) {
this.navigationStack.push(this.currentNode);
this.fromRootLevelStack.push(false);
} else if (!this.isSelectingActionType && this.selectedActionType != null) {
// We're at root nodes level, mark that we should return to root nodes
this.fromRootLevelStack.push(true);
}
this.currentNode = node;
this.currentPage = 0;
this.refreshContainer();
}
public boolean navigateBack() {
// If we're at root node level (no navigation stack), go back to action type selection
if (this.navigationStack.isEmpty() && this.fromRootLevelStack.isEmpty()) {
// If we have a selected action type, we're at the root nodes level
if (!this.isSelectingActionType && this.selectedActionType != null) {
this.showActionTypes();
return true;
}
// Already at action type selection
return false;
}
// Check if we should return to root nodes level
boolean fromRootLevel = !this.fromRootLevelStack.isEmpty() && this.fromRootLevelStack.pop();
if (fromRootLevel) {
// Return to root nodes level
this.currentNode = null;
this.showRootNodes();
} else if (!this.navigationStack.isEmpty()) {
// Pop from navigation stack and refresh
this.currentNode = this.navigationStack.pop();
this.refreshContainer();
}
return true;
}
public void navigateHome() {
this.showActionTypes();
}
private void refreshContainer() {
this.clearContent();
this.fillBorder();
Set<GuiNode> children = this.getChildrenOfCurrentNode();
if (children == null || children.isEmpty()) {
// Empty page: auto-navigate back
this.navigateBack();
return;
}
this.renderCurrentNodeChildren();
this.addNavigationButtons();
}
@Nullable
private Set<GuiNode> getChildrenOfCurrentNode() {
if (this.currentNode == null) {
return null;
}
if (this.currentNode instanceof GuiRootNode rootNode) {
return rootNode.getChildren();
}
return null;
}
private void renderActionTypes() {
List<ActionType> allTypes = List.of(ActionType.values());
this.renderPagedItems(allTypes, (actionType, slot) -> {
ItemStack item = actionType.toConfirmNode(null).getItemStack();
if (item != null && !item.isEmpty()) {
this.setItem(slot, item);
}
});
}
private void renderRootNodes() {
List<GuiRootNode> allNodes = new ArrayList<>(GUI_ROOT_NODE_MAP.values());
this.renderPagedItems(allNodes, (node, slot) -> {
ItemStack item = createNodeItemWithRunHint(node);
if (item != null && !item.isEmpty()) {
this.setItem(slot, item);
}
});
}
private void renderCurrentNodeChildren() {
Set<GuiNode> children = this.getChildrenOfCurrentNode();
if (children != null && !children.isEmpty()) {
List<GuiNode> childList = new ArrayList<>(children);
this.renderPagedItems(childList, (node, slot) -> {
ItemStack item = createNodeItemWithRunHint(node);
if (item != null && !item.isEmpty()) {
this.setItem(slot, item);
}
});
}
}
private void renderBotActions() {
int actionSize = this.bot.getActionSize();
if (actionSize > 0) {
List<Integer> actionIndices = new ArrayList<>();
for (int i = 0; i < actionSize; i++) {
actionIndices.add(i);
}
this.renderPagedItems(actionIndices, (index, slot) -> {
BotAction<?> action = this.bot.getAction(index);
ItemStack item = createActionItem(action, index);
if (!item.isEmpty()) {
this.setItem(slot, item);
}
});
} else {
// Show empty placeholder when no actions are active
this.setItem(CONTENT_SLOTS[0], emptyActionPlaceholder);
}
}
private static ItemStack createEmptyActionPlaceholder() {
ItemStack item = new ItemStack(Items.STRUCTURE_VOID);
item.set(DataComponents.CUSTOM_NAME, Component.literal("§cNo active actions"));
item.set(DataComponents.LORE, new ItemLore(List.of(
Component.literal("§cThis bot has no running actions"),
Component.literal("§cUse START to add new actions")
)));
return item;
}
@Nullable
public GuiNode getGuiNodeAtSlot(int slot) {
if (slot == BACK_BUTTON_SLOT && this.canNavigateBack()) {
return null; // back button is handled separately
}
// Check if slot is a border slot
for (int borderSlot : BORDER_SLOTS) {
if (borderSlot == slot) {
return null;
}
}
Set<GuiNode> nodes = this.getCurrentDisplayNodes();
if (nodes == null) {
return null;
}
// Find the content index for this slot
int contentIndex = -1;
for (int i = 0; i < CONTENT_SLOTS.length; i++) {
if (CONTENT_SLOTS[i] == slot) {
contentIndex = i;
break;
}
}
if (contentIndex == -1) {
return null;
}
// Apply pagination offset
int actualIndex = this.currentPage * CONTENT_SLOTS.length + contentIndex;
int index = 0;
for (GuiNode node : nodes) {
if (index == actualIndex) {
return node;
}
index++;
}
return null;
}
public boolean isBackButtonSlot(int slot) {
return slot == BACK_BUTTON_SLOT && this.canNavigateBack();
}
public boolean isHomeButtonSlot(int slot) {
return slot == HOME_BUTTON_SLOT && this.canNavigateHome();
}
private static ItemStack createBackButtonItem() {
ItemStack item = new ItemStack(Items.RED_WOOL);
item.set(DataComponents.CUSTOM_NAME, Component.literal("§cBack"));
item.set(DataComponents.LORE, new ItemLore(List.of(Component.literal("Return to previous page"))));
return item;
}
private static ItemStack createHomeButtonItem() {
ItemStack item = new ItemStack(Items.RED_BED);
item.set(DataComponents.CUSTOM_NAME, Component.literal("§aHome"));
item.set(DataComponents.LORE, new ItemLore(List.of(Component.literal("Return to main menu"))));
return item;
}
private ItemStack createNodeItemWithRunHint(GuiNode node) {
ItemStack itemStack = node.getItemStack();
// Check if this is a last-level node (no children)
boolean isLastLevel;
if (node instanceof GuiRootNode rootNode) {
isLastLevel = rootNode.getChildren().isEmpty();
} else {
// Non-RootNode is always considered last level
isLastLevel = true;
}
// If it's the last level and we have an action type selected, add run hint
if (isLastLevel && this.selectedActionType != null && !this.isSelectingActionType) {
// Get existing lore or create new one
ItemLore existingLore = itemStack.get(DataComponents.LORE);
List<Component> loreLines = new ArrayList<>();
if (existingLore != null) {
loreLines.addAll(existingLore.lines());
}
// Add golden run hint
loreLines.add(Component.literal("§6Click to execute"));
itemStack.set(DataComponents.LORE, new ItemLore(loreLines));
}
return itemStack;
}
/**
* Render items with pagination support.
*
* @param items the full list of items
* @param renderer callback to render each item at a specific slot
*/
private <T> void renderPagedItems(List<T> items, PagedItemRenderer<T> renderer) {
int pageSize = CONTENT_SLOTS.length;
int totalPages = Math.max(1, (items.size() + pageSize - 1) / pageSize);
// Clamp current page
if (this.currentPage >= totalPages) {
this.currentPage = totalPages - 1;
}
if (this.currentPage < 0) {
this.currentPage = 0;
}
int start = this.currentPage * pageSize;
int end = Math.min(start + pageSize, items.size());
for (int i = start; i < end; i++) {
int slotIndex = i - start;
renderer.render(items.get(i), CONTENT_SLOTS[slotIndex]);
}
}
@FunctionalInterface
private interface PagedItemRenderer<T> {
void render(T item, int slot);
}
/**
* Add navigation buttons including pagination controls.
*/
private void addNavigationButtons() {
if (this.canNavigateBack()) {
this.setItem(BACK_BUTTON_SLOT, createBackButtonItem());
}
this.setItem(COMMAND_BUILDER_SLOT, createCommandBuilderItem());
if (this.canNavigateHome()) {
this.setItem(HOME_BUTTON_SLOT, createHomeButtonItem());
}
// Pagination buttons
if (this.hasPrevPage()) {
this.setItem(PREV_PAGE_SLOT, createPrevPageItem());
}
if (this.hasNextPage()) {
this.setItem(NEXT_PAGE_SLOT, createNextPageItem());
}
}
private boolean hasPrevPage() {
return this.currentPage > 0;
}
private boolean hasNextPage() {
int totalItems = this.getCurrentTotalItemCount();
int totalPages = Math.max(1, (totalItems + CONTENT_SLOTS.length - 1) / CONTENT_SLOTS.length);
return this.currentPage < totalPages - 1;
}
private int getCurrentTotalItemCount() {
if (this.isSelectingActionType) {
return ActionType.values().length;
}
if (this.selectedActionType == ActionType.ACTION_STOP) {
return this.bot.getActionSize();
}
if (this.currentNode == null) {
return GUI_ROOT_NODE_MAP.size();
}
Set<GuiNode> children = this.getChildrenOfCurrentNode();
return children != null ? children.size() : 0;
}
public void prevPage() {
if (this.hasPrevPage()) {
this.currentPage--;
this.redisplayCurrentViewWithoutReset();
}
}
public void nextPage() {
if (this.hasNextPage()) {
this.currentPage++;
this.redisplayCurrentViewWithoutReset();
}
}
public boolean isPrevPageSlot(int slot) {
return slot == PREV_PAGE_SLOT && this.hasPrevPage();
}
public boolean isNextPageSlot(int slot) {
return slot == NEXT_PAGE_SLOT && this.hasNextPage();
}
/**
* Re-render the current view without resetting pagination state.
* Used for page navigation to preserve currentPage value.
*/
private void redisplayCurrentViewWithoutReset() {
this.clearContent();
this.fillBorder();
if (this.isSelectingActionType) {
this.renderActionTypes();
} else if (this.selectedActionType == ActionType.ACTION_STOP) {
this.renderBotActions();
} else if (this.currentNode != null) {
this.renderCurrentNodeChildren();
} else {
this.renderRootNodes();
}
this.addNavigationButtons();
}
private static ItemStack createPrevPageItem() {
ItemStack item = new ItemStack(Items.ARROW);
item.set(DataComponents.CUSTOM_NAME, Component.literal("§ePrevious Page"));
return item;
}
private static ItemStack createNextPageItem() {
ItemStack item = new ItemStack(Items.ARROW);
item.set(DataComponents.CUSTOM_NAME, Component.literal("§eNext Page"));
return item;
}
private ItemStack createCommandBuilderItem() {
ItemStack item = new ItemStack(Items.BOOK);
// Build the complete command preview
String commandPreview = buildCommandPreview();
// Check if current node is runnable (confirmable)
boolean canRun = this.currentNode != null && this.currentNode.isConfirmable();
List<Component> loreLines = new ArrayList<>();
loreLines.add(Component.literal("§eCommand:"));
loreLines.add(Component.literal("§f" + commandPreview));
// Add run hint if the node is confirmable
if (canRun) {
loreLines.add(Component.literal("§6Click to execute"));
}
item.set(DataComponents.CUSTOM_NAME, Component.literal("§6Command Builder"));
item.set(DataComponents.LORE, new ItemLore(loreLines));
return item;
}
private String buildCommandPreview() {
if (this.selectedActionType == null) {
return "Select an action type first";
}
// For STOP action type, show different preview
if (this.selectedActionType == ActionType.ACTION_STOP) {
int actionSize = this.bot.getActionSize();
if (actionSize == 0) {
return "No actions to stop";
}
return "Click an action to stop it (" + actionSize + " active)";
}
try {
String actionPrefix = this.selectedActionType.getCommandActionPrefix();
String actionSuffix = this.selectedActionType.getCommandActionSuffix();
if (!actionSuffix.isEmpty()) {
actionSuffix = actionSuffix + " ";
}
String extra = actionPrefix + " " + this.bot.getName() + " " + actionSuffix;
// If we have a current node and it's a GuiRootNode, build the command
if (this.currentNode instanceof GuiRootNode rootNode) {
String command = rootNode.buildCommand(extra);
// Apply parameter limit based on ActionType
if (this.selectedActionType.getMaxAllowedParameters() == 0) {
command = actionPrefix + " " + this.bot.getName();
}
return command;
} else if (!this.isSelectingActionType) {
// At root nodes level or selecting action type
return extra.trim() + " <command>";
} else {
return "Select a command node";
}
} catch (Exception e) {
return "Error building command";
}
}
@Nullable
private Set<GuiNode> getCurrentDisplayNodes() {
// If selecting action type, return null (handled separately)
if (this.isSelectingActionType) {
return null;
}
if (this.currentNode == null) {
return new LinkedHashSet<>(GUI_ROOT_NODE_MAP.values());
}
return this.getChildrenOfCurrentNode();
}
/**
* Called when user selects an ActionType (START/STOP)
*/
public void selectActionType(ActionType actionType) {
this.selectedActionType = actionType;
this.isSelectingActionType = false;
// If STOP action type is selected, show current bot actions instead of root nodes
if (actionType == ActionType.ACTION_STOP) {
this.showCurrentBotActions();
} else {
this.showRootNodes();
}
}
/**
* Show the current bot's scheduled actions for stopping
*/
public void showCurrentBotActions() {
this.clearContent();
this.fillBorder();
this.currentNode = null;
this.navigationStack.clear();
this.fromRootLevelStack.clear();
this.isSelectingActionType = false;
this.currentPage = 0;
this.renderBotActions();
this.addNavigationButtons();
}
/**
* Create an ItemStack representing a bot action for display in the GUI
*/
private ItemStack createActionItem(BotAction<?> action, int index) {
// Use the same item model as registered in GuiRootNode (start action display)
AbstractBotAction<?> handle = ((CraftBotAction<?, ?>) action).getHandle();
GuiRootNode guiData = handle.getGuiData();
ItemStack item = guiData != null ? guiData.getItemStack() : new ItemStack(Items.PAPER);
String actionName = action.getName();
String actionHash = action.getUUID().toString();
item.set(DataComponents.CUSTOM_NAME, Component.literal("§c" + actionName));
// Store action hash in CUSTOM_DATA for later retrieval
CompoundTag customTag = new CompoundTag();
customTag.putString("action_hash", actionHash);
item.set(DataComponents.CUSTOM_DATA, CustomData.of(customTag));
List<Component> loreLines = new ArrayList<>();
CompoundTag nbt = new CompoundTag();
((CraftBotAction<?, ?>) action).getHandle().save(nbt);
nbt.forEach((key, tag) -> loreLines.add(Component.literal("§7" + key + ": §f" + tag)));
loreLines.add(Component.literal("§7Index: §f" + index));
loreLines.add(Component.literal("§7Hash: §f" + actionHash.substring(0, 8)));
loreLines.add(Component.literal("§6Click to stop this action"));
item.set(DataComponents.LORE, new ItemLore(loreLines));
return item;
}
/**
* Get the action hash (UUID string) at a specific slot (for STOP action type)
* Returns null if not found or not in STOP mode
*/
@Nullable
public String getActionHashAtSlot(int slot) {
if (this.selectedActionType != ActionType.ACTION_STOP) {
return null;
}
// Read the action hash directly from the ItemStack's CUSTOM_DATA
ItemStack item = this.getItem(slot);
if (item.isEmpty()) {
return null;
}
CustomData customData = item.get(DataComponents.CUSTOM_DATA);
if (customData == null) {
return null;
}
CompoundTag tag = customData.copyTag();
if (tag.contains("action_hash")) {
return tag.getString("action_hash").get();
}
return null;
}
/**
* Get the ActionType at a specific slot, accounting for pagination offset.
* Returns null if the slot is not a valid content slot or the index is out of bounds.
*/
@Nullable
public ActionType getActionTypeAtSlot(int slot) {
if (!this.isSelectingActionType) {
return null;
}
int contentIndex = -1;
for (int i = 0; i < CONTENT_SLOTS.length; i++) {
if (CONTENT_SLOTS[i] == slot) {
contentIndex = i;
break;
}
}
if (contentIndex == -1) {
return null;
}
ActionType[] actionTypes = ActionType.values();
int actualIndex = this.currentPage * CONTENT_SLOTS.length + contentIndex;
if (actualIndex >= 0 && actualIndex < actionTypes.length) {
return actionTypes[actualIndex];
}
return null;
}
/**
* Get the currently selected ActionType
*/
@Nullable
public ActionType getSelectedActionType() {
return this.selectedActionType;
}
/**
* Check if we're in action type selection mode
*/
public boolean isSelectingActionType() {
return this.isSelectingActionType;
}
public boolean canNavigateBack() {
return !this.navigationStack.isEmpty() || (!this.isSelectingActionType && this.selectedActionType != null);
}
public boolean canNavigateHome() {
return !this.isSelectingActionType && (this.currentNode != null || this.selectedActionType != null);
}
/**
* Check if the command builder can be executed.
* Returns true if there is a current node and it is confirmable.
*/
public boolean canExecuteCommandBuilder() {
return this.currentNode != null && this.currentNode.isConfirmable();
}
@Nullable
public GuiNode getCurrentNode() {
return this.currentNode;
}
/**
* Get the current parameter count (number of selected command nodes).
* This equals the navigation stack size plus 1 if currentNode is not null.
*/
public int getCurrentParameterCount() {
return this.navigationStack.size() + (this.currentNode != null ? 1 : 0);
}
public CraftBot getBot() {
return this.bot;
}
public CraftPlayer getPlayer() {
return this.player;
}
@Override
public boolean stillValid(@NotNull Player player) {
return this.player.getHandle() == player && this.bot.isValid();
}
}
@@ -0,0 +1,279 @@
package fun.bm.lophine.bot;
import com.mojang.logging.LogUtils;
import fun.bm.lophine.bot.action.gui.ActionType;
import fun.bm.lophine.bot.action.gui.GuiNode;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import fun.bm.lophine.carpet.config.modules.FakePlayerCompatConfig;
import fun.bm.lophine.config.modules.function.FakeplayerConfig;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ContainerInput;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.craftbukkit.inventory.CraftInventoryView;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.entity.bot.CraftBot;
import java.rmi.UnexpectedException;
public class BotActionGuiMenu extends AbstractContainerMenu {
private final BotActionGuiContainer container;
private final CraftBot bot;
private final CraftPlayer player;
private CraftInventoryView view = null;
public BotActionGuiMenu(int containerId, Inventory inventory, BotActionGuiContainer container) {
super(MenuType.GENERIC_9x6, containerId);
this.container = container;
this.bot = container.getBot();
this.player = container.getPlayer();
for (int row = 0; row < 6; row++) {
for (int col = 0; col < 9; col++) {
this.addSlot(new Slot(container, col + row * 9, 8 + col * 18, 18 + row * 18) {
@Override
public boolean mayPickup(Player player) {
return false;
}
@Override
public boolean mayPlace(ItemStack stack) {
return false;
}
});
}
}
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 9; col++) {
this.addSlot(new Slot(inventory, col + row * 9 + 9, 8 + col * 18, 140 + row * 18));
}
}
for (int col = 0; col < 9; col++) {
this.addSlot(new Slot(inventory, col, 8 + col * 18, 198));
}
}
@Override
public org.bukkit.inventory.InventoryView getBukkitView() {
if (this.view == null) {
CraftInventory inventory = new CraftInventory(this.container);
this.view = new CraftInventoryView(
this.player,
inventory,
this
);
}
return this.view;
}
@Override
public boolean stillValid(@NotNull Player player) {
return this.container.stillValid(player);
}
@Override
public void clicked(int slotIndex, int buttonNum, ContainerInput containerInput, Player player) {
if (slotIndex >= 0 && slotIndex < 54) {
if (this.container.isBackButtonSlot(slotIndex)) {
this.container.navigateBack();
this.refreshSlots();
return;
}
if (this.container.isHomeButtonSlot(slotIndex)) {
this.container.navigateHome();
this.refreshSlots();
return;
}
// Handle pagination buttons
if (this.container.isPrevPageSlot(slotIndex)) {
this.container.prevPage();
this.refreshSlots();
return;
}
if (this.container.isNextPageSlot(slotIndex)) {
this.container.nextPage();
this.refreshSlots();
return;
}
// Handle command builder click
if (this.container.isCommandBuilderSlot(slotIndex) && this.container.canExecuteCommandBuilder()) {
this.executeCommandBuilder(player);
this.refreshSlots();
return;
}
// Handle ActionType selection (with pagination offset)
if (this.container.isSelectingActionType()) {
ActionType clickedType = this.container.getActionTypeAtSlot(slotIndex);
if (clickedType != null) {
this.container.selectActionType(clickedType);
this.refreshSlots();
}
return;
}
// Handle action stop click (when STOP action type is selected)
if (this.container.getSelectedActionType() == ActionType.ACTION_STOP) {
String actionHash = this.container.getActionHashAtSlot(slotIndex);
if (actionHash != null) {
this.stopActionByHash(actionHash, player);
this.refreshSlots();
}
return;
}
GuiNode node = this.container.getGuiNodeAtSlot(slotIndex);
if (node != null) {
this.handleNodeClick(node, player);
this.refreshSlots();
}
return;
}
super.clicked(slotIndex, buttonNum, containerInput, player);
}
private void handleNodeClick(GuiNode node, Player player) {
if (node instanceof GuiRootNode rootNode) {
// Get the selected action type
ActionType actionType = this.container.getSelectedActionType();
if (actionType == null) {
return;
}
int maxAllowedParameters = actionType.getMaxAllowedParameters();
// If the node has children, check parameter limit before navigating
if (!rootNode.getChildren().isEmpty()) {
// Calculate current parameter count (navigation stack size + 1 for current node if exists)
int currentParamCount = this.container.getCurrentParameterCount();
// If adding this node would exceed the limit, execute command instead of navigating
if (currentParamCount + 1 > maxAllowedParameters) {
this.executeCommand(rootNode, actionType, player);
// Close GUI after START action execution
if (actionType == ActionType.ACTION_START && player instanceof ServerPlayer serverPlayer) {
serverPlayer.closeContainer();
}
} else {
this.container.navigateToChild(rootNode);
}
} else {
// Execute command with t`he selected action type
this.executeCommand(rootNode, actionType, player);
// Close GUI after START action execution
if (actionType == ActionType.ACTION_START && player instanceof ServerPlayer serverPlayer) {
serverPlayer.closeContainer();
}
}
}
}
private void executeCommand(GuiRootNode node, ActionType actionType, Player player) {
try {
String actionPrefix = actionType.getCommandActionPrefix();
String actionSuffix = actionType.getCommandActionSuffix();
if (!actionSuffix.isEmpty()) {
actionSuffix = actionSuffix + " ";
}
String extra = actionPrefix + " " + bot.getName() + " " + actionSuffix;
String command = node.buildCommand(extra);
// Apply parameter limit based on ActionType
if (actionType.getMaxAllowedParameters() == 0) {
// For actions like STOP that don't allow parameters, trim to just "action botName"
command = actionPrefix + " " + bot.getName();
}
if (player instanceof ServerPlayer serverPlayer) {
MinecraftServer.getServer().getCommands().performPrefixedCommand(
serverPlayer.createCommandSourceStack(),
command
);
}
} catch (Exception e) {
LogUtils.getLogger().warn("Error executing command: ", e);
}
}
/**
* Execute the command from the command builder (book item).
* Uses the current node and selected action type.
*/
private void executeCommandBuilder(Player player) {
GuiNode currentNode = this.container.getCurrentNode();
ActionType actionType = this.container.getSelectedActionType();
if (currentNode instanceof GuiRootNode rootNode && actionType != null) {
this.executeCommand(rootNode, actionType, player);
// Close GUI after START action execution
if (actionType != ActionType.ACTION_STOP && player instanceof ServerPlayer serverPlayer) {
serverPlayer.closeContainer();
}
}
}
/**
* Stop a bot action by its hash (UUID string)
*/
private void stopActionByHash(String actionHash, Player player) {
try {
if (player instanceof ServerPlayer serverPlayer) {
String command = getStopActionCommand(actionHash);
MinecraftServer.getServer().getCommands().performPrefixedCommand(
serverPlayer.createCommandSourceStack(),
command
);
}
} catch (Exception e) {
LogUtils.getLogger().warn("Error stopping action with hash {}: ", actionHash, e);
}
// Always refresh to show updated action list after stop attempt
this.container.showCurrentBotActions();
}
private String getStopActionCommand(String actionHash) throws UnexpectedException {
boolean botCommand = FakeplayerConfig.enable;
boolean playerCommand = FakePlayerCompatConfig.commandPlayer;
String command;
if (botCommand) {
command = "bot ";
} else if (playerCommand) {
command = "player ";
} else {
throw new UnexpectedException("Unable to build String from commandNode.");
}
command = command + "action " + this.bot.getName() + " stop " + actionHash;
return command;
}
private void refreshSlots() {
for (int i = 0; i < 54; i++) {
Slot slot = this.slots.get(i);
ItemStack item = this.container.getItem(i);
slot.set(item);
}
}
@Override
public ItemStack quickMoveStack(Player player, int index) {
return ItemStack.EMPTY;
}
public BotActionGuiContainer getContainer() {
return this.container;
}
}
@@ -0,0 +1,115 @@
package fun.bm.lophine.bot.action.gui;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.component.ItemLore;
import java.util.ArrayList;
import java.util.List;
public enum ActionType {
ACTION_START("Start", "Start a action", Items.LIME_DYE, "action", "start"),
ACTION_STOP("Stop", "Stop a action or see this bot scheduled tasks", Items.RED_DYE, "action", "stop", 0);
private final String displayName;
private final String description;
private final Item item;
private final String commandActionPrefix;
private final String commandActionSuffix;
private final int maxAllowedParameters;
ActionType(String displayName, String description, Item item, String commandActionPrefix) {
this(displayName, description, item, commandActionPrefix, "", Integer.MAX_VALUE);
}
ActionType(String displayName, String description, Item item, String commandActionPrefix, String commandActionSuffix) {
this(displayName, description, item, commandActionPrefix, commandActionSuffix, Integer.MAX_VALUE);
}
ActionType(String displayName, String description, Item item, String commandActionPrefix, String commandActionSuffix, int maxAllowedParameters) {
this.displayName = displayName;
this.description = description;
this.item = item;
this.commandActionPrefix = commandActionPrefix;
this.commandActionSuffix = commandActionSuffix;
this.maxAllowedParameters = maxAllowedParameters;
}
public String getDisplayName() {
return this.displayName;
}
public String getDescription() {
return this.description;
}
public Item getItem() {
return this.item;
}
public String getCommandActionPrefix() {
return this.commandActionPrefix;
}
public String getCommandActionSuffix() {
return commandActionSuffix;
}
public GuiNode toConfirmNode(GuiRootNode targetNode) {
return new ActionConfirmNode(this.displayName, this.description, this.item, targetNode, this.commandActionPrefix, this.maxAllowedParameters);
}
public int getMaxAllowedParameters() {
return maxAllowedParameters;
}
/**
* A special node used in the action type confirmation UI.
* It holds a reference to the target GuiRootNode and the selected action string.
*/
public static class ActionConfirmNode extends GuiNode {
private final GuiRootNode targetNode;
private final String action;
private final int maxAllowedParameters;
public ActionConfirmNode(String name, String description, Item item, GuiRootNode targetNode, String action, int maxAllowedParameters) {
super(name, description, item);
this.targetNode = targetNode;
this.action = action;
this.maxAllowedParameters = maxAllowedParameters;
}
public GuiRootNode getTargetNode() {
return this.targetNode;
}
public String getAction() {
return this.action;
}
public int getMaxAllowedParameters() {
return this.maxAllowedParameters;
}
@Override
public ItemStack getItemStack() {
ItemStack itemStack = super.getItemStack();
// Add parameter limit info to lore
List<Component> loreLines = new ArrayList<>();
if (this.description != null && !this.description.isEmpty()) {
loreLines.add(Component.literal(this.description));
}
if (!loreLines.isEmpty()) {
itemStack.set(DataComponents.LORE, new ItemLore(loreLines));
}
return itemStack;
}
}
}
@@ -0,0 +1,52 @@
package fun.bm.lophine.bot.action.gui;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.component.ItemLore;
import java.util.List;
public class GuiNode {
protected final String name;
protected final String description;
protected final Item item;
protected final boolean confirmable;
private static final Item defaultItem = Items.PAPER;
public GuiNode(String name, String description, Item item, boolean confirmable) {
this.name = name;
this.description = description;
this.item = item;
this.confirmable = confirmable;
}
public GuiNode(String name, String description, Item item) {
this(name, description, item, true);
}
public String getName() {
return this.name;
}
public String getDescription() {
return this.description;
}
public boolean isConfirmable() {
return this.confirmable;
}
public ItemStack getItemStack() {
ItemStack itemStack = new ItemStack(this.item == null ? defaultItem : this.item).copy();
itemStack.set(DataComponents.CUSTOM_NAME, Component.literal(this.name));
if (this.description != null && !this.description.isEmpty()) {
ItemLore lore = new ItemLore(List.of(Component.literal(this.description)));
itemStack.set(DataComponents.LORE, lore);
}
return itemStack;
}
}
@@ -0,0 +1,78 @@
package fun.bm.lophine.bot.action.gui;
import fun.bm.lophine.carpet.config.modules.FakePlayerCompatConfig;
import fun.bm.lophine.config.modules.function.FakeplayerConfig;
import net.minecraft.world.item.Item;
import java.rmi.UnexpectedException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class GuiRootNode extends GuiNode {
protected final Set<GuiNode> children;
protected final String commandNode;
public GuiRootNode(String name, String description, Item item, String commandNode) {
this(name, description, item, commandNode, true);
}
public GuiRootNode(String name, String description, Item item, String commandNode, boolean confirmable) {
this(name, description, item, new LinkedHashSet<>(), commandNode, confirmable);
}
public GuiRootNode(String name, String description, Item item, Set<GuiNode> children, String commandNode) {
this(name, description, item, children, commandNode, true);
}
public GuiRootNode(String name, String description, Item item, Set<GuiNode> children, String commandNode, boolean confirmable) {
super(name, description, item, confirmable);
this.children = children;
this.commandNode = commandNode;
}
public final void child(GuiNode... node) {
this.children.addAll(List.of(node));
}
@SafeVarargs
public final void child(Supplier<? extends GuiNode>... node) {
this.children.addAll(Stream.of(node).map(Supplier::get).toList());
}
public Set<GuiNode> getChildren() {
return this.children;
}
public Set<GuiNode> getAllFurthestChildren() {
if (this.children.isEmpty()) {
Set<GuiNode> result = new LinkedHashSet<>();
result.add(this);
return result;
}
Set<GuiNode> furthestChildren = new LinkedHashSet<>();
for (GuiNode child : this.children) {
furthestChildren.addAll(((GuiRootNode) child).getAllFurthestChildren());
}
return furthestChildren;
}
public String getCommandNode() {
return this.commandNode;
}
public String buildCommand(String extra) throws UnexpectedException {
boolean botCommand = FakeplayerConfig.enable;
boolean playerCommand = FakePlayerCompatConfig.commandPlayer;
if (botCommand) {
return "bot " + extra + this.getCommandNode();
}
if (playerCommand) {
return "player " + extra + this.getCommandNode();
}
throw new UnexpectedException("Unable to build String from commandNode.");
}
}
@@ -0,0 +1,37 @@
package fun.bm.lophine.bot.action.gui;
import net.minecraft.world.item.Item;
import java.rmi.UnexpectedException;
import java.util.LinkedHashSet;
import java.util.Set;
public class GuiSubNode extends GuiRootNode {
protected final GuiNode parent;
public GuiSubNode(String name, String description, Item item, GuiNode parent, Set<GuiNode> children, String commandNode) {
this(name, description, item, parent, children, commandNode, true);
}
public GuiSubNode(String name, String description, Item item, GuiNode parent, Set<GuiNode> children, String commandNode, boolean confirmable) {
super(name, description, item, children, commandNode, confirmable);
this.parent = parent;
}
public GuiSubNode(String name, String description, Item item, GuiNode parent, String commandNode) {
this(name, description, item, parent, commandNode, true);
}
public GuiSubNode(String name, String description, Item item, GuiNode parent, String commandNode, boolean confirmable) {
this(name, description, item, parent, new LinkedHashSet<>(), commandNode, confirmable);
}
public GuiNode getParent() {
return this.parent;
}
@Override
public String buildCommand(String extra) throws UnexpectedException {
return ((GuiRootNode) parent).buildCommand(extra) + " " + getCommandNode();
}
}
@@ -70,7 +70,7 @@ public class FakePlayerCompatConfig implements IConfigModule {
@Override
public void onLoaded(CommentedFileConfig configInstance, @Nullable Set<Exception> exs) {
if (commandPlayer) {
if (commandPlayer && command == null) {
command = new BotCommand("player");
command.register();
}
@@ -40,6 +40,11 @@ public class FakeplayerConfig implements IConfigModule {
Regeneration amount for fakeplayers""")
public static double regenAmount = 0.0;
@ConfigInfo(name = "open-action-gui", comments = """
Allow opening fakeplayer action gui,
need sneak to open if you enabled inventory open gui""")
public static boolean canOpenActionGui = false;
@ConfigInfo(name = "use-action", comments = """
Allow fakeplayers to use actions""")
public static boolean canUseAction = true;
@@ -95,7 +100,7 @@ public class FakeplayerConfig implements IConfigModule {
@Override
public void onLoaded(CommentedFileConfig configInstance, @Nullable Set<Exception> exs) {
if (enable) {
if (enable && command == null) {
command = new BotCommand("bot");
command.register();
}
@@ -20,6 +20,8 @@ package org.leavesmc.leaves.bot;
import com.google.common.collect.ImmutableMap;
import com.mojang.authlib.GameProfile;
import fun.bm.lophine.LophineLogger;
import fun.bm.lophine.bot.BotActionGuiContainer;
import fun.bm.lophine.bot.BotActionGuiMenu;
import fun.bm.lophine.carpet.config.modules.FakePlayerCompatConfig;
import fun.bm.lophine.config.modules.function.FakeplayerConfig;
import io.papermc.paper.adventure.PaperAdventure;
@@ -357,15 +359,28 @@ public class ServerBot extends ServerPlayer {
}
@Override
public @NotNull InteractionResult interact(@NotNull Player player, @NotNull InteractionHand hand, @NotNull net.minecraft.world.phys.Vec3 location) { // Leaves - Paper 26.1: Entity#interact now takes Vec3
if (FakePlayerCompatConfig.openFakePlayerInventory) {
if (player instanceof ServerPlayer player1 && player.getMainHandItem().isEmpty()) {
public @NotNull InteractionResult interact(@NotNull Player player, @NotNull InteractionHand hand, @NotNull net.minecraft.world.phys.Vec3 location) {
if (player instanceof ServerPlayer player1 && player.getMainHandItem().isEmpty()) {
boolean isSneaking = player.isShiftKeyDown();
boolean enabled1Only = FakePlayerCompatConfig.openFakePlayerInventory ^ FakeplayerConfig.canOpenActionGui;
boolean openInventory = enabled1Only ? FakePlayerCompatConfig.openFakePlayerInventory : FakePlayerCompatConfig.openFakePlayerInventory && !isSneaking;
boolean openActionGui = enabled1Only ? FakeplayerConfig.canOpenActionGui : FakeplayerConfig.canOpenActionGui && isSneaking;
if (openInventory) {
BotInventoryOpenEvent event = new BotInventoryOpenEvent(this.getBukkitEntity(), player1.getBukkitEntity());
getServer().server.getPluginManager().callEvent(event);
if (!event.isCancelled()) {
player.openMenu(new SimpleMenuProvider((i, inventory, p) -> ChestMenu.sixRows(i, inventory, this.container), this.getDisplayName()));
return InteractionResult.SUCCESS;
}
} else if (openActionGui) {
BotActionGuiOpenEvent event = new BotActionGuiOpenEvent(this.getBukkitEntity(), player1.getBukkitEntity());
getServer().server.getPluginManager().callEvent(event);
if (!event.isCancelled()) {
BotActionGuiContainer container = new BotActionGuiContainer(this.getBukkitEntity(), player1.getBukkitEntity());
player.openMenu(new SimpleMenuProvider((i, inventory, p) -> new BotActionGuiMenu(i, inventory, container), this.getDisplayName()));
return InteractionResult.SUCCESS;
}
}
}
return super.interact(player, hand, location);
@@ -17,6 +17,7 @@
package org.leavesmc.leaves.bot.agent;
import fun.bm.lophine.bot.BotActionGuiContainer;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -59,6 +60,9 @@ public class Actions {
if (!actionsByName.containsKey(action.getName())) {
actionsByName.put(action.getName(), action);
actionsByClass.put(type, action);
if (action.getGuiData() != null) {
BotActionGuiContainer.registerGuiRootNode(action.getGuiData());
}
return true;
}
return false;
@@ -70,6 +74,8 @@ public class Actions {
public static boolean unregister(@NotNull String name) {
AbstractBotAction<?> action = actionsByName.remove(name);
BotActionGuiContainer.unregisterGuiRootNode(name);
if (action != null) {
actionsByClass.remove(action.getClass());
return true;
@@ -20,10 +20,12 @@ package org.leavesmc.leaves.bot.agent.actions;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import fun.bm.lophine.LophineLogger;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import net.minecraft.core.UUIDUtil;
import net.minecraft.nbt.CompoundTag;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.ExtraData;
import org.leavesmc.leaves.command.CommandContext;
@@ -57,6 +59,8 @@ public abstract class AbstractBotAction<E extends AbstractBotAction<E>> {
private Consumer<E> onSuccess;
private Consumer<E> onStop;
protected GuiRootNode guiData;
public AbstractBotAction(String name, Supplier<E> creator) {
this.name = name;
this.uuid = UUID.randomUUID();
@@ -261,4 +265,8 @@ public abstract class AbstractBotAction<E extends AbstractBotAction<E>> {
public void setOnStop(Consumer<E> onStop) {
this.onStop = onStop;
}
public @Nullable GuiRootNode getGuiData() {
return this.guiData;
}
}
@@ -17,6 +17,8 @@
package org.leavesmc.leaves.bot.agent.actions;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import fun.bm.lophine.bot.action.gui.GuiSubNode;
import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.agent.ExtraData;
@@ -29,13 +31,37 @@ import static org.leavesmc.leaves.command.ArgumentNode.ArgumentSuggestions.strin
public abstract class AbstractTimerBotAction<E extends AbstractTimerBotAction<E>> extends AbstractBotAction<E> {
public AbstractTimerBotAction(String name, Supplier<E> creator) {
public AbstractTimerBotAction(String name, Supplier<E> creator, GuiRootNode guiData) {
super(name, creator);
this.addArgument("delay", integer(0)).suggests(strings("0", "5", "10", "20")).setOptional(true);
this.addArgument("interval", integer(0)).suggests(strings("20", "0", "5", "10")).setOptional(true);
String[] delaySuggestions = {"0", "5", "10", "20"};
String[] intervalSuggestions = {"20", "0", "5", "10"};
this.addArgument("delay", integer(0)).suggests(strings(delaySuggestions)).setOptional(true);
this.addArgument("interval", integer(0)).suggests(strings(intervalSuggestions)).setOptional(true);
this.addArgument("do_number", integer(-1))
.suggests(((context, builder) -> builder.suggest("-1", Component.literal("do infinite times"))))
.setOptional(true);
if (guiData == null) return;
this.guiData = guiData;
GuiSubNode[] node1 = new GuiSubNode[delaySuggestions.length];
for (int i = 0; i < delaySuggestions.length; i++) {
node1[i] = new GuiSubNode(delaySuggestions[i], "Delay for a few ticks", null, guiData, delaySuggestions[i]);
guiData.child(node1[i]);
}
GuiSubNode[] node2 = new GuiSubNode[delaySuggestions.length * intervalSuggestions.length];
for (int i = 0; i < delaySuggestions.length; i++) {
for (int j = 0; j < intervalSuggestions.length; j++) {
int id = i * intervalSuggestions.length + j;
node2[id] = new GuiSubNode(intervalSuggestions[j], "Interval for a few ticks", null, node1[i], intervalSuggestions[j]);
node1[i].child(node2[id]);
}
}
for (GuiSubNode node : node2) {
GuiSubNode subNode = new GuiSubNode("-1", "Number of times to do", null, node, "-1");
node.child(subNode);
}
}
@Override
@@ -17,6 +17,9 @@
package org.leavesmc.leaves.bot.agent.actions;
import fun.bm.lophine.bot.action.gui.GuiNode;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import fun.bm.lophine.bot.action.gui.GuiSubNode;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.world.InteractionResult;
@@ -35,8 +38,8 @@ public abstract class AbstractUseBotAction<T extends AbstractUseBotAction<T>> ex
private int alreadyUsedTick = 0;
private int useItemRemainingTicks = 0;
public AbstractUseBotAction(String name, Supplier<T> supplier) {
super(name, supplier);
public AbstractUseBotAction(String name, Supplier<T> supplier, GuiRootNode guiData) {
super(name, supplier, guiData);
this.addArgument("use_timeout", integer(-1))
.suggests((context, builder) -> {
builder.suggest("-1", Component.literal("no use timeout"));
@@ -44,6 +47,17 @@ public abstract class AbstractUseBotAction<T extends AbstractUseBotAction<T>> ex
builder.suggest("10", Component.literal("minimum trident shoot time"));
})
.setOptional(true);
if (guiData == null) return;
for (GuiNode node : guiData.getAllFurthestChildren()) {
GuiSubNode node0 = (GuiSubNode) node;
node0.child(
new GuiSubNode("-1", "no use timeout", null, node0, "-1"),
new GuiSubNode("3", "minimum bow shoot time", null, node0, "3"),
new GuiSubNode("10", "minimum trident shoot time", null, node0, "10")
);
}
}
@Override
@@ -17,6 +17,8 @@
package org.leavesmc.leaves.bot.agent.actions;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import net.minecraft.world.item.Items;
import net.minecraft.world.phys.EntityHitResult;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
@@ -25,7 +27,8 @@ import org.leavesmc.leaves.entity.bot.actions.CraftAttackAction;
public class ServerAttackAction extends AbstractTimerBotAction<ServerAttackAction> {
public ServerAttackAction() {
super("attack", ServerAttackAction::new);
GuiRootNode guiData = new GuiRootNode("Attack", "Attack an entity", Items.DIAMOND_SWORD, "attack");
super("attack", ServerAttackAction::new, guiData);
}
@Override
@@ -17,10 +17,12 @@
package org.leavesmc.leaves.bot.agent.actions;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import net.minecraft.core.BlockPos;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
@@ -33,7 +35,8 @@ import org.leavesmc.leaves.entity.bot.actions.CraftBreakBlockAction;
public class ServerBreakBlockAction extends AbstractTimerBotAction<ServerBreakBlockAction> {
public ServerBreakBlockAction() {
super("break", ServerBreakBlockAction::new);
GuiRootNode guiRootNode = new GuiRootNode("Break", "Break a block", Items.DIAMOND_PICKAXE, "break");
super("break", ServerBreakBlockAction::new, guiRootNode);
}
private ItemStack lastItem = null;
@@ -17,6 +17,8 @@
package org.leavesmc.leaves.bot.agent.actions;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.entity.bot.actions.CraftDropAction;
@@ -24,7 +26,8 @@ import org.leavesmc.leaves.entity.bot.actions.CraftDropAction;
public class ServerDropAction extends AbstractTimerBotAction<ServerDropAction> {
public ServerDropAction() {
super("drop", ServerDropAction::new);
GuiRootNode guiRootNode = new GuiRootNode("Drop", "Drop all items", Items.BARRIER, "drop");
super("drop", ServerDropAction::new, guiRootNode);
}
@Override
@@ -17,11 +17,13 @@
package org.leavesmc.leaves.bot.agent.actions;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.projectile.FishingHook;
import net.minecraft.world.item.FishingRodItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.entity.bot.actions.CraftFishAction;
@@ -29,7 +31,8 @@ import org.leavesmc.leaves.entity.bot.actions.CraftFishAction;
public class ServerFishAction extends AbstractTimerBotAction<ServerFishAction> {
public ServerFishAction() {
super("fish", ServerFishAction::new);
GuiRootNode guiRootNode = new GuiRootNode("Fish", "Fish", Items.FISHING_ROD, "fish");
super("fish", ServerFishAction::new, guiRootNode);
}
private static final int CATCH_ENTITY_DELAY = 20;
@@ -17,6 +17,8 @@
package org.leavesmc.leaves.bot.agent.actions;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.entity.bot.actions.CraftJumpAction;
@@ -24,7 +26,8 @@ import org.leavesmc.leaves.entity.bot.actions.CraftJumpAction;
public class ServerJumpAction extends AbstractTimerBotAction<ServerJumpAction> {
public ServerJumpAction() {
super("jump", ServerJumpAction::new);
GuiRootNode guiRootNode = new GuiRootNode("Jump", "Jump", Items.ELYTRA, "jump");
super("jump", ServerJumpAction::new, guiRootNode);
}
@Override
@@ -17,6 +17,8 @@
package org.leavesmc.leaves.bot.agent.actions;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import net.minecraft.world.item.Items;
import org.bukkit.Location;
import org.bukkit.craftbukkit.entity.CraftVehicle;
import org.bukkit.entity.Vehicle;
@@ -31,6 +33,8 @@ public class ServerMountAction extends AbstractBotAction<ServerMountAction> {
public ServerMountAction() {
super("mount", ServerMountAction::new);
this.guiData = new GuiRootNode("Mount", "Mount", Items.SADDLE, "mount");
}
@Override
@@ -17,6 +17,8 @@
package org.leavesmc.leaves.bot.agent.actions;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.ExtraData;
@@ -32,6 +34,11 @@ public class ServerMoveAction extends AbstractStateBotAction<ServerMoveAction> {
public ServerMoveAction() {
super("move", ServerMoveAction::new);
this.addArgument("direction", EnumArgumentType.fromEnum(MoveDirection.class));
this.guiData = new GuiRootNode("Move", "Move", null, "move", false);
for (MoveDirection direction : MoveDirection.values()) {
this.guiData.child(new GuiRootNode(direction.name, direction.name, Items.LEATHER_BOOTS, "move " + direction.name));
}
}
@Override
@@ -17,6 +17,7 @@
package org.leavesmc.leaves.bot.agent.actions;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.entity.bot.actions.CraftSneakAction;
@@ -26,6 +27,8 @@ public class ServerSneakAction extends AbstractStateBotAction<ServerSneakAction>
public ServerSneakAction() {
super("sneak", ServerSneakAction::new);
this.guiData = new GuiRootNode("Sneak", "Sneak", null, "sneak");
}
@Override
@@ -17,8 +17,10 @@
package org.leavesmc.leaves.bot.agent.actions;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.entity.bot.actions.CraftSwapAction;
@@ -27,6 +29,8 @@ public class ServerSwapAction extends AbstractBotAction<ServerSwapAction> {
public ServerSwapAction() {
super("swap", ServerSwapAction::new);
this.guiData = new GuiRootNode("Swap", "Swap", Items.SHIELD, "swap");
}
@Override
@@ -17,6 +17,8 @@
package org.leavesmc.leaves.bot.agent.actions;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import net.minecraft.world.item.Items;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
@@ -26,6 +28,8 @@ public class ServerSwimAction extends AbstractStateBotAction<ServerSwimAction> {
public ServerSwimAction() {
super("swim", ServerSwimAction::new);
this.guiData = new GuiRootNode("Swim", "Swim", Items.WATER_BUCKET, "swim");
}
@Override
@@ -26,7 +26,7 @@ import org.leavesmc.leaves.entity.bot.actions.CraftUseItemAction;
public class ServerUseItemAction extends AbstractUseBotAction<ServerUseItemAction> {
public ServerUseItemAction() {
super("use", ServerUseItemAction::new);
super("use", ServerUseItemAction::new, null);
}
@Override
@@ -17,10 +17,12 @@
package org.leavesmc.leaves.bot.agent.actions;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult;
@@ -37,7 +39,8 @@ import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemToAction.useIte
public class ServerUseItemAutoAction extends AbstractUseBotAction<ServerUseItemAutoAction> {
public ServerUseItemAutoAction() {
super("use_auto", ServerUseItemAutoAction::new);
GuiRootNode guiRootNode = new GuiRootNode("Use", "Use Item", Items.BOW, "use_auto");
super("use_auto", ServerUseItemAutoAction::new, guiRootNode);
}
@Override
@@ -27,7 +27,7 @@ import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemAction.useItem;
public class ServerUseItemOffhandAction extends AbstractUseBotAction<ServerUseItemOffhandAction> {
public ServerUseItemOffhandAction() {
super("use_offhand", ServerUseItemOffhandAction::new);
super("use_offhand", ServerUseItemOffhandAction::new, null);
}
@Override
@@ -28,7 +28,7 @@ import org.leavesmc.leaves.entity.bot.actions.CraftUseItemOnAction;
public class ServerUseItemOnAction extends AbstractUseBotAction<ServerUseItemOnAction> {
public ServerUseItemOnAction() {
super("use_on", ServerUseItemOnAction::new);
super("use_on", ServerUseItemOnAction::new, null);
}
@Override
@@ -28,7 +28,7 @@ import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemOnAction.useIte
public class ServerUseItemOnOffhandAction extends AbstractUseBotAction<ServerUseItemOnOffhandAction> {
public ServerUseItemOnOffhandAction() {
super("use_on_offhand", ServerUseItemOnOffhandAction::new);
super("use_on_offhand", ServerUseItemOnOffhandAction::new, null);
}
@Override
@@ -32,7 +32,7 @@ import org.leavesmc.leaves.entity.bot.actions.CraftUseItemToAction;
public class ServerUseItemToAction extends AbstractUseBotAction<ServerUseItemToAction> {
public ServerUseItemToAction() {
super("use_to", ServerUseItemToAction::new);
super("use_to", ServerUseItemToAction::new, null);
}
@Override
@@ -28,7 +28,7 @@ import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemToAction.useIte
public class ServerUseItemToOffhandAction extends AbstractUseBotAction<ServerUseItemToOffhandAction> {
public ServerUseItemToOffhandAction() {
super("use_to_offhand", ServerUseItemToOffhandAction::new);
super("use_to_offhand", ServerUseItemToOffhandAction::new, null);
}
@Override
@@ -27,10 +27,11 @@ import org.leavesmc.leaves.command.bot.subcommands.*;
import static org.leavesmc.leaves.command.CommandUtils.registerPermissions;
public class BotCommand extends RootNode {
private static final String PERM_BASE = "bukkit.command.bot";
private static final String PERM_BASE_BOT = "bukkit.command.bot";
private static final String PERM_BASE_PLAYER = "bukkit.command.player";
public BotCommand(String key) {
super(key, PERM_BASE);
super(key, getPermBase(key));
this.children(
ListCommand::new,
ConfigCommand::new,
@@ -44,15 +45,26 @@ public class BotCommand extends RootNode {
@Override
protected ArgumentBuilder<CommandSourceStack, ?> compile() {
registerPermissions(PERM_BASE, this.children);
registerPermissions(getPermBase(this.getName()), this.children);
return super.compile();
}
public static boolean hasPermission(@NotNull CommandSender sender) {
return sender.hasPermission(PERM_BASE);
return sender.hasPermission(PERM_BASE_BOT) || sender.hasPermission(PERM_BASE_PLAYER);
}
public static boolean hasPermission(@NotNull CommandSender sender, String subcommand) {
return sender.hasPermission(PERM_BASE) || sender.hasPermission(PERM_BASE + "." + subcommand);
return sender.hasPermission(PERM_BASE_BOT) || sender.hasPermission(PERM_BASE_BOT + "." + subcommand)
|| sender.hasPermission(PERM_BASE_PLAYER) || sender.hasPermission(PERM_BASE_PLAYER + "." + subcommand);
}
public static String getPermBase(String key) {
if (key.equals("bot")) {
return PERM_BASE_BOT;
}
if (key.equals("player")) {
return PERM_BASE_PLAYER;
}
throw new IllegalArgumentException("Invalid key: " + key);
}
}
@@ -56,11 +56,13 @@ public class StartCommand extends LiteralNode {
ServerBot bot = getBot(context);
CommandSender sender = context.getSender();
action.loadCommand(context);
if (bot.addBotAction(action, sender)) {
// Create a new instance of the action to avoid sharing state between multiple actions
AbstractBotAction<?> newAction = action.create();
newAction.loadCommand(context);
if (bot.addBotAction(newAction, sender)) {
sender.sendMessage(join(spaces(),
text("Action", GRAY),
text(action.getName(), AQUA).hoverEvent(showText(text(action.getActionDataString()))),
text(newAction.getName(), AQUA).hoverEvent(showText(text(newAction.getActionDataString()))),
text("has been issued to", GRAY),
asAdventure(bot.getDisplayName())
));
@@ -18,6 +18,7 @@
package org.leavesmc.leaves.command.bot.subcommands.action;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
@@ -49,6 +50,7 @@ public class StopCommand extends LiteralNode {
public StopCommand() {
super("stop");
children(StopIndexArgument::new);
children(StopHashArgument::new);
children(StopAll::new);
}
@@ -105,6 +107,47 @@ public class StopCommand extends LiteralNode {
}
}
private static class StopHashArgument extends ArgumentNode<String> {
private StopHashArgument() {
super("hash", StringArgumentType.string());
}
@Override
protected boolean execute(CommandContext context) throws CommandSyntaxException {
ServerBot bot = ActionCommand.BotArgument.getBot(context);
CommandSender sender = context.getSender();
String hash = context.getArgument(StopHashArgument.class);
AbstractBotAction<?> action = null;
for (AbstractBotAction<?> action1 : bot.getBotActions()) {
if (action1.getUUID().toString().equals(hash)) {
action = action1;
break;
}
}
if (action == null) throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create();
BotActionStopEvent event = new BotActionStopEvent(
bot.getBukkitEntity(), action.getName(), action.getUUID(), BotActionStopEvent.Reason.COMMAND, sender
);
event.callEvent();
if (!event.isCancelled()) {
action.stop(bot, BotActionStopEvent.Reason.COMMAND);
bot.getBotActions().remove(action);
sender.sendMessage(join(spaces(),
text("Already stopped", GRAY),
asAdventure(bot.getDisplayName()).append(text("'s", GRAY)),
text("action", GRAY),
text(action.getName(), AQUA).hoverEvent(showText(text(action.getActionDataString())))
));
} else {
sender.sendMessage(text("Action stop cancelled by a plugin", RED));
}
return true;
}
}
private static class StopAll extends LiteralNode {
private StopAll() {
@@ -141,13 +184,13 @@ public class StopCommand extends LiteralNode {
if (canceled.isEmpty()) {
sender.sendMessage(join(spaces(),
asAdventure(bot.getDisplayName()).append(text("'s", GRAY)),
text("'s action list cleared", GRAY)
text("action list cleared", GRAY)
));
} else {
sender.sendMessage(join(spaces(),
text("Tried to clear", GRAY),
asAdventure(bot.getDisplayName()).append(text("'s", GRAY)),
text("'s action list, but following actions' stop was canceled by plugin:", GRAY)
text("action list, but following actions' stop was canceled by plugin:", GRAY)
));
for (AbstractBotAction<?> action : canceled) {
context.getSender().sendMessage(