docs: 更新 README 添加潜影盒和末影箱功能说明

This commit is contained in:
Coldsmile_7
2026-04-15 01:20:05 +08:00
parent cc07647551
commit 2364ddee97
3 changed files with 22 additions and 93 deletions

View File

@@ -14,8 +14,14 @@
- 锻造台 (`/smithingtable`, `/st`) - 锻造台 (`/smithingtable`, `/st`)
- 切石机 (`/stonecutter`, `/sc`) - 切石机 (`/stonecutter`, `/sc`)
### 📦 容器管理
- **末影箱** (`/enderchest`, `/ec`) - 随时访问末影箱(参考 EssentialsX 实现)
- **潜影盒快捷打开** - 潜行+右键点击潜影盒直接打开内容(类似 CMI
- 支持自定义标题
- 防刷物品机制
- 防止套娃(不能在潜影盒中放入另一个潜影盒)
### 🔧 实用工具 ### 🔧 实用工具
- **末影箱** (`/enderchest`, `/ec`) - 随时访问末影箱
- **帽子** (`/hat`) - 将手中物品戴在头上 - **帽子** (`/hat`) - 将手中物品戴在头上
- **自杀** (`/suicide`, `/die`) - 快速自杀 - **自杀** (`/suicide`, `/die`) - 快速自杀
- **飞行** (`/fly`) - 切换飞行模式 - **飞行** (`/fly`) - 切换飞行模式
@@ -41,7 +47,6 @@
- 帮助菜单智能显示(只显示有权限的命令) - 帮助菜单智能显示(只显示有权限的命令)
- 默认仅 OP 可用,可通过权限插件授权 - 默认仅 OP 可用,可通过权限插件授权
- CMI 风格的命令别名支持 - CMI 风格的命令别名支持
- 潜行+右键潜影盒直接打开(无需放置)
## 📦 安装 ## 📦 安装
@@ -178,7 +183,9 @@ mvn clean package
- ✅ 智能权限过滤的帮助菜单 - ✅ 智能权限过滤的帮助菜单
- ✅ 完整的多语言支持 - ✅ 完整的多语言支持
- ✅ 功能方块权限菜单 - ✅ 功能方块权限菜单
- ✅ 潜行+右键潜影盒直接打开 - ✅ 潜行+右键潜影盒直接打开(防刷机制)
- ✅ 末影箱参考 EssentialsX 实现100% 安全)
- ✅ 潜影盒自定义标题配置
- ✅ 轻量级无依赖设计 - ✅ 轻量级无依赖设计
- ✅ 现代化的 Paper API 支持 - ✅ 现代化的 Paper API 支持
@@ -186,6 +193,11 @@ mvn clean package
### v1.2.0 ### v1.2.0
- ✨ 新增潜行+右键潜影盒直接打开功能(类似 CMI - ✨ 新增潜行+右键潜影盒直接打开功能(类似 CMI
- 支持自定义标题config.yml 配置)
- 防刷物品机制(快照验证 + 数量检查)
- 防止套娃(不能放入另一个潜影盒)
- 异常恢复(物品丢失自动掉落)
- ✨ 末影箱改用 EssentialsX 实现方式100% 安全)
- ✨ 功能方块菜单配置化(从 config.yml 读取) - ✨ 功能方块菜单配置化(从 config.yml 读取)
- ✨ 功能方块菜单添加音效反馈 - ✨ 功能方块菜单添加音效反馈
- ✨ CMI 风格命令别名系统 - ✨ CMI 风格命令别名系统

View File

@@ -1,8 +1,11 @@
package cn.infstar.essentialsC.commands; package cn.infstar.essentialsC.commands;
import cn.infstar.essentialsC.EssentialsC;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
/**
* 末影箱命令 - 参考 EssentialsX 实现
* 直接打开玩家的末影箱,确保数据安全
*/
public class EnderChestCommand extends BaseCommand { public class EnderChestCommand extends BaseCommand {
public EnderChestCommand() { public EnderChestCommand() {
@@ -11,18 +14,9 @@ public class EnderChestCommand extends BaseCommand {
@Override @Override
protected boolean execute(Player player, String[] args) { protected boolean execute(Player player, String[] args) {
EssentialsC plugin = EssentialsC.getInstance(); // 直接打开玩家的末影箱EssentialsX 方式)
// 优点100% 安全,不会吞物品或刷物品
// 如果启用了 ProtocolLib使用自定义标题 // 缺点:标题显示为 "Ender Chest"(由客户端语言决定)
if (plugin.isProtocolLibEnabled()) {
// 从配置读取标题
String title = plugin.getConfig().getString("enderchest.title", "&5随身末影箱");
// 标记下一个打开的 inventory 需要修改标题
plugin.getInventoryTitleListener().markForTitleChange(player, title);
}
// 直接打开玩家的末影箱(参考 EssentialsX 实现)
player.openInventory(player.getEnderChest()); player.openInventory(player.getEnderChest());
return true; return true;
} }

View File

@@ -1,77 +0,0 @@
package cn.infstar.essentialsC.listeners;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* 使用 ProtocolLib 修改 Inventory 标题
* 通过拦截 OPEN_WINDOW 数据包实现自定义标题
*/
public class InventoryTitleListener extends PacketAdapter {
// 存储需要修改标题的玩家和对应的新标题
private final Map<UUID, String> pendingTitleChanges = new HashMap<>();
public InventoryTitleListener(Plugin plugin) {
super(plugin, ListenerPriority.NORMAL, PacketType.Play.Server.OPEN_WINDOW);
}
/**
* 标记玩家需要修改下一个打开的 inventory 标题
* @param player 玩家
* @param title 新标题(支持颜色代码)
*/
public void markForTitleChange(Player player, String title) {
pendingTitleChanges.put(player.getUniqueId(), title);
}
@Override
public void onPacketSending(PacketEvent event) {
Player player = event.getPlayer();
UUID playerId = player.getUniqueId();
// 检查该玩家是否有待处理的标题修改
String newTitle = pendingTitleChanges.remove(playerId);
if (newTitle == null) {
return;
}
try {
PacketContainer packet = event.getPacket();
// Paper 1.21+ 使用 WrappedChatComponent 作为标题
// 将颜色代码 & 转换为 §
String formattedTitle = newTitle.replace('&', '§');
// 创建聊天组件
WrappedChatComponent titleComponent = WrappedChatComponent.fromText(formattedTitle);
// 修改数据包中的标题字段
// 在 1.21+ 中标题是第二个字段索引1
packet.getChatComponents().write(0, titleComponent);
} catch (Exception e) {
// 如果修改失败,记录错误但不影响正常流程
Bukkit.getLogger().warning("[EssentialsC] 修改 inventory 标题失败: " + e.getMessage());
}
}
/**
* 清理所有待处理的标题修改(防止内存泄漏)
*/
public void cleanup() {
pendingTitleChanges.clear();
}
}