Click Callbacks - HimxhMC/HimDSL GitHub Wiki

点击回调 / Click Callbacks

四种点击 / Four Click Types

HimDSL 的槽位支持四种点击回调:

A slot supports four click callbacks:

字段 触发时机
onLeftClick 普通左键点击
onRightClick 普通右键点击
onShiftLeftClick Shift + 左键
onShiftRightClick Shift + 右键
Field Trigger
onLeftClick plain left-click
onRightClick plain right-click
onShiftLeftClick shift + left-click
onShiftRightClick shift + right-click

只注册你要用的。没注册的点击不做任何事——注意这是"不做任何事",不是"触发其他回调"。

Register only what you need. Unregistered clicks do nothing — note: they do nothing, they do not fall through to another callback.


用 lambda 注册 / Registering with a Lambda

回调是一个 lambda,点击时执行:

A callback is a lambda executed on click:

var slot = new GUISlot(Material.DIAMOND);
slot.name = "§a点击领取";

slot.onLeftClick = () -> {
    player p = @s;
    runcmd(p.name, "give " + p.name + " diamond 1");
    runcmd(p.name, "msg §a你获得了钻石!");
    gui.close(p);
};

回调 lambda 没有参数,用 @s 拿点击的玩家。

The lambda takes no arguments; use @s to get the clicking player.


@s 的作用 / What @s Refers To

在 GUI 点击回调里,@s 是点击者。这是 GUI 交互的核心——每个玩家打开同一个 GUI,点击时回调里的 @s 指向那个点击的玩家。

Inside a GUI click callback, @s is the clicker. This is central to GUI interaction: multiple players may have the same GUI open, and @s inside the callback refers to the one who clicked.

slot.onLeftClick = () -> {
    player p = @s;              // 点击的玩家
    runcmd(p.name, "msg 你点了我");
};

不需要在 GUI 上"记住当前玩家"——@s 自动处理。

No need to track "the current player" on the GUI — @s handles it.


多个回调 / Multiple Callbacks

同一个槽位可以注册多种点击:

One slot may register several click types:

slot.onLeftClick = () -> {
    player p = @s;
    runcmd(p.name, "msg 左键:购买");
};

slot.onRightClick = () -> {
    player p = @s;
    runcmd(p.name, "msg 右键:查看详情");
};

两种点击各自触发各自的逻辑,互不干扰。

Each click type fires its own logic independently.


GUI 与槽位的访问 / Accessing GUI and Slot

回调里可以访问闭包捕获的变量,包括 gui 和 slot 本身:

Callbacks capture enclosing variables by closure, including gui and slot:

var gui = new GUI(1, "菜单");

slot.onLeftClick = () -> {
    player p = @s;
    gui.close(p);               // 关闭这个 GUI
};

这让你可以在回调里调用 gui.close、修改槽位显示、重新刷新界面。

This lets callbacks call gui.close, change slot display, or refresh the interface.


常见交互模式 / Common Interaction Patterns

关闭界面 / Closing on Click

slot.onLeftClick = () -> {
    player p = @s;
    runcmd(p.name, "msg 操作完成");
    gui.close(p);
};

扣费与发奖 / Charging and Rewarding

slot.onLeftClick = () -> {
    player p = @s;
    // 假设有经济系统
    runcmd("CONSOLE", "eco take " + p.name + " 100");
    runcmd("CONSOLE", "give " + p.name + " diamond 1");
    runcmd(p.name, "msg §a购买成功");
};

切换菜单 / Switching Menus

slot.onLeftClick = () -> {
    player p = @s;
    gui.close(p);

    // 假设 menu2 是另一个已定义的 GUI
    // menu2.open(p);
};

修改自己 / Modifying the Slot

int count;

slot.onLeftClick = () -> {
    count += 1;
    slot.lore = {
        "§7已点击: §e" + count,
        "§7再点一次试试"
    };
    gui.update(0, 4);
};

修改槽位后必须 update,否则玩家看不到变化。

Must update after modifying a slot, or the player sees no change.


一个完整的商店例子 / A Complete Shop Example

int main() {
    var gui = new GUI(3, "§6简易商店");

    // 商品槽位
    var diamondSlot = new GUISlot(Material.DIAMOND);
    diamondSlot.name = "§b钻石";
    diamondSlot.lore = {
        "§7价格: §e100 金币",
        "§a左键购买"
    };

    diamondSlot.onLeftClick = () -> {
        player p = @s;
        runcmd("CONSOLE", "eco take " + p.name + " 100");
        runcmd("CONSOLE", "give " + p.name + " diamond 1");
        runcmd(p.name, "msg §a购买成功");
        gui.close(p);
    };

    gui[1][4] = diamondSlot;

    // 关闭按钮
    var closeSlot = new GUISlot(Material.BARRIER);
    closeSlot.name = "§c关闭";
    closeSlot.onLeftClick = () -> {
        gui.close(@s);
    };

    gui[2][8] = closeSlot;

    // 装饰玻璃
    var glass = new GUISlot(Material.GRAY_STAINED_GLASS_PANE);
    glass.name = " ";
    for (int r = 0; r < 3; r = r + 1) {
        for (int c = 0; c < 9; c = c + 1) {
            if (gui[r][c].icon == Material.AIR) {
                // 只在空格上铺玻璃,避免覆盖已有物品
                gui[r][c] = glass;
            }
        }
    }

    gui.updateAll();
    gui.open(@p);
}

注意最后一段:先放商品,后铺装饰,装饰时检查 gui[r][c].icon == Material.AIR 才写入,避免覆盖。顺序很重要。

Note the ordering: place products first, then decorate, checking gui[r][c].icon == Material.AIR before writing. Order matters.


点击类型回退 / Fallback Behavior

HimDSL 不会把 Shift 点击回退到普通点击。如果你只注册了 onLeftClick:

HimDSL does not fall back from shift-clicks to plain clicks. With only onLeftClick registered:

  • 左键 → 触发 onLeftClick。

  • Shift + 左键 → 什么也不做。

  • Left-click → fires onLeftClick.

  • Shift + left-click → does nothing.

想要 Shift + 左键也响应,就显式注册:

To respond to shift-left too, register it explicitly:

slot.onLeftClick = () -> { /* 普通左键 */ };
slot.onShiftLeftClick = () -> { /* Shift 左键 */ };

或者,如果两种点击效果相同,在 lambda 里写一份逻辑,两处引用它(通过具名函数)。

Or, if both clicks do the same, put the logic in a named function and reference it from both.


常见错误 / Common Mistakes

错误一:回调里用 @p

slot.onLeftClick = () -> {
    player p = @p;              // ✗ 可能拿到别人
};

@p 是最近的玩家,不一定是点击者。用 @s。

错误二:修改槽位忘记刷新

slot.onLeftClick = () -> {
    slot.name = "已购买";
    // ✗ 忘记 gui.update(...)
};

错误三:在回调里 sleep

slot.onLeftClick = () -> {
    runcmd(p.name, "msg 处理中...");
    sleep(2000);                // ✗ 卡主线程
    runcmd(p.name, "msg 完成");
};

sleep 会阻塞服务器。用 @BukkitRunnable 拆分执行。

Mistake 1: @p inside a callback

@p is the nearest player, not necessarily the clicker. Use @s.

Mistake 2: Forgetting to refresh

After modifying the slot, call gui.update(...).

Mistake 3: sleep inside a callback

Blocks the server. Use @BukkitRunnable instead.


小结 / Summary

  • 四种回调:onLeftClick、onRightClick、onShiftLeftClick、onShiftRightClick。

  • 用无参 lambda 赋值。

  • @s 在回调里是点击者。

  • 未注册的点击什么也不做,不会回退。

  • 修改槽位后要 update。

  • 不要在回调里 sleep。

  • Four callbacks: onLeftClick, onRightClick, onShiftLeftClick, onShiftRightClick.

  • Assign a no-arg lambda.

  • @s inside the callback is the clicker.

  • Unregistered clicks do nothing; no fallback.

  • update after modifying the slot.

  • No sleep inside callbacks.


下一步 / Next Steps

下一章 [onClose 回调 / onClose] 讲界面被玩家关闭时如何响应。

Next, [onClose 回调 / onClose] covers reacting when the player closes the GUI.