GUI
概述
GUI 将单个 TypeWriter 条目 Open GUI 变成一个完整的物品栏菜单。你选择容器类型(箱子、铁砧、商人窗口、成书等),用 MiniMessage 写好标题,然后把内容描述为一棵存放在该条目 layoutPool 中的布局树。每个布局会放置若干物品:材质、名称、lore,以及玩家点击它时会发生什么。
一切都在网页面板中编辑:无需手写任何菜单文件。菜单的打开方式与其他动作一样,可以来自任务步骤、NPC 对话、命令事件或另一个菜单。点击可以运行控制台命令、触发其他条目并修改 fact,因此菜单只是驱动你的 TypeWriter 页面的又一种方式。
GUI 也是其他 BTC 菜单扩展的基础。Shops 和 Quest Codex 并不自带窗口:它们在 open_gui 布局中放置标记物品,再由扩展用商品或任务来填充这些标记。
功能
- 十种布局 —
simple、flex、paginated、scrollable、frame、composite、book、merchant、storage和leaderboard。 - 24 种容器类型 — 9 到 54 格的箱子尺寸,以及铁砧、附魔台、锻造台、切石机、砂轮、织布机、制图台、商人、书、漏斗、各类熔炉等。
- 处处支持 MiniMessage — 标题、物品名称和 lore 中可使用颜色、渐变和 PlaceholderAPI 占位符。
- 按点击类型设置点击动作 — 左键、右键、Shift、中键、双击、数字键、丢弃和副手交换,每种都有各自的命令和触发器。
- 槽位权限 — 用
viewPermission隐藏槽位,或用clickPermission让槽位可见但无法点击。 - 槽位重复 — 用
count、direction、gap和repeatY填满一行或一块区域,无需复制物品。 - 视图与标签页 — 一个菜单外壳、多个界面,带有面包屑标题和会高亮的标签页。
- 持久存储 — 按玩家或按组保存物品的槽位,支持配额、占位符以及可选的关闭时掉落。
- 排行榜 — 根据一个或多个 fact 对玩家、世界或组进行排名(包括离线玩家),也可以通过
source字段显示其他扩展提供的行(例如 Dungeon 的统计)。 - 扩展物品栏 — 把玩家自己的物品栏变成额外的四行菜单,形成十行的画布。
安装
- 在 Paper 或 Folia 服务器上安装 TypeWriter。必须存在 TypeWriter 本来就需要的 PacketEvents。
- 从 GUI 的 GitHub releases 下载 JAR(即上方摘要面板中的按钮)。
- 将其放入
plugins/Typewriter/extensions/,然后重启服务器。 - 可选:如果你的标题使用
<image>或<shift>标签来绘制自定义背景,请安装 CraftEngine。
请在依赖 GUI 的扩展(如 Shops 和 Quest Codex)之前先安装 GUI。
公开发布的 GUI 版本不包含独立的菜单编辑器及其网页服务器:菜单在 TypeWriter 网页面板中编辑。该版本只附带一项启动检查,它会读取你已发布的 open_gui 条目,并在控制台中报告有问题的菜单(参见“故障排查”)。
快速上手
构建一个包含三个按钮的小菜单:
- 在网页面板中打开一个 sequence 页面,添加一个 Open GUI 条目,并命名为
main_menu。 - 将
guiType设为CUSTOM,size设为SIZE_27,title设为<gold>Main menu。 - 在
layoutPool中添加一个类型为simple的布局,给它设置id为main,然后在mainLayoutId中输入main。 - 在该布局的
items中,在x为 4、y为 1 的位置添加一个物品。在item中选择DIAMOND,将displayName设为<aqua>Rewards,并添加一行lore:<gray>Click to claim。 - 在
interactionList中添加一个type为LEFT_CLICK的交互。在commands中添加一条控制台命令,例如give %player% diamond 1,或在triggers中链接一个条目。开启closeMenu可在点击后关闭菜单。 - 再添加第二个
simple物品,设置count为 9、direction为right,并选用灰色染色玻璃板,绘制出一行边框。将其displayName设为一个空格。 - 将该 Open GUI 条目链接到能触发它的东西,例如某个对话选项的触发器或某个命令事件,然后 Publish(发布)。
在添加 frame、scrollable 或 paginated 图层之前,先让单个 simple 布局在游戏中正常工作。每个布局都通过其 id 来寻址,所以当菜单打开后是空的时,首先要检查的就是 mainLayoutId 是否输入错误。
工作原理
动作触发时,扩展会为该玩家构建布局树(此时才会评估条件和权限),注册一个菜单会话并显示物品栏。每次点击都会根据该位置上的槽位来解析,然后运行对应的交互。会话会记住之前的菜单,因此 gui:back 会返回玩家来时的菜单。
菜单
容器类型
guiType 用于选择窗口。CUSTOM 是一个普通的类箱子物品栏,其高度通过 size 选择。size 只对 CUSTOM 生效,默认是六行;其他类型使用其窗口的原版尺寸。
guiType | 窗口 |
|---|---|
CUSTOM, CHEST, ENDER_CHEST, BARREL, SHULKER_BOX | 箱子式网格。对于 CUSTOM,size 可为 SIZE_9、SIZE_18、SIZE_27、SIZE_36、SIZE_45 或 SIZE_54。 |
HOPPER, DISPENSER, DROPPER | 小型固定尺寸容器。 |
FURNACE, BLAST_FURNACE, SMOKER, BREWING_STAND, BEACON | 机器窗口。 |
CRAFTING_TABLE, ENCHANTING_TABLE, ANVIL, SMITHING_TABLE, GRINDSTONE, LOOM, CARTOGRAPHY_TABLE, STONECUTTER | 原版工作站。没有布局时,它们的行为与原版界面相同;GUI 只负责打开和关闭。 |
MERCHANT, VILLAGER_TRADE | 村民交易窗口,由 merchant 布局填充。 |
BOOK | 一本成书,由 book 布局填充。 |
布局
每个布局都是 layoutPool 中一个具名的条目。mainLayoutId 中指定的那个是根布局;其他布局则通过引用来到达(composite 的子项、scrollable 的 innerId、frame 的 layoutId)。
| 布局 | 适用场景 | 关键字段 |
|---|---|---|
simple | 位于 x/y 位置的固定按钮和装饰。 | items |
flex | 自动按行排列的物品。 | justifyContent (START, CENTER, END, SPACE_BETWEEN), alignItems, wrap, virtualHeight |
paginated | 分成多页的长列表。 | itemsPerPage, slots, items, navigationButtons (NEXT, PREVIOUS, BACK, INDICATOR) |
scrollable | 比窗口更高或更宽的内容。 | innerId, virtualWidth, virtualHeight, buttons (direction, step), showDefaultButtons, defaultStep |
frame | 多个相互独立的矩形,例如标签页、内容和页脚。 | frames (id, x, y, width, height, layoutId) |
composite | 叠放图层:背景、内容、按钮。 | children(布局 id,后面的子项绘制在上层) |
book | 成书的各页。 | pages (MiniMessage) |
merchant | 使用自定义物品的村民交易。 | trades (result, costOne, costTwo, maxUses, criteria) |
storage | 由 GUI Storage artifact 支撑的持久槽位。 | entry, groupKey, slots |
leaderboard | 带分页的排名行,来自 fact 或其他扩展的 source。 | leaderboard, x, y, width, height, previousButton, nextButton |
book 或 merchant 布局只能作为主布局使用,并且 guiType 会被覆盖为对应的窗口。只要 showDefaultButtons 处于开启状态(默认),scrollable 布局就会获得默认箭头:每个在 buttons 中没有条目的方向各有一个,参见 布局、框架与槽位。
各个布局的详细讲解请参见布局、框架与槽位。
物品与槽位
布局中的每个物品都是一个槽位描述,字段相同:
| 字段 | 作用 |
|---|---|
item | 要显示的物品。留空时,槽位会显示玩家主手所持的物品。 |
displayName, lore | 支持 PlaceholderAPI 的 MiniMessage 文本。名称默认是正体;如果想要斜体,请添加 <italic>。空的 displayName 会保留物品原本的名称,而不是用空名称替换它。 |
x, y | 列为 0 到 8,行从顶部的 0 开始。 |
rollPerSlot | 用于在多个槽位中重复且 item 为随机的物品:关闭(默认)时只抽取一次,所有槽位显示同一结果;开启时,每个槽位在菜单打开时各自抽取。菜单保持打开期间结果不变,除非开启了 liveItem。 |
liveItem | 关闭(默认):物品在菜单打开时抽取,并在整个会话中保持;刷新时只有名称和说明会更新。开启:每次刷新都会重新计算该物品,因此随机物品每次都会重新抽取。开启 rollPerSlot 时,每个重复的槽位在每次刷新时各自抽取;否则所有重复槽位每次刷新共用一次抽取。空名称仍会被忽略。 |
count, direction, gap, repeatY | 槽位重复,见下文。 |
criteria | 对不满足条件的玩家不绘制该槽位。 |
viewPermission | 看到该槽位所需的权限。 |
clickPermission | 使用该槽位所需的权限;没有该权限时,槽位会被绘制,但无法点击。 |
allowPickup, isGhost | 物品是否可以从菜单中拿出。两者默认都关闭;幽灵物品(ghost)可以被点击,但绝不能被移动或复制。 |
cooldownTicks | 同一个槽位在可再次点击之前的 tick 数。 |
interactionList, triggers, modifiers | 点击会触发什么。 |
input | 点击时打开文本输入对话框。 |
storageId | 将该槽位变成来自 storagePool 的持久存储槽位。 |
buttonType, buttonPrefix | 将该槽位标记为由另一个扩展提供的按钮。 |
animation | 菜单打开时,在以毫秒为单位的时长内把槽位移动到目标位置。 |
槽位重复。 count 会沿着 direction(right、left、down、up)重复一个物品。gap 是步长,而不是间距:1 表示相邻,2 表示副本之间隔一个空槽,0 的行为等同于 1。repeatY 会在垂直方向的轴上重复整行。没有 direction 时,count、gap 和 repeatY 都会被忽略,只放置一个槽位。这些字段计算的是槽位数量,而不是物品的堆叠数量。
重叠。 当两个槽位占用同一个格子时,有实际作用的那个(带点击动作、标签或存储槽位的)会胜过纯装饰性的,与声明顺序无关。在同类的两个槽位之间,后声明的胜出。系统会记录一次指明该格子的警告。
点击动作
interactionList 将点击类型映射到命令和触发器。可用的类型有 LEFT_CLICK、RIGHT_CLICK、SHIFT_LEFT_CLICK、SHIFT_RIGHT_CLICK、MIDDLE_CLICK、DOUBLE_CLICK、NUMBER_KEY_1 到 NUMBER_KEY_9、DROP、DROP_ALL 和 SWAP_OFFHAND。
commands由控制台执行。%player%会被替换为玩家名称,PlaceholderAPI 占位符会先被解析。triggers会为该玩家触发 TypeWriter 条目。槽位自身的triggers和modifiers会在每次左键或右键点击时触发,或在某个特定交互匹配时触发。closeMenu会在动作执行后关闭窗口。executeReturn会返回上一个菜单。
以 gui: 开头的命令由 GUI 自身处理:
| 命令 | 效果 |
|---|---|
gui:close | 关闭菜单。 |
gui:back | 重新打开该会话的上一个菜单。 |
gui:open <entry_id> 或 gui:action <entry_id> | 触发另一个条目,通常是另一个 Open GUI。 |
gui:view <view_id> | 切换到另一个视图,而不关闭物品栏。 |
gui:page <delta> [layout_id] | 让分页或排行榜布局翻动相应的页数。 |
gui:scroll <dx> <dy> [layout_id] | 滚动一个 scrollable 布局。 |
gui:refresh | 立即重绘菜单。 |
gui:sound <sound_key> | 向玩家播放一个声音。 |
打开、返回与关闭
从另一个菜单打开的 Open GUI 会被推入会话历史,因此 gui:back 或 executeReturn 可以返回到它。按 Escape 关闭会结束会话。打开 book 布局会先结束当前会话。
设置 audio 可在打开、关闭、滚动和点击时播放声音。设置 autoRefreshTicks 可在菜单打开期间每 N 个 tick 重绘一次,这正是让名称和 lore 中的占位符像计数器或计时器一样跳动的原因。
视图、标签页与继承
views 让一个 open_gui 变成共用同一外壳的多个界面。layoutId 为 @view 的 frame 会由当前视图的布局来填充:首先是该视图显式的 frames 映射,然后是名为 <view id>_<frame id> 的布局,最后是对视图 parentId 进行同样的查找。将标签物品的 buttonPrefix 设为 view:,并把 buttonType 设为视图 id,点击时它就会切换视图,并在激活期间高亮。视图标题支持 {breadcrumb}、{view} 和 {root}。
baseMenuId 指向另一个 Open GUI,并把它的 layoutPool、views 和 storagePool 合并到你的菜单之下:id 相同的布局会替换被继承的布局。详情请参见视图、标签页与继承。
存储槽位
GUI Storage artifact 按玩家或按组保存物品。在条目的 storagePool 中声明可复用的配置,然后为物品设置 storageId,或使用 storage 布局。存储槽位的物品名称和 lore 支持 {stored_name}、{stored_amount} 和 {stored_max}。使用 source 的排行榜行还可以使用 {score_raw} 和 {value_<key>}。
默认的点击绑定,你可以在 GUI Global Settings 中修改:
| 操作 | 默认点击方式 |
|---|---|
| 从光标放入一个物品 | 左键点击 |
| 放入光标上的整组物品 | Shift + 左键点击 |
| 取出一个物品 | 右键点击 |
| 取出全部 | 左键点击 |
| 取出一组 | Shift + 右键点击 |
| 从物品栏填充 | 交换副手(F) |
| 将所有物品丢在地上 | 丢弃(Q) |
dropOnClose 会在菜单真正关闭时(按 Escape 或断开连接时,但不包括玩家切换到另一个菜单时),把仍存放在其中的物品丢在玩家脚下。
扩展物品栏
extendToPlayerInventory 会把玩家自己的 36 个槽位显示为额外的四行,形成十行的画布(y 为 0 到 9)。它仅在客户端生效:真实的物品栏永远不会被修改,并会在菜单关闭时恢复,而且无法从底部几行拿起物品。它要求 guiType 为 CUSTOM 且 size 为 SIZE_54。id 以 _extended 结尾的根布局会自行启用它。
配置
GUI 没有配置文件。可以用两个可选的 manifest 条目进行调整:
- GUI Extension Configuration 用于启用
debug日志,以及默认开启的hideVanillaItemStats选项,该选项会隐藏菜单按钮上的攻击伤害、耐久度和附魔行。 - GUI Global Settings 用于设置存储的点击绑定以及技能树连接线物品。
占位符
| 占位符 | 返回值 |
|---|---|
%typewriter_gui_storage_amount_<entry_id>_<slot_index>% | 玩家在某个 GUI Storage 条目中存放的物品数量,为空时返回 0。 |
%typewriter_gui_storage_has_<entry_id>_<slot_index>% | 槽位中有物品时为 true,否则为 false。 |
<slot_index> 是存储槽位的 y * 9 + x。这些占位符始终读取玩家自己的存储,绝不会读取共享的组存储。
在菜单内部,扩展会替换以下标记:{stored_name}、{stored_amount}、{stored_max}(存储槽位),{page}、{next_page}、{prev_page}、{total_pages} 和 {page_indicator}(分页布局的 INDICATOR 按钮),以及 {rank}、{name}、{score}、{group}、{world} 和 {score_<fact_id>}(排行榜行)。
条目
6 个条目,由扩展源码生成。
动作1
- Open GUIOpen a GUI for the player
构件2
- GUI Leaderboard PopulationOffline population data for GUI leaderboards
- GUI StoragePersistent storage artifact for GUI storage slots
清单3
- GUI ConfigGUI Extension Configuration
- GUI LeaderboardGUI leaderboard backed by Typewriter facts
- GUI SettingsGUI Global Settings
故障排查
菜单打开后是空的。 mainLayoutId 为空,或与 layoutPool 中任何布局的 id 都不匹配(id 区分大小写)。使用 baseMenuId 时,留空的 mainLayoutId 会回退为父级的值。启动检查会在控制台中报告无法解析的主布局。
缺少某个槽位,控制台提示 “falls outside the grid”。 该物品落在了负数的 x 或 y,或超出了第 8 列。这通常来自过大的 count,因为它计算的是槽位数。日志会指出布局和被拒绝的坐标。
控制台提示 “Slot overlap ... gap”。 两个槽位占用了同一个格子。gap 是步长:1 表示槽位相邻,2 表示每个之间隔一个空槽,因此把 gap: 1 理解为“一个间隔”的作者,会让副本叠在相邻副本的位置上。请修改 gap 或移动其中一个槽位。
某个槽位管理员能看到,玩家却看不到。 viewPermission 指定了一个只有 OP 才拥有的权限。空值会被忽略,但拼写错误会变成一个真实存在、仅限 OP 的权限节点。视图的 viewPermission 也适用同样的规则。
按钮能显示但没有任何作用。 请依次检查 clickPermission、cooldownTicks,然后是 criteria。如果该槽位有 buttonType,其 buttonPrefix 必须与提供它的扩展匹配;无人认领的标签只会保留你自己的 interactionList,除此之外没有任何作用。命令由控制台运行,因此需要玩家作为发送者的命令会静默失败。
可滚动的内容不会移动。 请检查内容是否比窗口大(virtualWidth 和 virtualHeight):只有在该方向上还有可滚动的内容时,箭头才会显示。如果 showDefaultButtons 关闭且没有 buttons,则完全没有箭头。当默认箭头所在的格子已经有内容物品、按钮或装饰时,它也会让出位置:控制台不会有任何记录,因此请在 GUI Settings 的 scrollArrows 中移动它,或在被滚动区域之外添加带有 direction 和 step 的自定义 buttons。
排行榜只列出在线玩家。 请在排行榜的 population 字段中链接一个 GUI Leaderboard Population artifact,以保留离线玩家最后已知的数值。
扩展菜单没有显示十行。 extendToPlayerInventory 需要 guiType 为 CUSTOM 且 size 为 SIZE_54。
相关扩展
- Shops — 基于 GUI 布局和
shop_button:标记构建的商店菜单。 - Quest Codex — 任务日志菜单,基于同一个
open_gui底板构建。