Compare commits

..

12 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
44 changed files with 1728 additions and 128 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 lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee 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 run: chmod +x gradlew
- name: Configure Git User Details - 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 - name: Apply All Patches
run: ./gradlew --refresh-dependencies applyAllPatches run: ./gradlew --refresh-dependencies applyAllPatches
@@ -124,7 +124,7 @@ jobs:
tag: ${{ env.tag }} tag: ${{ env.tag }}
name: ${{ env.project_id_b }} ${{ env.mcversion }} - ${{ env.commit_id }}${{ env.release_count }} name: ${{ env.project_id_b }} ${{ env.mcversion }} - ${{ env.commit_id }}${{ env.release_count }}
body: | 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 This release is automatically compiled by GitHub Actions
### Branch Info ### Branch Info
> ${{ github.ref_name }} > ${{ github.ref_name }}
@@ -143,7 +143,7 @@ jobs:
tag: ${{ env.tag }} tag: ${{ env.tag }}
name: ${{ env.project_id_b }} ${{ env.mcversion }} - ${{ env.commit_id }}${{ env.release_count }} name: ${{ env.project_id_b }} ${{ env.mcversion }} - ${{ env.commit_id }}${{ env.release_count }}
body: | 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 This release is automatically compiled by GitHub Actions
### Comments ### Comments
> ${{ inputs.comments }} > ${{ inputs.comments }}
+13 -43
View File
@@ -5,15 +5,15 @@
*Lophine 是一个基于Luminol的分支,具有许多有用的优化和可配置的原版特性,目标是在Folia上实现更多生电的内容(请注意,完整生电请使用Fabric)* *Lophine 是一个基于Luminol的分支,具有许多有用的优化和可配置的原版特性,目标是在Folia上实现更多生电的内容(请注意,完整生电请使用Fabric)*
![Created At](https://img.shields.io/github/created-at/LuminolMC/Lophine?style=for-the-badge&color=blue) ![Created At](https://img.shields.io/github/created-at/LophineLabs/Lophine?style=for-the-badge&color=blue)
[![License](https://img.shields.io/github/license/LuminolMC/Lophine?style=for-the-badge&color=green)](LICENSE.md) [![License](https://img.shields.io/github/license/LophineLabs/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) [![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) ![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/LuminolMC/Lophine?style=for-the-badge&color=yellow) ![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/LuminolMC/Lophine/total?style=for-the-badge&color=red) ![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) | **中文** [English](./README_EN.md) | **中文**
</div> </div>
@@ -32,7 +32,7 @@
## 📥 下载 ## 📥 下载
### 稳定版本 ### 稳定版本
所有发布版本都可以在 [Releases](https://github.com/LuminolMC/Lophine/releases) 页面找到。 所有发布版本都可以在 [Releases](https://github.com/LophineLabs/Lophine/releases) 页面找到。
### 开发版本 ### 开发版本
如果您想体验最新功能,可以通过以下步骤自行构建。 如果您想体验最新功能,可以通过以下步骤自行构建。
@@ -41,7 +41,7 @@
```bash ```bash
# 克隆项目 # 克隆项目
git clone https://github.com/LuminolMC/Lophine.git git clone https://github.com/LophineLabs/Lophine.git
cd Lophine cd Lophine
# 应用补丁并构建 Paperclip JAR # 应用补丁并构建 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群**: [1020403749](https://qm.qq.com/cgi-bin/qm/qr?k=y_MA9UaN7PM9e9J1LIs9Eea3LK8C0h6J&jump_from=webapi&authKey=ap5f8MlbeezXYtnmpnT5ZOFljDuOyV6OAb2PIcViQ+Ilr60Ycq63FDDTsJOZDYtj)
- **QQ频道**: [点击加入](https://pd.qq.com/s/eq9krf9j) - **Discord**: [点击加入](https://discord.gg/UXSgPZczcy)
- **Telegram**: [点击加入](https://t.me/LuminolMinecraft)
- **Discord**: [点击加入](https://discord.gg/Qd7m3V6eDx)
### 获取帮助 ### 获取帮助
- 📋 [提交 Issue](https://github.com/LuminolMC/Lophine/issues) - 📋 [提交 Issue](https://github.com/LophineLabs/Lophine/issues)
- 💬 [GitHub Discussions](https://github.com/LuminolMC/Lophine/discussions) - 💬 [GitHub Discussions](https://github.com/LophineLabs/Lophine/discussions)
- 📖 [项目文档](./docs/) - 📖 [项目文档](./docs/)
## 🐛 问题反馈 ## 🐛 问题反馈
@@ -124,34 +122,6 @@ dependencies {
![bStats](https://bstats.org/signatures/server-implementation/Lophine.svg "bStats") ![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 就是我们每一个前进的动力。 > 你的每一个免费的 ⭐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)* *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) ![Created At](https://img.shields.io/github/created-at/LophineLabs/Lophine?style=for-the-badge&color=blue)
[![License](https://img.shields.io/github/license/LuminolMC/Lophine?style=for-the-badge&color=green)](LICENSE.md) [![License](https://img.shields.io/github/license/LophineLabs/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) [![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) ![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/LuminolMC/Lophine?style=for-the-badge&color=yellow) ![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/LuminolMC/Lophine/total?style=for-the-badge&color=red) ![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) **English** | [中文](./README.md)
</div> </div>
@@ -32,7 +32,7 @@
## 📥 Download ## 📥 Download
### Stable Releases ### 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 ### Development Builds
If you want to experience the latest features, you can build it yourself following the steps below. 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 ```bash
# Clone the project # Clone the project
git clone https://github.com/LuminolMC/Lophine.git git clone https://github.com/LophineLabs/Lophine.git
cd Lophine cd Lophine
# Apply patches and build Paperclip JAR # Apply patches and build Paperclip JAR
@@ -92,15 +92,13 @@ dependencies {
### Join Our Community ### 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 Group**: [1020403749](https://qm.qq.com/cgi-bin/qm/qr?k=y_MA9UaN7PM9e9J1LIs9Eea3LK8C0h6J&jump_from=webapi&authKey=ap5f8MlbeezXYtnmpnT5ZOFljDuOyV6OAb2PIcViQ+Ilr60Ycq63FDDTsJOZDYtj)
- **QQ Channel**: [Join Here](https://pd.qq.com/s/eq9krf9j) - **Discord**: [Join Here](https://discord.gg/UXSgPZczcy)
- **Telegram**: [Join Here](https://t.me/LuminolMinecraft)
- **Discord**: [Join Here](https://discord.gg/Qd7m3V6eDx)
### Get Help ### Get Help
- 📋 [Submit Issues](https://github.com/LuminolMC/Lophine/issues) - 📋 [Submit Issues](https://github.com/LophineLabs/Lophine/issues)
- 💬 [GitHub Discussions](https://github.com/LuminolMC/Lophine/discussions) - 💬 [GitHub Discussions](https://github.com/LophineLabs/Lophine/discussions)
- 📖 [Project Documentation](./docs/) - 📖 [Project Documentation](./docs/)
## 🐛 Bug Reports ## 🐛 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") ![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! ## ⭐ Give Us a Star!
> Every free ⭐Star you give is the motivation for our every step forward. > 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 { paperweight {
filterPatches = false filterPatches = false
upstreams.register("luminol") { upstreams.register("luminol") {
repo = github("LuminolMC", "Luminol") repo = github("LuminolCustomArchive", "Luminol")
ref = providers.gradleProperty("luminolRef") ref = providers.gradleProperty("luminolRef")
println("Upstream commit ref: " + ref.get()) println("Upstream commit ref: " + ref.get())
@@ -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") { val log4jPlugins = sourceSets.create("log4jPlugins") {
@@ -158,7 +_,7 @@ @@ -158,10 +_,10 @@
} }
dependencies { dependencies {
@@ -50,7 +50,11 @@
+ implementation(project(":lophine-api")) + implementation(project(":lophine-api"))
// Luminol start - Dependenices insert // Luminol start - Dependenices insert
implementation("net.objecthunter:exp4j:0.4.8") 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 @@ @@ -234,14 +_,14 @@
val gitBranch = git.exec(providers, "rev-parse", "--abbrev-ref", "HEAD").get().trim() val gitBranch = git.exec(providers, "rev-parse", "--abbrev-ref", "HEAD").get().trim()
attributes( attributes(
@@ -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", () -> { metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> {
Map<String, Map<String, Integer>> map = new HashMap<>(); 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 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 --- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
+++ b/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 { @@ -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 int DISTANCE_UNKNOWN = -2;
- private static final String DOWNLOAD_PAGE = "https://github.com/LuminolMC/Luminol"; // Luminol - private static final String DOWNLOAD_PAGE = "https://github.com/LuminolMC/Luminol"; // Luminol
- private static final String REPOSITORY = "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 DOWNLOAD_PAGE = "https://github.com/LophineLabs/Lophine"; // Luminol // Lophine
+ private static final String REPOSITORY = "LuminolMC/Lophine"; // Luminol // Lophine + private static final String REPOSITORY = "LophineLabs/Lophine"; // Luminol // Lophine
private static final ServerBuildInfo BUILD_INFO = ServerBuildInfo.buildInfo(); 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 String USER_AGENT = BUILD_INFO.brandName() + "/" + BUILD_INFO.asString(VERSION_SIMPLE) + " (https://papermc.io)";
private static final Gson GSON = new Gson(); private static final Gson GSON = new Gson();
@@ -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 @Override
public void onLoaded(CommentedFileConfig configInstance, @Nullable Set<Exception> exs) { public void onLoaded(CommentedFileConfig configInstance, @Nullable Set<Exception> exs) {
if (commandPlayer) { if (commandPlayer && command == null) {
command = new BotCommand("player"); command = new BotCommand("player");
command.register(); command.register();
} }
@@ -40,6 +40,11 @@ public class FakeplayerConfig implements IConfigModule {
Regeneration amount for fakeplayers""") Regeneration amount for fakeplayers""")
public static double regenAmount = 0.0; 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 = """ @ConfigInfo(name = "use-action", comments = """
Allow fakeplayers to use actions""") Allow fakeplayers to use actions""")
public static boolean canUseAction = true; public static boolean canUseAction = true;
@@ -95,7 +100,7 @@ public class FakeplayerConfig implements IConfigModule {
@Override @Override
public void onLoaded(CommentedFileConfig configInstance, @Nullable Set<Exception> exs) { public void onLoaded(CommentedFileConfig configInstance, @Nullable Set<Exception> exs) {
if (enable) { if (enable && command == null) {
command = new BotCommand("bot"); command = new BotCommand("bot");
command.register(); command.register();
} }
@@ -20,6 +20,8 @@ package org.leavesmc.leaves.bot;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import fun.bm.lophine.LophineLogger; 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.carpet.config.modules.FakePlayerCompatConfig;
import fun.bm.lophine.config.modules.function.FakeplayerConfig; import fun.bm.lophine.config.modules.function.FakeplayerConfig;
import io.papermc.paper.adventure.PaperAdventure; import io.papermc.paper.adventure.PaperAdventure;
@@ -357,15 +359,28 @@ public class ServerBot extends ServerPlayer {
} }
@Override @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 public @NotNull InteractionResult interact(@NotNull Player player, @NotNull InteractionHand hand, @NotNull net.minecraft.world.phys.Vec3 location) {
if (FakePlayerCompatConfig.openFakePlayerInventory) { if (player instanceof ServerPlayer player1 && player.getMainHandItem().isEmpty()) {
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()); BotInventoryOpenEvent event = new BotInventoryOpenEvent(this.getBukkitEntity(), player1.getBukkitEntity());
getServer().server.getPluginManager().callEvent(event); getServer().server.getPluginManager().callEvent(event);
if (!event.isCancelled()) { if (!event.isCancelled()) {
player.openMenu(new SimpleMenuProvider((i, inventory, p) -> ChestMenu.sixRows(i, inventory, this.container), this.getDisplayName())); player.openMenu(new SimpleMenuProvider((i, inventory, p) -> ChestMenu.sixRows(i, inventory, this.container), this.getDisplayName()));
return InteractionResult.SUCCESS; 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); return super.interact(player, hand, location);
@@ -17,6 +17,7 @@
package org.leavesmc.leaves.bot.agent; package org.leavesmc.leaves.bot.agent;
import fun.bm.lophine.bot.BotActionGuiContainer;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -59,6 +60,9 @@ public class Actions {
if (!actionsByName.containsKey(action.getName())) { if (!actionsByName.containsKey(action.getName())) {
actionsByName.put(action.getName(), action); actionsByName.put(action.getName(), action);
actionsByClass.put(type, action); actionsByClass.put(type, action);
if (action.getGuiData() != null) {
BotActionGuiContainer.registerGuiRootNode(action.getGuiData());
}
return true; return true;
} }
return false; return false;
@@ -70,6 +74,8 @@ public class Actions {
public static boolean unregister(@NotNull String name) { public static boolean unregister(@NotNull String name) {
AbstractBotAction<?> action = actionsByName.remove(name); AbstractBotAction<?> action = actionsByName.remove(name);
BotActionGuiContainer.unregisterGuiRootNode(name);
if (action != null) { if (action != null) {
actionsByClass.remove(action.getClass()); actionsByClass.remove(action.getClass());
return true; return true;
@@ -20,10 +20,12 @@ package org.leavesmc.leaves.bot.agent.actions;
import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.CommandSyntaxException;
import fun.bm.lophine.LophineLogger; import fun.bm.lophine.LophineLogger;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import net.minecraft.core.UUIDUtil; import net.minecraft.core.UUIDUtil;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.ExtraData; import org.leavesmc.leaves.bot.agent.ExtraData;
import org.leavesmc.leaves.command.CommandContext; 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> onSuccess;
private Consumer<E> onStop; private Consumer<E> onStop;
protected GuiRootNode guiData;
public AbstractBotAction(String name, Supplier<E> creator) { public AbstractBotAction(String name, Supplier<E> creator) {
this.name = name; this.name = name;
this.uuid = UUID.randomUUID(); this.uuid = UUID.randomUUID();
@@ -261,4 +265,8 @@ public abstract class AbstractBotAction<E extends AbstractBotAction<E>> {
public void setOnStop(Consumer<E> onStop) { public void setOnStop(Consumer<E> onStop) {
this.onStop = onStop; this.onStop = onStop;
} }
public @Nullable GuiRootNode getGuiData() {
return this.guiData;
}
} }
@@ -17,6 +17,8 @@
package org.leavesmc.leaves.bot.agent.actions; 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 net.minecraft.network.chat.Component;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.agent.ExtraData; 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 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); super(name, creator);
this.addArgument("delay", integer(0)).suggests(strings("0", "5", "10", "20")).setOptional(true); String[] delaySuggestions = {"0", "5", "10", "20"};
this.addArgument("interval", integer(0)).suggests(strings("20", "0", "5", "10")).setOptional(true); 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)) this.addArgument("do_number", integer(-1))
.suggests(((context, builder) -> builder.suggest("-1", Component.literal("do infinite times")))) .suggests(((context, builder) -> builder.suggest("-1", Component.literal("do infinite times"))))
.setOptional(true); .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 @Override
@@ -17,6 +17,9 @@
package org.leavesmc.leaves.bot.agent.actions; 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.nbt.CompoundTag;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResult;
@@ -35,8 +38,8 @@ public abstract class AbstractUseBotAction<T extends AbstractUseBotAction<T>> ex
private int alreadyUsedTick = 0; private int alreadyUsedTick = 0;
private int useItemRemainingTicks = 0; private int useItemRemainingTicks = 0;
public AbstractUseBotAction(String name, Supplier<T> supplier) { public AbstractUseBotAction(String name, Supplier<T> supplier, GuiRootNode guiData) {
super(name, supplier); super(name, supplier, guiData);
this.addArgument("use_timeout", integer(-1)) this.addArgument("use_timeout", integer(-1))
.suggests((context, builder) -> { .suggests((context, builder) -> {
builder.suggest("-1", Component.literal("no use timeout")); 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")); builder.suggest("10", Component.literal("minimum trident shoot time"));
}) })
.setOptional(true); .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 @Override
@@ -17,6 +17,8 @@
package org.leavesmc.leaves.bot.agent.actions; 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 net.minecraft.world.phys.EntityHitResult;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot; 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 class ServerAttackAction extends AbstractTimerBotAction<ServerAttackAction> {
public 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 @Override
@@ -17,10 +17,12 @@
package org.leavesmc.leaves.bot.agent.actions; package org.leavesmc.leaves.bot.agent.actions;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3; 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 class ServerBreakBlockAction extends AbstractTimerBotAction<ServerBreakBlockAction> {
public 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; private ItemStack lastItem = null;
@@ -17,6 +17,8 @@
package org.leavesmc.leaves.bot.agent.actions; 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.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.entity.bot.actions.CraftDropAction; 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 class ServerDropAction extends AbstractTimerBotAction<ServerDropAction> {
public ServerDropAction() { public ServerDropAction() {
super("drop", ServerDropAction::new); GuiRootNode guiRootNode = new GuiRootNode("Drop", "Drop all items", Items.BARRIER, "drop");
super("drop", ServerDropAction::new, guiRootNode);
} }
@Override @Override
@@ -17,11 +17,13 @@
package org.leavesmc.leaves.bot.agent.actions; package org.leavesmc.leaves.bot.agent.actions;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.projectile.FishingHook; import net.minecraft.world.entity.projectile.FishingHook;
import net.minecraft.world.item.FishingRodItem; import net.minecraft.world.item.FishingRodItem;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.entity.bot.actions.CraftFishAction; 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 class ServerFishAction extends AbstractTimerBotAction<ServerFishAction> {
public 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; private static final int CATCH_ENTITY_DELAY = 20;
@@ -17,6 +17,8 @@
package org.leavesmc.leaves.bot.agent.actions; 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.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.entity.bot.actions.CraftJumpAction; 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 class ServerJumpAction extends AbstractTimerBotAction<ServerJumpAction> {
public ServerJumpAction() { public ServerJumpAction() {
super("jump", ServerJumpAction::new); GuiRootNode guiRootNode = new GuiRootNode("Jump", "Jump", Items.ELYTRA, "jump");
super("jump", ServerJumpAction::new, guiRootNode);
} }
@Override @Override
@@ -17,6 +17,8 @@
package org.leavesmc.leaves.bot.agent.actions; 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.Location;
import org.bukkit.craftbukkit.entity.CraftVehicle; import org.bukkit.craftbukkit.entity.CraftVehicle;
import org.bukkit.entity.Vehicle; import org.bukkit.entity.Vehicle;
@@ -31,6 +33,8 @@ public class ServerMountAction extends AbstractBotAction<ServerMountAction> {
public ServerMountAction() { public ServerMountAction() {
super("mount", ServerMountAction::new); super("mount", ServerMountAction::new);
this.guiData = new GuiRootNode("Mount", "Mount", Items.SADDLE, "mount");
} }
@Override @Override
@@ -17,6 +17,8 @@
package org.leavesmc.leaves.bot.agent.actions; 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.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.ExtraData; import org.leavesmc.leaves.bot.agent.ExtraData;
@@ -32,6 +34,11 @@ public class ServerMoveAction extends AbstractStateBotAction<ServerMoveAction> {
public ServerMoveAction() { public ServerMoveAction() {
super("move", ServerMoveAction::new); super("move", ServerMoveAction::new);
this.addArgument("direction", EnumArgumentType.fromEnum(MoveDirection.class)); 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 @Override
@@ -17,6 +17,7 @@
package org.leavesmc.leaves.bot.agent.actions; package org.leavesmc.leaves.bot.agent.actions;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.entity.bot.actions.CraftSneakAction; import org.leavesmc.leaves.entity.bot.actions.CraftSneakAction;
@@ -26,6 +27,8 @@ public class ServerSneakAction extends AbstractStateBotAction<ServerSneakAction>
public ServerSneakAction() { public ServerSneakAction() {
super("sneak", ServerSneakAction::new); super("sneak", ServerSneakAction::new);
this.guiData = new GuiRootNode("Sneak", "Sneak", null, "sneak");
} }
@Override @Override
@@ -17,8 +17,10 @@
package org.leavesmc.leaves.bot.agent.actions; package org.leavesmc.leaves.bot.agent.actions;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.entity.bot.actions.CraftSwapAction; import org.leavesmc.leaves.entity.bot.actions.CraftSwapAction;
@@ -27,6 +29,8 @@ public class ServerSwapAction extends AbstractBotAction<ServerSwapAction> {
public ServerSwapAction() { public ServerSwapAction() {
super("swap", ServerSwapAction::new); super("swap", ServerSwapAction::new);
this.guiData = new GuiRootNode("Swap", "Swap", Items.SHIELD, "swap");
} }
@Override @Override
@@ -17,6 +17,8 @@
package org.leavesmc.leaves.bot.agent.actions; 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 net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.bot.ServerBot;
@@ -26,6 +28,8 @@ public class ServerSwimAction extends AbstractStateBotAction<ServerSwimAction> {
public ServerSwimAction() { public ServerSwimAction() {
super("swim", ServerSwimAction::new); super("swim", ServerSwimAction::new);
this.guiData = new GuiRootNode("Swim", "Swim", Items.WATER_BUCKET, "swim");
} }
@Override @Override
@@ -26,7 +26,7 @@ import org.leavesmc.leaves.entity.bot.actions.CraftUseItemAction;
public class ServerUseItemAction extends AbstractUseBotAction<ServerUseItemAction> { public class ServerUseItemAction extends AbstractUseBotAction<ServerUseItemAction> {
public ServerUseItemAction() { public ServerUseItemAction() {
super("use", ServerUseItemAction::new); super("use", ServerUseItemAction::new, null);
} }
@Override @Override
@@ -17,10 +17,12 @@
package org.leavesmc.leaves.bot.agent.actions; package org.leavesmc.leaves.bot.agent.actions;
import fun.bm.lophine.bot.action.gui.GuiRootNode;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.decoration.ArmorStand; import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.EntityHitResult; import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult; 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 class ServerUseItemAutoAction extends AbstractUseBotAction<ServerUseItemAutoAction> {
public 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 @Override
@@ -27,7 +27,7 @@ import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemAction.useItem;
public class ServerUseItemOffhandAction extends AbstractUseBotAction<ServerUseItemOffhandAction> { public class ServerUseItemOffhandAction extends AbstractUseBotAction<ServerUseItemOffhandAction> {
public ServerUseItemOffhandAction() { public ServerUseItemOffhandAction() {
super("use_offhand", ServerUseItemOffhandAction::new); super("use_offhand", ServerUseItemOffhandAction::new, null);
} }
@Override @Override
@@ -28,7 +28,7 @@ import org.leavesmc.leaves.entity.bot.actions.CraftUseItemOnAction;
public class ServerUseItemOnAction extends AbstractUseBotAction<ServerUseItemOnAction> { public class ServerUseItemOnAction extends AbstractUseBotAction<ServerUseItemOnAction> {
public ServerUseItemOnAction() { public ServerUseItemOnAction() {
super("use_on", ServerUseItemOnAction::new); super("use_on", ServerUseItemOnAction::new, null);
} }
@Override @Override
@@ -28,7 +28,7 @@ import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemOnAction.useIte
public class ServerUseItemOnOffhandAction extends AbstractUseBotAction<ServerUseItemOnOffhandAction> { public class ServerUseItemOnOffhandAction extends AbstractUseBotAction<ServerUseItemOnOffhandAction> {
public ServerUseItemOnOffhandAction() { public ServerUseItemOnOffhandAction() {
super("use_on_offhand", ServerUseItemOnOffhandAction::new); super("use_on_offhand", ServerUseItemOnOffhandAction::new, null);
} }
@Override @Override
@@ -32,7 +32,7 @@ import org.leavesmc.leaves.entity.bot.actions.CraftUseItemToAction;
public class ServerUseItemToAction extends AbstractUseBotAction<ServerUseItemToAction> { public class ServerUseItemToAction extends AbstractUseBotAction<ServerUseItemToAction> {
public ServerUseItemToAction() { public ServerUseItemToAction() {
super("use_to", ServerUseItemToAction::new); super("use_to", ServerUseItemToAction::new, null);
} }
@Override @Override
@@ -28,7 +28,7 @@ import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemToAction.useIte
public class ServerUseItemToOffhandAction extends AbstractUseBotAction<ServerUseItemToOffhandAction> { public class ServerUseItemToOffhandAction extends AbstractUseBotAction<ServerUseItemToOffhandAction> {
public ServerUseItemToOffhandAction() { public ServerUseItemToOffhandAction() {
super("use_to_offhand", ServerUseItemToOffhandAction::new); super("use_to_offhand", ServerUseItemToOffhandAction::new, null);
} }
@Override @Override
@@ -27,10 +27,11 @@ import org.leavesmc.leaves.command.bot.subcommands.*;
import static org.leavesmc.leaves.command.CommandUtils.registerPermissions; import static org.leavesmc.leaves.command.CommandUtils.registerPermissions;
public class BotCommand extends RootNode { 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) { public BotCommand(String key) {
super(key, PERM_BASE); super(key, getPermBase(key));
this.children( this.children(
ListCommand::new, ListCommand::new,
ConfigCommand::new, ConfigCommand::new,
@@ -44,15 +45,26 @@ public class BotCommand extends RootNode {
@Override @Override
protected ArgumentBuilder<CommandSourceStack, ?> compile() { protected ArgumentBuilder<CommandSourceStack, ?> compile() {
registerPermissions(PERM_BASE, this.children); registerPermissions(getPermBase(this.getName()), this.children);
return super.compile(); return super.compile();
} }
public static boolean hasPermission(@NotNull CommandSender sender) { 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) { 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); ServerBot bot = getBot(context);
CommandSender sender = context.getSender(); CommandSender sender = context.getSender();
action.loadCommand(context); // Create a new instance of the action to avoid sharing state between multiple actions
if (bot.addBotAction(action, sender)) { AbstractBotAction<?> newAction = action.create();
newAction.loadCommand(context);
if (bot.addBotAction(newAction, sender)) {
sender.sendMessage(join(spaces(), sender.sendMessage(join(spaces(),
text("Action", GRAY), 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), text("has been issued to", GRAY),
asAdventure(bot.getDisplayName()) asAdventure(bot.getDisplayName())
)); ));
@@ -18,6 +18,7 @@
package org.leavesmc.leaves.command.bot.subcommands.action; package org.leavesmc.leaves.command.bot.subcommands.action;
import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder; import com.mojang.brigadier.suggestion.SuggestionsBuilder;
@@ -49,6 +50,7 @@ public class StopCommand extends LiteralNode {
public StopCommand() { public StopCommand() {
super("stop"); super("stop");
children(StopIndexArgument::new); children(StopIndexArgument::new);
children(StopHashArgument::new);
children(StopAll::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 static class StopAll extends LiteralNode {
private StopAll() { private StopAll() {
@@ -141,13 +184,13 @@ public class StopCommand extends LiteralNode {
if (canceled.isEmpty()) { if (canceled.isEmpty()) {
sender.sendMessage(join(spaces(), sender.sendMessage(join(spaces(),
asAdventure(bot.getDisplayName()).append(text("'s", GRAY)), asAdventure(bot.getDisplayName()).append(text("'s", GRAY)),
text("'s action list cleared", GRAY) text("action list cleared", GRAY)
)); ));
} else { } else {
sender.sendMessage(join(spaces(), sender.sendMessage(join(spaces(),
text("Tried to clear", GRAY), text("Tried to clear", GRAY),
asAdventure(bot.getDisplayName()).append(text("'s", 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) { for (AbstractBotAction<?> action : canceled) {
context.getSender().sendMessage( context.getSender().sendMessage(