Skip to content

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。

职责

  • 监听 InventoryClickEventInventoryCloseEventInventoryDragEventInventoryOpenEvent
  • 通过 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)

添加新的事件类型

  1. event/ 下创建新的事件类
  2. handler/ 下创建对应的处理器接口
  3. GUIListener 中添加事件监听
  4. InventoryGUI 中添加处理器存储和分发

自定义权限预设

SlotPermission 类中添加新的静态常量:

java
public static final SlotPermission CUSTOM = new SlotPermission(true, false, true, false);