EnderRealmFastGUI 架构说明
项目结构
EnderRealmFastGUI/
├── src/main/java/cn/enderrealm/fastgui/
│ ├── EnderRealmFastGUI.java # 主入口
│ │
│ └── inventory/ # 容器 UI 模块
│ ├── core/ # 核心类
│ │ ├── InventoryGUI.java # GUI 实例
│ │ └── InventoryGUIBuilder.java # Builder
│ │
│ ├── config/ # 配置
│ │ ├── ContainerType.java # 容器类型枚举
│ │ ├── ContainerConfig.java # 容器配置
│ │ ├── ContainerFactory.java # 工厂
│ │ └── SlotPermission.java # 权限类
│ │
│ ├── event/ # 事件定义
│ │ ├── base/
│ │ │ └── InventoryGUIEvent.java # 事件基类
│ │ ├── click/
│ │ │ ├── ClickType.java # 点击类型
│ │ │ ├── ClickTypeConverter.java # 转换器
│ │ │ └── InventoryClickEvent.java
│ │ ├── close/
│ │ │ └── InventoryCloseEvent.java
│ │ ├── drag/
│ │ │ └── InventoryDragEvent.java
│ │ └── open/
│ │ └── InventoryOpenEvent.java
│ │
│ ├── handler/ # 处理器接口
│ │ ├── ClickHandler.java
│ │ ├── CloseHandler.java
│ │ ├── DragHandler.java
│ │ └── OpenHandler.java
│ │
│ ├── holder/
│ │ └── GUIHolder.java # InventoryHolder
│ │
│ └── listener/
│ └── GUIListener.java # 事件监听器核心组件
1. EnderRealmFastGUI
主入口类,负责初始化和注册监听器。
java
public class EnderRealmFastGUI {
private static JavaPlugin plugin;
private static boolean initialized = false;
public static void init(JavaPlugin plugin) {
// 注册 GUIListener
plugin.getServer().getPluginManager().registerEvents(new GUIListener(), plugin);
initialized = true;
}
}2. InventoryGUI
核心类,管理容器的创建、配置和事件处理。
职责:
- 创建 Bukkit Inventory
- 存储 slot 配置(物品、处理器、权限)
- 处理事件分发
关键字段:
java
public class InventoryGUI {
private final ContainerConfig config;
private final GUIHolder holder;
private final Inventory inventory;
private final Map<Integer, ClickHandler> slotClickHandlers;
private final Map<Integer, ItemStack> slotItems;
private final Map<Integer, SlotPermission> slotPermissions;
private SlotPermission containerDefaultPermission;
private SlotPermission playerInventoryDefaultPermission;
// ...
}3. InventoryGUIBuilder
Builder 模式构建器,提供链式调用 API。
职责:
- 收集配置参数
- 验证参数合法性
- 创建 InventoryGUI 实例
关键方法:
java
public class InventoryGUIBuilder {
public InventoryGUIBuilder type(ContainerType type);
public InventoryGUIBuilder title(String title);
public InventoryGUIBuilder rows(int rows);
public InventoryGUIBuilder containerPermission(SlotPermission permission);
public InventoryGUIBuilder playerInventoryPermission(SlotPermission permission);
public InventoryGUIBuilder slot(int slot, ItemStack item, ClickHandler handler, SlotPermission permission);
public InventoryGUIBuilder fill(ItemStack item, SlotPermission permission);
public InventoryGUI build();
}4. SlotPermission
权限类,定义 slot 的操作权限。
四个维度:
clickable- 是否可点击触发回调takeable- 是否可拿走placeable- 是否可放入movable- 是否可移动
预设权限:
java
public class SlotPermission {
public static final SlotPermission READ_ONLY = new SlotPermission(false, false, false, false);
public static final SlotPermission INTERACT_ONLY = new SlotPermission(true, false, false, false);
public static final SlotPermission TAKE_ONLY = new SlotPermission(false, true, false, false);
public static final SlotPermission PLACE_ONLY = new SlotPermission(false, false, true, false);
public static final SlotPermission FULL_ACCESS = new SlotPermission(true, true, true, true);
}5. GUIHolder
实现 InventoryHolder 接口,用于标识 GUI 容器。
作用:
- 与 Bukkit Inventory 绑定
- 在事件中识别是否为 GUI 容器
- 持有 InventoryGUI 引用
java
public class GUIHolder implements InventoryHolder {
private final InventoryGUI gui;
private Inventory inventory;
public InventoryGUI getGui() {
return gui;
}
@Override
public Inventory getInventory() {
return inventory;
}
}6. GUIListener
事件监听器,监听所有容器事件并分发到对应 GUI。
职责:
- 监听
InventoryClickEvent、InventoryCloseEvent、InventoryDragEvent、InventoryOpenEvent - 通过
GUIHolder识别 GUI 容器 - 根据权限系统决定是否允许操作
- 分发事件到对应 GUI 的处理器
权限检查流程:
java
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
// 1. 获取 GUI
InventoryGUI gui = getGUI(event.getInventory());
if (gui == null) return;
// 2. 获取 slot 权限
SlotPermission permission = gui.getSlotPermission(slot);
// 3. 检查权限
if (isMoveAction(action) && !permission.isTakeable()) {
event.setCancelled(true);
return;
}
// 4. 触发回调
gui.handleClick(guiEvent);
}事件流程
点击事件
玩家点击 slot
↓
Bukkit InventoryClickEvent
↓
GUIListener.onInventoryClick()
↓
通过 GUIHolder 获取 InventoryGUI
↓
获取 slot 权限(单独设置 > 区域默认)
↓
检查权限(clickable、takeable、placeable)
↓
创建 InventoryClickEvent
↓
InventoryGUI.handleClick()
↓
调用 slot 特定处理器
↓
调用全局处理器拖拽事件
玩家拖拽物品
↓
Bukkit InventoryDragEvent
↓
GUIListener.onInventoryDrag()
↓
通过 GUIHolder 获取 InventoryGUI
↓
检查所有涉及 slot 的 movable 权限
↓
创建 InventoryDragEvent
↓
InventoryGUI.handleDrag()
↓
调用拖拽处理器设计决策
为什么使用 InventoryHolder 而不是 ID?
使用 InventoryHolder 类型检查(instanceof)而非 ID 映射:
优点:
- 类型安全,不会混淆
- 每个 Inventory 直接持有 GUI 引用,无需 Map 查找
- 无需管理 ID 生命周期
缺点:
- 没有"共享 GUI"概念(多人同时打开同一商店需要多个实例)
为什么默认取消事件?
Bukkit 的容器事件默认是允许的,如果忘记取消会导致物品丢失。EnderRealmFastGUI 默认取消所有事件,只有明确允许的操作才会放行。
为什么区分容器区域和背包区域?
原始 Bukkit 事件中,rawSlot 区分容器区域(0 到 size-1)和背包区域(size 到 size+35)。EnderRealmFastGUI 为这两个区域提供独立的默认权限,方便控制。
扩展点
添加新的容器类型
在 ContainerType 枚举中添加:
java
NEW_TYPE(InventoryType.NEW_TYPE, size, resizable)添加新的事件类型
- 在
event/下创建新的事件类 - 在
handler/下创建对应的处理器接口 - 在
GUIListener中添加事件监听 - 在
InventoryGUI中添加处理器存储和分发
自定义权限预设
在 SlotPermission 类中添加新的静态常量:
java
public static final SlotPermission CUSTOM = new SlotPermission(true, false, true, false);