LinView 使用资源文件描述视图的尺寸、标题、布局、静态控件和动态区模板。业务数据与点击逻辑不应写入资源文件,而应通过 Source 与 Hook 接入。
标题、图标名称和整段 lore 也可以引用语言文件,随当前插件语言切换和重载更新。写法与刷新规则见文件服务的在文件中引用 ⇱。
视图文件位于当前插件数据目录的 gui 子目录:
plugins/<YourPlugin>/gui/<viewId>.yml
加载顺序为 .yml、.yaml、.json。例如:
lin.linView().open(player, "demo.shop");
会依次查找 gui/demo.shop.yml、gui/demo.shop.yaml 和 gui/demo.shop.json。通常建议使用 YAML,以便保持布局的可读性。
文件中的 id 应与打开时使用的 viewId 一致。实际文件查找以传给 open(...) 的 viewId 为准。
当前可用的顶层字段如下:
id:视图 ID。type:视图类型,当前填写 inventory。allowManualClose:是否允许玩家手动关闭。rows:Inventory 行数,建议为 1 至 6。title:界面标题。layout:每行 9 个字符的布局矩阵。legend:静态字符定义。dynamicAreas:动态区定义列表。建议显式填写顶层字段以便阅读。省略 allowManualClose 时默认为 true。
id: "demo.shop"
type: "inventory"
allowManualClose: true
rows: 3
title: "§fShop - {state.category}"
layout:
- "#########"
- "#LLLLLLL#"
- "#P.....N#"
legend: {}
dynamicAreas: []
allowManualClose: false 时,玩家尝试关闭 Inventory 后,Bukkit 运行时会在下一 Tick 重新打开该界面。代码可以在打开时通过状态覆盖此值:
lin.linView().open(player, "demo.shop", state -> {
state.put("_allowManualClose", true);
});
layout 按从上到下、从左到右的顺序映射 Inventory 槽位。每行应当包含 9 个字符,行数应与 rows 一致。
推荐约定:
.:空槽位。#:边框或装饰。编译器会对过短、过长或行数不符的布局进行补齐与截断,但不应依赖这种容错行为。明确写出正确矩阵更容易检查槽位位置。
legend 将布局字符映射为静态图标或按钮:
legend:
"#":
kind: "static"
icon:
kind: "vanilla"
key: "BLACK_STAINED_GLASS_PANE"
amount: 1
name: " "
lore: []
"N":
kind: "static"
uid: "next"
icon:
kind: "vanilla"
key: "ARROW"
name: "§a下一页"
lore: []
action:
type: "hook"
args:
hookId: "shop.next"
refresh: ""
静态字段:
kind:当前使用 static。uid:代码操作控件时使用的稳定 ID,可省略。icon:图标定义。action:点击动作,可省略。带有 uid 的 legend 字符在布局中只能出现一次。装饰字符可以重复出现,但不应为它声明 UID。
图标字段包括:
kind:vanilla、minecraft 或 itemsadder。key:Bukkit Material 名称或 ItemsAdder ID。amount:物品数量。name:显示名称。lore:Lore 字符串列表。meta:附加数据。其字符串值支持占位符;ItemsAdder 图标可使用 fallback 或 fallbackMaterial 声明原版回退材质。原版图标示例:
icon:
kind: "vanilla"
key: "DIAMOND"
amount: 1
name: "§b钻石"
lore:
- "§7价格:§e{row.price}"
ItemsAdder 图标使用 kind: itemsadder,key 填写 namespace:id。服务器未安装 ItemsAdder 或 ID 无效时,可以回退到原版材质:
icon:
kind: "itemsadder"
key: "myplugin:coin"
name: "§e金币"
meta:
fallback: "GOLD_NUGGET"
动作字段包括:
type:动作类型。args:传给动作或 Hook 的参数。refresh:动作完成后的刷新策略。当前 Bukkit 运行时可执行:
hook:读取 args.hookId 并调用注册的 Hook。open:读取 args.viewId 打开目标视图,并将 args.state 写入新会话;当前视图会进入导航栈。back:返回导航栈中的上一视图,并恢复离开时的状态。close:关闭当前会话。state:把 args.state 中的值写入当前状态;也可以直接把 args 作为状态补丁。值为 null 时删除该状态。command:读取 args.command,以当前玩家身份执行命令。当前刷新策略可用:
view 或 session:重新加载所有 Source 并渲染整个视图。area:<id>:重新加载指定动态区的 Source,并更新该区域槽位。widget:<uid>:只更新指定静态控件的槽位。open、back 与 close 会结束当前动作流程,不再执行该动作上的额外刷新策略。
动态区通过 dynamicAreas 声明:
dynamicAreas:
- id: "items"
areaChar: "L"
overflow: "truncate"
emptyFill:
icon:
kind: "vanilla"
key: "GRAY_STAINED_GLASS_PANE"
name: " "
lore: []
source:
id: "shop.items"
args: {}
template:
icon:
kind: "vanilla"
key: "{row.material}"
name: "§f{row.name}"
lore:
- "§7价格:§e{row.price}"
action:
type: "hook"
args:
hookId: "shop.buy"
itemId: "{row.id}"
refresh: ""
variants: []
source.args 会在加载 Source 前解析占位符,并通过 GuiContext.args() 传入。需要由按钮持续修改的分页和筛选值仍适合保存在 GuiState 中,再由 source.args 引用:
source:
id: "shop.items"
args:
category: "{state.category}"
lin.linView().reload();
lin.linView().reload("demo.shop");
reload() 会先加载和编译新定义,准备成功后再更新缓存及活动会话。reload(viewId) 只处理指定视图。重新打开时保留原会话状态,但重新执行 Source 并重新建立静态区与动态区绑定。
讨论