Skip to content
On this page

API 方法表 ​

ChaUI 服务端 API 供其他 Bukkit 插件控制已经打开的页面、槽位和实体组件,也可以监听玩家在 ChaUI 页面中的交互。

公开类位于以下包中:

java
import com.github.ginirohikocha.chaui.api.ChaUIAPI;
import com.github.ginirohikocha.chaui.api.ChaUIEntityAPI;
import com.github.ginirohikocha.chaui.api.ChaUISlotAPI;
import com.github.ginirohikocha.chaui.api.GlobalScriptExecutionInfo;
import com.github.ginirohikocha.chaui.api.PageRegistrationResult;
import com.github.ginirohikocha.chaui.api.PersistentWorldPage;
import com.github.ginirohikocha.chaui.api.WorldMoveType;
import com.github.ginirohikocha.chaui.api.event.ChaUIClientReadyEvent;
import com.github.ginirohikocha.chaui.api.event.ChaUIPacketEvent;
import com.github.ginirohikocha.chaui.api.event.ChaUIReloadCompleteEvent;
import com.github.ginirohikocha.chaui.api.event.ChaUISlotClickEvent;
import java.util.List;

身份字段速查 ​

字段含义作用范围
pageId页面配置的 id标识页面种类。按 pageId 下发的状态会应用到该玩家当前打开的同 ID 页面;同一页面存在多个世界实例时会一起收到状态
sessionId页面每次打开时生成的会话 ID精确标识一次打开会话,关闭并重新打开后会变化
elementId / componentIdelements 中组件的 id标识页面中的一个组件;两个名称表达同一种组件身份
mode + binditem_slot.itemSlot.mode + bind标识玩家的一份槽位状态;不同页面使用相同组合时共享,同 bind 的不同模式彼此隔离
instanceId打开世界页面时指定的实例 ID在同一玩家当前打开的全部世界页面中全局唯一;关闭世界实例时不需要 pageId

不要混用

槽位 API 使用 mode + bind,不接收页面或 sessionId;普通页面状态修改通常使用 pageId + elementId,世界实例关闭只使用 instanceId。

ChaUIAPI ​

用于打开、关闭和修改页面运行时状态。状态修改只影响当前客户端页面会话,不会改写服务端的页面 yml。

方法参数返回值说明
setVariable(Player player, String pageId, String name, Object value)玩家、页面 ID、变量名、变量值void修改一个 vars 变量。变量名和变量值必须符合页面变量规则
getGlobalVariable(String name)已声明变量名Object读取服务器全局变量,未声明时拒绝;主线程调用
setGlobalVariable(String name, Object value)已声明变量名、同类型标量值void修改并自动同步到已就绪玩家;主线程调用
globalVariables()无只读 Map<String, Object>当前服务器全局变量快照;主线程调用
setVariables(Player player, String pageId, Map<String, Object> variables)玩家、页面 ID、变量映射void一次修改多个 vars 变量
setComponentState(Player player, String pageId, String elementId, String property, Object value)玩家、页面 ID、组件 ID、属性路径、值void修改一个允许动态更新的组件属性,例如 visible 或 text.value
setComponentStates(Player player, String pageId, String elementId, Map<String, Object> properties)玩家、页面 ID、组件 ID、属性映射void一次修改同一组件的多个属性
runScript(Player player, String pageId, String script)玩家、页面 ID、ChaUI Scriptvoid在玩家当前页面会话中执行客户端脚本
executeGlobalMethod(Player player, String methodId, Object... arguments)在线玩家或 null、通用方法 ID、顺序参数String启动一项独立服务端通用方法并返回根执行 ID;必须在 Bukkit 主线程调用
stopGlobalScript(String executionId)根执行 IDboolean停止仍活动的通用脚本及其子方法和计时任务;找到并停止时返回 true
activeGlobalScripts()无只读 List<GlobalScriptExecutionInfo>返回当前仍活动的通用脚本根执行快照,包含执行 ID、方法、来源和目标玩家信息
playSound(Player player, String pageId, String soundPath)玩家、页面 ID、ChaUI 素材目录下的 .ogg 相对路径void在玩家客户端播放本地 ChaUI 音效;非法路径会抛出 IllegalArgumentException
resetRememberedComponentPositions(Player player)玩家void重置该玩家在当前服务器下记住的全部组件位置;只接受在线且客户端已就绪的玩家
registerPage(Plugin sourcePlugin, String resourcePath)来源插件、Jar 内页面资源路径PageRegistrationResult不覆盖地注册并立即加载第三方页面
registerPage(Plugin sourcePlugin, YamlConfiguration configuration)来源插件、完整页面配置PageRegistrationResult直接从 Bukkit YamlConfiguration 注册并立即加载页面
elementType(String pageId, String elementId)页面 ID、组件 IDString 或 null返回当前已加载页面中指定组件的类型;参数为空、页面未加载或组件不存在时返回 null,不会暴露内部页面定义对象
open(Player player, String pageId)玩家、页面 IDvoid从 ChaUI 已加载的页面中查找并打开普通页面或 HUD。同一玩家的相同页面会先完整关闭旧会话再打开新会话
openSubPage(Player player, String pageId)玩家、子页面 IDvoid在玩家当前普通 GUI 或容器替换 GUI 上方打开一层 screen 子页面
replaceSubPage(Player player, String pageId)玩家、目标子页面 IDCompletableFuture<String>关闭根页面上方的旧子页面链,再把目标页面直接挂到根页面;完成后返回新子页面的 sessionId。快速连续调用时最后一次请求生效
closeSubPage(Player player)玩家void只关闭当前 GUI 栈最上层子页面
openWorld(Player player, String pageId, String instanceId)玩家、世界页面 ID、实例 IDvoid使用页面的默认位置或跟随绑定,以及 yaw/pitch 打开世界页面
openWorld(Player player, String pageId, String instanceId, double x, double y, double z, String yaw)玩家、世界页面 ID、实例 ID、世界坐标、朝向void兼容重载;使用调用方坐标与 yaw,并把 pitch 设为 0
openWorld(Player player, String pageId, String instanceId, double x, double y, double z, String yaw, double pitch)玩家、世界页面 ID、实例 ID、世界坐标、水平朝向、倾角void使用调用方给出的完整位置和朝向。yaw 可填写有限数字字符串或 follow_player;pitch 必须是有限数字
moveWorld(Player player, String instanceId, double x, double y, double z)玩家、世界实例 ID、目标坐标void立即把已经打开的世界页面实例移动到目标坐标,不关闭或重新打开页面
moveWorld(Player player, String instanceId, double x, double y, double z, long durationMillis, WorldMoveType type)玩家、世界实例 ID、目标坐标、毫秒时长、插值类型void在指定时间内移动现有实例;支持 LINEAR 与 EASE_IN_OUT
close(Player player, String pageId)玩家、页面 IDvoid关闭该玩家当前打开的普通页面或 HUD,并执行完整关闭生命周期
closeWorld(Player player, String instanceId)玩家、世界实例 IDvoid在该玩家的全部世界页面中查找并关闭指定实例,不需要页面 ID
addPersistentWorldPage(String worldName, String pageId, String instanceId)世界名、世界页面 ID、全服实例 IDPersistentWorldPage使用页面默认位置或跟随绑定、yaw 与 pitch 添加并持久化全服常驻实例
addPersistentWorldPage(String worldName, String pageId, String instanceId, double x, double y, double z, String yaw)世界名、页面 ID、实例 ID、坐标、朝向PersistentWorldPage兼容重载;使用显式位置与 yaw,并把 pitch 设为 0
addPersistentWorldPage(String worldName, String pageId, String instanceId, double x, double y, double z, String yaw, double pitch)世界名、页面 ID、实例 ID、坐标、水平朝向、倾角PersistentWorldPage使用显式位置和完整朝向添加并持久化全服常驻实例
removePersistentWorldPage(String instanceId)全服常驻实例 IDboolean删除持久记录并关闭在线玩家对应实例;不存在时返回 false
persistentWorldPages()无只读 List<PersistentWorldPage>按注册顺序返回常驻实例快照

全局变量与实体跟随 API ​

先在 global-scripts.yml 声明变量,页面再通过 vals.global.* 只读使用。完整配置见全局变量。

java
// Bukkit 主线程中执行,theme 已在 globals 中声明为字符串。
ChaUIAPI.setGlobalVariable("theme", "red");
Object theme = ChaUIAPI.getGlobalVariable("theme");
Map<String, Object> globals = ChaUIAPI.globalVariables();

跟随 API 同样在 Bukkit 主线程调用,页面必须是已加载的 world 页面:

方法返回值说明
openWorldFollowingPlayer(Player viewer, String pageId, String instanceId, double offsetX, double offsetY, double offsetZ)void只向 viewer 打开,跟随 viewer 自己
openWorldFollowingEntity(Player viewer, String pageId, String instanceId, Entity entity, double offsetX, double offsetY, double offsetZ)void只向 viewer 打开,Entity 必须与 viewer 同世界
openWorldFollowingEntity(Player viewer, String pageId, String instanceId, UUID entityUuid, double offsetX, double offsetY, double offsetZ)void允许目标暂时未加载,客户端看不到目标时隐藏
addPersistentWorldPageFollowingPlayer(String worldName, String pageId, String instanceId, double offsetX, double offsetY, double offsetZ)PersistentWorldPage常驻于目标世界,每位观看者分别跟随自己
addPersistentWorldPageFollowingEntity(String worldName, String pageId, String instanceId, UUID entityUuid, double offsetX, double offsetY, double offsetZ)PersistentWorldPage所有观看者看到同一个实体目标
addPersistentWorldPageFollowingEntity(String pageId, String instanceId, Entity entity, double offsetX, double offsetY, double offsetZ)PersistentWorldPage从 Entity 读取 UUID 和世界名,创建同一目标的常驻实例

例如在插件已有的主线程业务回调中显示血条:

java
import com.github.ginirohikocha.chaui.api.ChaUIAPI;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;

public final class HealthPageService {
    public static void show(Player viewer, Entity target) {
        // entity_health 的完整页面见世界界面教程。
        // 调用前等待 viewer 的 ChaUIClientReadyEvent。
        ChaUIAPI.openWorldFollowingEntity(
                viewer, "entity_health", "target_health", target.getUniqueId(), 0, 2.4, 0);
    }

    public static void pin(Entity target) {
        // 全服常驻 ID 不能重复;添加会保存记录并向就绪玩家打开。
        ChaUIAPI.addPersistentWorldPageFollowingEntity(
                "entity_health", "pinned_health", target, 0, 2.4, 0);
    }

    public static void hide(Player viewer) {
        ChaUIAPI.closeWorld(viewer, "target_health");
    }

    public static void unpin() {
        ChaUIAPI.removePersistentWorldPage("pinned_health");
    }
}

临时跟随 API 在客户端尚未就绪时抛出 IllegalStateException;页面、实体、UUID 或偏移非法时抛出 IllegalArgumentException。偏移按世界轴计算,单位为格,Y 从目标脚底开始。朝向和倾角使用页面默认值。moveWorld 对跟随实例修改偏移;对固定实例继续修改世界坐标。

短格式 openWorld / addPersistentWorldPage 读取页面的 follow 与默认偏移;显式坐标重载继续创建固定位置实例。PersistentWorldPage.getFollow() / getEntityUuid() 返回绑定信息,跟随记录的 getX/Y/Z() 返回偏移。完整页面见实体血条教程。

服务端通用脚本示例 ​

java
String executionId = ChaUIAPI.executeGlobalMethod(
        player, "notify_player", "欢迎回来");

List<GlobalScriptExecutionInfo> active = ChaUIAPI.activeGlobalScripts();
boolean stopped = ChaUIAPI.stopGlobalScript(executionId);

executeGlobalMethod 与 stopGlobalScript 只能在 Bukkit 主线程调用。传入玩家时必须在线;已安装并启用 PlaceholderAPI 时,字符串参数会在执行开始时为该玩家解析一次,占位结果不会在后续 timer / interval 中实时刷新。没有 PlaceholderAPI 时保留原始 %...% 文本。参数数量和 Java 类型必须与 global-scripts.yml 的声明严格一致,不会把字符串数字、字符串布尔值或其他对象隐式转换。

GlobalScriptExecutionInfo 是不可变记录,字段依次为 executionId、methodId、source、sourceId、playerId、playerName。source 可能是 api、command、page 或 cron;页面的 sourceId 是调用页面 ID,Cron 的 sourceId 是 Cron ID,控制台执行的玩家字段为 null。返回列表只是一刻的只读快照,已经完成或已停止的执行不会保留在其中。

同步完成且没有活动计时任务的方法也会返回执行 ID,但随后停止会得到 false。传入 null 只适合不依赖玩家、玩家 PAPI、玩家命令或页面打开动作的方法。完整教程见服务端通用脚本与 Cron。

registerPage 只能在 Bukkit 主线程调用,成功返回时页面已经可以立即打开,不需要再执行 reload。完整资源布置、三种返回结果和异常处理请阅读页面注册 API。

第三方插件需要校验已注册页面或服务器已有同 ID 页面中的组件类型时,应调用 elementType,不能直接访问内部 PageCache。公开世界页面定制回调允许修改实例变量和组件,不能修改页面 ID 或显示模式,且必须支持重载时在新副本上再次执行。

玩家原版名字显隐 API ​

这两个接口独立控制玩家头顶的原版名字,无需打开页面。target 是名字所属的玩家,viewer 是看这个名字的玩家;false 隐藏名字,true 恢复 Minecraft 原有显示规则,不强制显示原本被潜行或队伍规则隐藏的名字,也不改变 Tab 列表。

方法返回值说明
setPlayerNameVisible(Player target, boolean visible)void对所有观看者设置目标名字显隐;后续进入服务器的观看者也会收到当前状态
setPlayerNameVisible(Player target, Player viewer, boolean visible)void只设置指定观看者看到目标名字时的显隐状态

例如在已有插件的 Bukkit 主线程回调中调用:

java
import com.github.ginirohikocha.chaui.api.ChaUIAPI;
import org.bukkit.entity.Player;

public final class PlayerNames {
    public static void hideForEveryone(Player target) {
        ChaUIAPI.setPlayerNameVisible(target, false);
    }

    public static void showToViewer(Player target, Player viewer) {
        ChaUIAPI.setPlayerNameVisible(target, viewer, true);
    }

    public static void restoreForEveryone(Player target) {
        ChaUIAPI.setPlayerNameVisible(target, true);
    }
}

全体设置会清除该目标已有的特定观看者覆盖,之后可以再次调用指定观看者接口单独调整。目标与指定观看者必须在线;未就绪观看者会在完成 ChaUI 就绪后同步,重复设置相同有效状态不重发。离线参数抛出 IllegalArgumentException,异步调用抛出 IllegalStateException。

关闭页面和 /chaui reload 不会改变此独立状态;调用方结束隐藏功能时应显式传入 true。目标退出会清除该目标的全部状态,观看者退出会清除该观看者的单独设置;服务器重启后从默认显示规则开始。页面按钮也可以调用名称显隐脚本,服务端和观看者客户端均需使用支持此能力的配套包。

调用方只需要传页面 ID,不需要读取或构造 ChaUI 内部的页面定义对象。页面 ID 非法、页面不存在、打开方式与页面显示模式不匹配,或世界页面缺少 display.world 时,打开方法会抛出 IllegalArgumentException。客户端尚未发送 client_ready 时,打开操作会安全忽略;需要在进服时立即打开业务页面的插件应监听 ChaUIClientReadyEvent。

常驻世界页面 API 管理的是全服注册表,不接收 Player。添加与删除只能在 Bukkit 主线程调用;添加时目标世界必须已经加载,页面必须是有效世界页面,实例 ID 在整张常驻列表中必须唯一。磁盘保存失败不会提交内存状态,也不会提前向玩家打开或关闭实例。注册记录保存在 plugins/ChaUI/persistent-world-pages.yml,玩家进服就绪或进入匹配世界时自动打开,离开世界时自动关闭。

打开与关闭示例 ​

java
// 打开普通页面或 HUD
ChaUIAPI.open(player, "welcome");

// 使用页面 yml 中配置的世界坐标和朝向
ChaUIAPI.openWorld(player, "world_welcome", "主城欢迎牌");

// 临时覆盖世界坐标,并让竖直页面朝向跟随玩家
ChaUIAPI.openWorld(player, "world_welcome", "副本入口", 120.5, 65.0, -30.5, "follow_player");

// 把地图正面朝上横放;固定 yaw 可保持地图方向不随玩家旋转
ChaUIAPI.openWorld(player, "ground_map", "主城地图", 120.5, 64.01, -30.5, "0", 90.0D);

// 立即移动现有实例,不触发 close/open 生命周期
ChaUIAPI.moveWorld(player, "副本入口", 125.0, 66.0, -28.0);

// 在 800 毫秒内平滑移动;新移动会立即取代尚未完成的旧移动
ChaUIAPI.moveWorld(player, "副本入口", 130.0, 66.0, -25.0,
        800L, WorldMoveType.EASE_IN_OUT);

// 普通页面和 HUD 按页面 ID 关闭
ChaUIAPI.close(player, "welcome");

// 把当前内容子页面替换为商店页,并在真正打开后发送初始变量
ChaUIAPI.replaceSubPage(player, "barn_shop").thenAccept(sessionId ->
        ChaUIAPI.setVariables(player, "barn_shop", Map.of("quantity", 1))
);

// 重置该玩家在当前服务器下记住的全部组件位置
ChaUIAPI.resetRememberedComponentPositions(player);

// 世界页面按全局唯一的实例 ID 关闭,不填写页面 ID
ChaUIAPI.closeWorld(player, "主城欢迎牌");

replaceSubPage 适合顶部 Tab 和同级内容区。它会等待旧页面完成关闭与临时槽收尾,不需要调用方添加 tick 延迟。请不要用“先 closeSubPage、再立即 openSubPage”模拟替换。

instanceId 是玩家当前全部世界页面中的实例名称,可以使用中文或纯数字。重复使用同一个实例 ID 打开页面时,旧世界实例会先完整关闭,再由新实例替换。

moveWorld 对固定实例修改运行时世界坐标,对跟随实例修改相对目标脚底的世界轴偏移;不改写页面 yml,也不会重新初始化布局、素材、离屏纹理或页面生命周期。任意新移动请求都会终止旧移动,并从客户端当前实际显示位置开始;LINEAR 是匀速移动,EASE_IN_OUT 会在起点与终点处平滑减速。

resetRememberedComponentPositions 需要在 Bukkit 主线程调用。目标玩家必须在线并已经完成 ChaUI 客户端就绪握手;否则会明确拒绝,不会排队到下次登录。重置只影响当前服务器身份下该玩家的本地位置记忆,不会修改页面 yml,也不会影响其他玩家或其他服务器。

页面状态示例 ​

java
ChaUIAPI.setVariable(player, "welcome", "progress", 0.75);
ChaUIAPI.setComponentState(player, "welcome", "tip", "visible", true);
ChaUIAPI.setComponentState(player, "welcome", "dialog", "pointerEvents", "block");
ChaUIAPI.setComponentState(player, "welcome", "dialog", "scale", 1.05);
ChaUIAPI.setComponentState(player, "welcome", "dialog", "opacity", 0.9);
ChaUIAPI.runScript(player, "welcome", "vars.ready = true");

scale 与 opacity 也可以通过 runScript 绑定公式,例如 component("dialog").opacity = formula("vars.fade")。API 传入的属性和值仍会按页面字段规则校验;布局组件不能设置缩放或透明度,实体不能设置根级透明度。

世界页面状态

setVariable、setComponentState、runScript 等状态接口按 pageId 匹配页面。如果同一玩家打开了同一页面的多个世界实例,这些实例会一起应用该状态。

ChaUISlotAPI ​

槽位状态由服务端裁决。槽位身份是玩家 UUID 与 mode + bindId,不绑定页面或会话。操作 mapped 槽位会读写玩家的真实映射槽;操作 persistent 或 temporary 槽位会读写玩家级独立槽位系统。

方法参数返回值说明
getSlotItem(Player player, String mode, String bindId)玩家、模式、槽位绑定 IDItemStack 或 null读取一组可合法放入原版背包的物品;逻辑数量超过原版一组时只返回一组
getSlotPrototype(Player player, String mode, String bindId)玩家、模式、槽位绑定 IDItemStack 或 null读取数量固定为 1 的完整物品原型,适合序列化和持久化
getSlotAmount(Player player, String mode, String bindId)玩家、模式、槽位绑定 IDint读取槽位的完整逻辑数量,例如 999
getSlotMaxStack(Player player, String mode, String bindId)玩家、模式、槽位绑定 IDint读取当前实际数量上限;玩家没有打开任何 ChaUI 运行时页面时返回 0
setSlotItem(Player player, String mode, String bindId, ItemStack item)玩家、模式、槽位绑定 ID、物品void写入槽位并同步所有已打开的相同 mode + bindId;传入 null 可清空
setSlotItem(Player player, String mode, String bindId, ItemStack prototype, int amount)玩家、模式、槽位绑定 ID、物品原型、逻辑数量void使用物品原型和独立逻辑数量写入大容量槽;原型会按数量 1 保存
clearSlotItem(Player player, String mode, String bindId)玩家、模式、槽位绑定 IDvoid清空指定槽位并同步相关页面
refreshSlot(Player player, String mode, String bindId)玩家、模式、槽位绑定 IDvoid向所有已打开的相同 mode + bindId 组件刷新权威状态
refreshSlots(Player player, String mode)玩家、模式void刷新玩家当前打开页面中引用的该模式全部槽位
setItemDisplay(Player player, String pageId, String elementId, ItemStack item)玩家、页面 ID、item_display 组件 ID、展示物品void用一次原子状态补丁同步完整物品快照、物品 ID 和数量;空气、数量不大于 0 或 null 都按清空处理
clearItemDisplay(Player player, String pageId, String elementId)玩家、页面 ID、item_display 组件 IDvoid用一次原子状态补丁同时清空完整物品快照、物品 ID 和数量

修改读取结果

需要改变槽位物品时,请调用 setSlotItem。不要只修改 getSlotItem 返回的对象并期待页面自动同步。

大容量槽不能把 999 直接写入 Bukkit ItemStack.amount。Minecraft 只允许 ItemStack 使用当前版本认可的原版数量范围;超出后,物品序列化、保存或发包都可能失败。请把物品与数量分开处理:

java
ItemStack prototype = new ItemStack(Material.DIAMOND, 1);

// 页面中的 itemSlot.maxStack 必须允许 999。
ChaUISlotAPI.setSlotItem(player, "persistent", "roi_loot_000", prototype, 999);

// 持久化时分别保存原型和逻辑数量。
ItemStack savedPrototype = ChaUISlotAPI.getSlotPrototype(
        player, "persistent", "roi_loot_000");
int savedAmount = ChaUISlotAPI.getSlotAmount(
        player, "persistent", "roi_loot_000");

恢复数据时仍调用五参数 setSlotItem,不要执行 savedPrototype.setAmount(savedAmount)。getSlotItem 适合把一组物品交给原版背包;读取全部库存数量必须使用 getSlotAmount。

player 不能为空;mode 只允许 persistent、temporary、mapped;bindId 必须是安全绑定 ID,且 mapped 只能使用明确支持的 backpack_* 映射。persistent 的读取、写入、清空和刷新都要求 bindId 已在 slot.yml 注册,中文 ID 同样支持。非法参数或未注册持久槽会抛出 IllegalArgumentException。参数校验通过后,如果玩家没有打开任何 ChaUI 运行时页面,物品读取返回 null,数量与上限读取返回 0,写入、清空和刷新安全忽略。

只要玩家至少打开了一个运行时页面,persistent 与 temporary 就可以在当前页面尚无对应组件时提前写入。ChaUI 会从已加载页面目录中查找相同 mode + bindId 的 maxStack 契约,因此目标槽不必恰好出现在当前页面。之后任意页面出现相同槽位,会立即取得预存物品。预存临时槽位默认使用安全的 return_to_player 收尾策略。

API 调用不会执行组件的 itemSlot.check;该字段只是客户端点击发包前的便捷条件。setSlotItem 属于可信服务端写入,可以绕过注册项的 acceptWhen,但不能绕过 persistent ID 注册。插件业务仍需自行完成权限和物品安全判断。注册文件与接收条件见槽位注册表。

ChaUIEntityAPI ​

用于查询或修改 entity 组件。状态设置接口按 pageId 下发,因此同一页面的多个世界实例会同时更新。

方法参数返回值说明
entityUuid(Player player, String pageId, String elementId)玩家、页面 ID、实体组件 IDUUID 或 null查询普通页面或 HUD 当前会话中实体组件所显示来源实体的 UUID。页面未打开、组件不存在、组件类型不是 entity、来源实体无法解析或参数为空时返回 null;世界页面实例当前不能通过此方法查询
setDisplayName(Player player, String pageId, String elementId, String displayName)玩家、页面 ID、实体组件 ID、显示名void修改 entity.displayName
setSource(Player player, String pageId, String elementId, String source)玩家、页面 ID、实体组件 ID、来源void修改 entity.source,可用值为 self、uuid、entity_id、virtual
setEntityType(Player player, String pageId, String elementId, String entityType)玩家、页面 ID、实体组件 ID、实体类型 IDvoid修改虚拟实体的 entity.entityType,例如 minecraft:zombie
setYaw(Player player, String pageId, String elementId, Object yaw)玩家、页面 ID、实体组件 ID、有限数值void修改实体整体 yaw;无法转换为有限数值时抛出 IllegalArgumentException
setPitch(Player player, String pageId, String elementId, Object pitch)玩家、页面 ID、实体组件 ID、有限数值void修改实体整体 pitch;无法转换为有限数值时抛出 IllegalArgumentException
setRoll(Player player, String pageId, String elementId, Object roll)玩家、页面 ID、实体组件 ID、有限数值void修改实体整体 roll;无法转换为有限数值时抛出 IllegalArgumentException
setOrthographic(Player player, String pageId, String elementId, boolean orthographic)玩家、页面 ID、实体组件 ID、是否正交投影void修改 entity.orthographic;false 为默认 30 度透视投影,true 为正交投影
setTrackMouse(Player player, String pageId, String elementId, boolean trackMouse)玩家、页面 ID、实体组件 ID、是否追踪鼠标void修改 entity.trackMouse

entityUuid 应在页面会话建立后调用,其返回值由实体来源决定:

entity.sourceUUID 来源
self目标玩家的真实 UUID
uuidentity.uuid 指定实体的 UUID
entity_id目标玩家当前世界中对应网络实体 ID 的真实 UUID
virtual合法的 entity.virtualUuid;未配置或无效时按页面 ID、会话 ID 和组件 ID 生成确定性虚拟 UUID

ChaUIClientReadyEvent ​

客户端确认本地玩家、世界和 Play 发包通道均已可用后,会向服务端发送一次 client_ready。服务端验证协议版本并完成本次连接的幂等登记后,先触发 ChaUIClientReadyEvent,随后才统一同步页面定义、槽位会话、原版容器替换注册表、常驻 HUD 和当前世界匹配的常驻世界页面。

该事件不可取消,同一玩家同一次在线连接只触发一次;玩家退出并重新进入后会再次触发。

方法返回值说明
getPlayer()Player已完成 ChaUI 客户端就绪握手的玩家
getProtocolVersion()int客户端已通过验证的 ChaUI 协议版本

客户端就绪示例 ​

java
public final class ChaUIReadyListener implements Listener {
    @EventHandler
    public void onClientReady(ChaUIClientReadyEvent event) {
        ChaUIAPI.open(event.getPlayer(), "welcome");
    }
}

不要再用 PlayerJoinEvent + runTaskLater 猜测客户端加载时间。这个事件就是第三方插件在玩家进服后调用 ChaUI 打开、槽位和状态 API 的统一起点。

ChaUIReloadCompleteEvent ​

ChaUIReloadCompleteEvent 是同步 Bukkit 事件。ChaUI 完成一次成功的 ChaUI.reloadAll() 后触发一次;它没有玩家、页面或统计字段,只表示本次重载已经完成。事件不可取消。

页面候选、槽位或资源校验失败、编辑器租约阻塞等失败路径会直接返回失败结果,不会触发该事件。第三方插件可以用它在重载完成后重新计算自己的页面实例,不需要轮询猜测时机。

java
import com.github.ginirohikocha.chaui.api.event.ChaUIReloadCompleteEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;

public final class TagReloadListener implements Listener {
    @EventHandler
    public void onChaUIReloadComplete(ChaUIReloadCompleteEvent event) {
        // 重新更新依赖最新 ChaUI 页面定义的业务实例。
    }
}

ChaUISlotClickEvent ​

玩家点击 item_slot 后,ChaUI 会先校验会话和槽位版本、计算默认交换结果,再触发该事件。监听器可以取消操作,也可以修改最终的光标物品和槽位物品。

事件实现了 Bukkit Cancellable。

方法返回值或参数可修改说明
getPlayer()Player否触发操作的玩家
getSessionId()String否被点击页面的会话 ID
getPageId()String否页面 ID
getComponentId()String否被点击的槽位组件 ID
getBind()String否槽位绑定 ID
getAction()String否槽位动作名称,当前点击流程为 click
getSlotDefinition()ItemSlotDefinition否当前槽位配置
getCursorItemBefore()ItemStack 或 null否操作前的服务端光标物品,返回副本
getSlotItemBefore()ItemStack 或 null否操作前的槽位物品,返回副本
getCursorItemAfter()ItemStack 或 null通过 setter默认计算后的光标物品,返回副本
setCursorItemAfter(ItemStack item)void是覆盖操作完成后写回的光标物品
getSlotItemAfter()ItemStack 或 null通过 setter默认计算后的槽位物品,返回副本
setSlotItemAfter(ItemStack item)void是覆盖操作完成后写回的槽位物品
isCancelled()boolean通过 setter查询事件是否已取消
setCancelled(boolean cancelled)void是取消后不写回本次操作结果

槽位事件示例 ​

java
public final class RewardSlotListener implements Listener {
    @EventHandler
    public void onSlotClick(ChaUISlotClickEvent event) {
        if (!"reward_input".equals(event.getBind())) {
            return;
        }

        ItemStack result = event.getSlotItemAfter();
        if (result != null && result.getAmount() > 16) {
            event.setCancelled(true);
        }
    }
}

ChaUIPacketEvent ​

真实运行时脚本执行 packet(...) 或 发包(...) 后,服务端完成会话与页面校验,再触发该事件。该动作可以来自页面事件、组件全部事件、自定义方法、计时器回调或 API 下发脚本,不限于点击和悬浮。编辑器编辑状态与隔离预览不会发包。ChaUI 不会替业务插件处理 data 内容。

该事件不可取消,全部字段只读。

方法返回值说明
getPlayer()Player触发发包的玩家
getActionId()String本次已授权脚本动作的稳定 ID,可用于业务去重或追踪
getSessionId()String触发动作的页面会话 ID
getPageId()String页面 ID
getElementId()String触发脚本的组件 ID;页面级事件固定为空字符串
getTrigger()String原始组件事件或页面事件;方法和计时器继承创建它们的上下文,缺少明确事件来源时为 script
getPacketId()String页面脚本填写的业务包 ID
getData()只读 List<String>页面脚本传入的数据列表。复杂内容可由页面作者放入某一项 JSON 字符串,再由业务插件解析

自定义包事件示例 ​

java
public final class ChaUIPacketListener implements Listener {
    @EventHandler
    public void onPacket(ChaUIPacketEvent event) {
        if (!"treasure.claim".equals(event.getPacketId())) {
            return;
        }

        Player player = event.getPlayer();
        List<String> data = event.getData();
        // 在这里校验业务状态,再执行奖励逻辑。
    }
}

注册事件监听器 ​

监听器仍按标准 Bukkit 方式注册:

java
@Override
public void onEnable() {
    getServer().getPluginManager().registerEvents(new RewardSlotListener(), this);
    getServer().getPluginManager().registerEvents(new ChaUIPacketListener(), this);
    getServer().getPluginManager().registerEvents(new ChaUIReadyListener(), this);
}

服务端始终权威

不要根据客户端发来的字符串直接发放物品、执行命令或修改经济数据。业务插件仍需根据玩家、页面、会话和自身数据再次判断操作是否合法。