Puzzle
概览
Puzzle 可以把世界中的某个地点变成一个可解的谜题。你可以从九种机制中选择:把方块推入洞中、重复一个序列、按顺序拨动拉杆、重定向激光束等等。每个谜题都是你在网页面板中放置的一个受众条目。它通过触发器(onStart、onComplete、onFail、onTimeout、onLifespanExpire)和一个可选的事实,将结果报告给 TypeWriter 的其余部分。
棋盘的大部分内容都是在客户端绘制的:方块、高亮、预览和激光粒子都是发送给解谜玩家的数据包,因此两位玩家可以在同一块棋盘上解谜,而看不到彼此的进度。少数机制确实会触及世界:物品展示框谜题会旋转真实的物品展示框,而图案谜题会放置真实的方块,除非开启了 ghostBlocks。
谜题属于你搭建它的地点。它只会为站在其位置所在世界、且距其中某个位置在 activationRadius 格以内的玩家运行。到达棋盘会开始一个会话;走开则结束会话并清除客户端棋盘。
功能
- 九种谜题类型 — 推方块、记忆序列、激光网格、拉杆顺序、图案摆放、合作压力板、物品展示框、密码锁和基座献祭。
- 共享的生命周期 — 每个谜题都有时间限制、最大错误尝试次数、失败冷却、存续时间、提示和完成消息。
- 处处都有触发器 — 开始、完成、失败、超时和存续到期都可以启动一个 TypeWriter 条目。
- 事实奖励 — 谜题解开时,某个事实会增加
amount。 - 谜题链 — 按顺序运行多个谜题,并对整条链作出反应。
- 空间限制 — 世界检查和激活半径可防止棋盘出现在其他维度或很远的地方。
- 统计数据 — 每位玩家的尝试次数、解开次数和最佳时间,并提供排行榜命令。
- 状态事实 — 读取某个谜题对某位玩家是已解开还是处于激活状态。
- 兼容 Folia — 对世界的访问在拥有该谜题的区域上运行。
安装
- 安装 TypeWriter 官方的
Basic扩展,它提供了谜题所使用的共享类型(音效、物品、变量)。 - 将 Puzzle 的 JAR 放入
plugins/Typewriter/extensions/并重启服务器。 - 可选:如果你希望保存尝试次数、解开次数和最佳时间,请在页面中添加一个 Puzzle Artifact 条目(参见“配置”中的警告)。
快速上手
构建一个三拉杆的门谜题:
- 在 TypeWriter 网页面板中,打开一个**清单(manifest)**页面,添加一个 Lever Order 条目。
- 在
levers中添加三个拉杆。对每个拉杆,在游戏中捕获一个拉杆方块的position,并将orderIndex依次设为0、1和2,即玩家必须拨动它们的顺序。 - 将
amount设为3,使提示显示为1/3、2/3、3/3。开启resetOnWrong,如果你希望出错后有一段暂停,则将cooldownOnFail设为5。 - 将
onComplete链接到一个打开门的序列,并将fact链接到一个存储奖励的可缓存事实。这个事实也是记住谜题已被解开的可靠方式。 - 可选:把谜题放在某个受众筛选器之下,例如任务受众,使其只对合适的玩家运行。
- 发布,重启或重载扩展,然后走近拉杆。按正确顺序拨动它们即可解开谜题。拨错拉杆会重置拉杆状态并触发
onFail。
谜题条目会在扩展加载时注册。添加谜题后,请先重载 TypeWriter 或重启,再使用 /typewriter puzzle start。
工作原理
会话。 玩家进入半径范围时会话开始。它会清除进度、错误尝试次数和失败冷却,启动 timeLimit 和 lifespan,并触发 onStart。因此 maxAttempts 是每个会话的额度:用完额度的玩家可以走开再回来。玩家已经解开的谜题不会再次为其重新开启。
已解开的谜题会变为惰性。 玩家解开 Block Push、Combination Lock、Lever Order 或 Laser Grid 后,它不再为该玩家绘制面板,也不再取消其交互,即使玩家重新回到它的受众中(过滤器闪烁、重新加入)。已解开谜题的拉杆和按钮会像普通方块一样工作,已解开的 Pedestal 也不再吞掉物品的拾取。
被取消的点击。 服务器会用被点击方块的真实状态回应被取消的点击,这会擦除客户端绘制的方块。因此,每次点击被取消后一个 tick,面板都会重新绘制(Memory 按钮、Pattern 网格、被拒绝的放置、被拒绝的推动),玩家左键击打已绘制的镜子或拉杆时也是如此。
完成只会触发一次。 事实写入、完成消息、onComplete 以及链的进度都来自同一个被接受的结果,因此重复点击不会让它们触发两次。
进度。 每个正确的步骤会使进度加一,最多到 amount。hintMessage 中的 {progress} 和 {total} 显示进度和 amount。谜题不会根据自身大小设置 amount,因此请自行填入真实的步骤数。
共享选项
每个谜题都有这些字段。
| 字段 | 含义 |
|---|---|
fact | 谜题解开时增加 amount 的可缓存事实。 |
amount | 最大进度,以及加到事实上的奖励。 |
timeLimit | 谜题超时前的秒数。0 表示禁用。 |
cooldownOnFail | 失败后玩家必须等待的秒数。 |
maxAttempts | 每个会话允许的错误尝试次数。0 表示不设限制。 |
resetOnWrong | 错误的步骤是否清除进度。 |
showHints、hintMessage | 每一步之后的聊天提示,可使用 {progress} 和 {total}。 留空则不发送任何内容。 |
completionMessage | 解开时发送一次。留空则关闭。默认值为 <green>Puzzle completed!。 |
onStart、onComplete、onFail、onTimeout、onLifespanExpire | 为该玩家触发的条目。 |
lifespan | 无论玩家做什么,谜题对该玩家过期前的秒数。0 表示禁用。 |
activationRadius | 谜题位置周围的格数。0 会取消距离检查,但世界检查始终适用。默认值为 16。 |
criteria | 玩家要参与该谜题必须满足的条件。 |
共享谜题
当 isShared 开启且谜题的 fact 带有群组(队伍、团队)时,该群组共同进行同一个谜题:一名玩家的每一步、每次失误和冷却都会复制给该群组的在线玩家。第一个解开它的成员即为整个群组解开:fact 只增加一次 amount,每名成员各获得一次完成消息、onComplete 和链步骤。不在线的成员会在回来时获得它们。一名玩家对同一谜题最多获得一次完成,即使更换群组也是如此;只有涉及该玩家的管理员重置,才能让他再次获得。
- 解开的记录按群组保存。死亡或重新连接不会重置它,也不会再次给予任何奖励,这与单人谜题不同,后者在死亡或断开连接后可以重玩。
- 统计数据和每名玩家的棋盘仍然是个人的。如果
fact没有群组,或玩家不属于任何群组,则不共享任何内容。Coop Pressure 会忽略isShared:它本来就会为每名参与者完成谜题。 - 要再次游玩,请使用
/typewriter puzzle admin reset <player> <id>,它会为该玩家的整个群组重新开启该谜题。
群组的解开记录保存在 Puzzle Artifact 中。没有它,记录只存在于内存中:服务器重启后,群组可以再次解开谜题并再次获得奖励。使用 isShared 之前请先添加该 artifact。回退到扩展的旧版本同样会丢失共享记录,因为该版本保存 artifact 时不含此记录,奖励会被再次发放。解开记录会在下一次保存时写入(每 30 秒一次):在此期间发生崩溃可能会丢失它。
谜题类型
| 条目 | 玩家的操作 | 主要字段 |
|---|---|---|
| Block Push | 左键或右键点击方块,将其朝玩家面对的方向推一格,直到每个目标洞都被填满。 | pushBlocks、targetHoles(含 acceptedMaterial)、blockMaterial、holeMaterial。方块数量必须等于洞的数量。 allowPull 没有效果(拉动尚未实现)。 |
| Memory Sequence | 观看按钮依次亮起,然后按相同顺序点击它们。每个会话的序列都是随机的。 | buttons(至少 2 个)、sequenceLength、highlightDuration、stepGap、inputDelay(tick)、highlightMaterial。 |
| Laser Grid | 右键点击镜子使其旋转,直到每个接收器都被其对应颜色的光束击中。 | emitters(颜色、方向)、mirrors(rotatable、initialDirection)、receivers(requiredColor)、maxReflections、maxBeamLength、beamDensity。 |
| Lever Order | 按 orderIndex 的顺序右键点击拉杆。拨错拉杆会重置所有拉杆。 | levers(position、orderIndex)。 |
| Pattern Placement | 在网格上放置方块以复现一个图案。 | pattern、gridOrigin、columns、previewOffsetY、ghostBlocks、tolerance。 |
| Coop Pressure | 多名玩家站在不同的压力板上并坚持几秒钟。 | plates、requiredPlayers、holdDuration(秒)。 |
| Item Frame | 把物品放入已有的物品展示框,然后将其旋转到目标角度。 | frameTargets(correctRotation 0 到 7、requiredItem)、checkItem。 |
| Combination Lock | 按密码的顺序点击数字方块。 | digits(位置),code(数字索引,从 0 开始计数)。 |
| Pedestal | 手持物品右键点击基座方块,或在其附近丢下或拾起该物品。 | targetPosition、requiredItem、consumeItem、pickupRadius、consumeDroppedItem。 |
各类型的详细说明:
- Laser Grid。 光束从发射器旁一格处开始,并沿直线前进。镜子会把它导向镜子所朝的方向。右键点击镜子会把它的方向设为与你点击的那一面相反的方向;如果点击的是它的顶面或底面,则顺时针旋转。实心方块会挡住光束。当每个接收器都被其
requiredColor颜色的光束击中时,谜题即告解开。光束以粒子显示。 右键点击镜子时它不会消失:点击后面板会重新绘制。光束从方块中心穿过,并恰好在每面镜子上转向;beamDensity是每个方块的粒子数。光束最长为maxBeamLength格(默认 64;0 或更小等于 64,绝不无限),并且从不加载区块:它会在已加载世界的边缘停止。光束更长的棋盘必须调高maxBeamLength。 - Block Push。 每次推动会让方块朝玩家面对的方向移动一格。被推回起始格的方块总会在那里被接受,方块被推过目标洞并离开后,该目标洞会重新绘制。请让世界中的起始格保持为空(空气)。 那里的真实方块在客户端会被隐藏,但对服务器仍是实心的,因此没有人能站在上面。被拒绝的推动(墙或其他方块)每个方块和目标格只计一次错误,直到某次推动成功,因此对着墙壁按住点击不会耗尽
maxAttempts。 - Lever Order。 拉杆会以玩家到达面板时它在世界中的状态静置,从该状态开始切换,并在重置或顺序错误后恢复到该状态,即使它静置时是开启的。失败冷却期间的点击不会触及真实的拉杆。
- Combination Lock。 编号从 0 开始:
digits中列出的第一个位置是数字 0,第二个是数字 1,依此类推。code按点击顺序列出这些索引。例如,digits中有四个按钮时,密码[2, 0, 3]表示:第三个按钮、第一个按钮、第四个按钮。code中超出数字范围的值会使锁忽略所有按压;服务器日志会指出该值。 - Pattern Placement。
pattern逐行列出材质,宽度为columns,其长度必须是columns的倍数。网格位于gridOrigin的 Y 高度处的水平面上,列沿 X 方向,行沿 Z 方向。预览悬浮在其上方previewOffsetY格处。开启ghostBlocks时,网格上会直接绘制半透明版本,正确的方块会在客户端显示,并从玩家身上扣除物品。错误的放置始终会被取消。tolerance是进度重置之前允许的错误放置次数。 - Coop Pressure。 当玩家的脚踩在该方块上,或其上方一格的方块上时,压力板就会被计入。每块板一名玩家。受众中的所有玩家都会参与,因此请用
/typewriter puzzle join <entry id>添加他们。当压力板被坚持了holdDuration后,所有参与者都完成该谜题。 - Item Frame。 展示框必须已存在于配置的位置;谜题从不会生成展示框。空的展示框只接受
requiredItem,并从玩家手中取走。每次点击已放入物品的展示框都会将其旋转一步(45 度),correctRotation是正确位置所对应的步数,范围为 0 到 7。展示框是真实的实体,因此旋转对所有人都可见。 - Pedestal。
requiredItem可以通过右键点击给出,或丢在基座周围pickupRadius格以内。当开启consumeDroppedItem时,丢下的匹配物品会被移除。
命令
所有命令都是 /typewriter(别名 /tw)的子命令。<id> 是谜题的条目 id。只有 admin 命令需要权限。
| 命令 | 说明 |
|---|---|
/typewriter puzzle | 打开第一个 Puzzle Menu 条目的聊天菜单,或提示不存在。 |
/typewriter puzzle list | 列出已注册的谜题及其类型。 |
/typewriter puzzle start <id> | 将你自己加入某个谜题的受众。如果你已经解开了它,则会拒绝。 |
/typewriter puzzle join <id> | 加入一个已在运行的谜题,例如合作谜题。 |
/typewriter puzzle chain <id> | 通过条目 id 或名称启动一条谜题链。 |
/typewriter puzzle status | 显示你在每个谜题上的状态:激活、已完成、已停止或未激活。 |
/typewriter puzzle leaderboard <id> | 显示你在某个谜题上的统计数据以及前十名玩家。 |
/typewriter puzzle stop [id] | 停止一个谜题或你所有激活的谜题,并重置其进度。 |
/typewriter puzzle close | 关闭你已打开的物品栏。 |
/typewriter puzzle admin reset <player> <id> | 重置某位玩家在某个谜题上的状态并重新启动它。 对于共享谜题,会为该玩家的整个群组重新开启。 |
/typewriter puzzle admin reload | 清除所有运行时状态和谜题链。 |
权限
| 权限 | 授予 |
|---|---|
puzzle.admin | /typewriter puzzle admin 命令。 |
配置
持久化。 没有 Puzzle Artifact 时,不会保存任何内容。有了它,扩展会每 30 秒以及在关闭时保存状态和统计数据,并在启动时加载。请恰好创建一个构件,并保留其生成的 artifactId。 共享谜题必须使用该 artifact(参见共享谜题)。
玩家断开连接时,其谜题状态会从内存中移除,下一次保存也不会包含它。统计数据会保留。若要永久记住已解开的谜题,请设置该谜题的 fact,并在你的条件中读取该事实。Puzzle Solved Fact 只反映内存中的状态。 共享谜题是例外:它的解开记录属于群组,并保存在 artifact 中。
消息。 游戏中显示的文本没有任何一条固定在代码里。属于单个谜题的文本是其条目的字段,命令的回复和所有谜题共用的两条消息则是 snippet。参见可编辑文本。
可编辑文本
文本使用 MiniMessage,因此可以改写、翻译或重新着色。文本中的 {name} 会被下表所列的值替换;不存在的占位符会原样保留。PlaceholderAPI 占位符(%...%)会为接收文本的玩家解析。只有服务器控制台日志保持固定,为英文。
条目的字段
这些字段位于 Web 面板中的条目上。每个字段都是按玩家显示的文本,因此支持变量,留空则不发送任何内容。现有页面保留其已保存的文本;下面的默认值适用于新条目,以及从未保存过该字段的页面。
| 条目 | 字段 | 默认值 | 占位符 |
|---|---|---|---|
| 每个谜题 | hintMessage | 每种谜题类型的提示,例如 <yellow>Repeat the sequence! {progress}/{total}。 | {progress}, {total} |
| 每个谜题 | completionMessage | <green>Puzzle completed! | 无 |
| Puzzle Menu | titleText | <gold><bold>Puzzles</bold> <gray>- | 无 |
| Puzzle Menu | puzzleLineText | <green>{puzzle} <gray>- <aqua>click to start | {puzzle}(谜题条目的 id) |
| Puzzle Menu | footerText | <gray>Use <white>/typewriter puzzle stop<gray> to stop all active puzzles. | 无 |
| Memory Sequence | memorizeMessage | <gold>Memorize the sequence… | 无 |
| Memory Sequence | yourTurnMessage | <green>Your turn! <white>{progress}/{total} | {progress}, {total} |
| Memory Sequence | stepCorrectMessage | <green>Well done! <white>{progress}/{total} | {progress}, {total} |
| Lever Order | wrongOrderMessage | <red>Wrong order — the levers have been reset. | 无 |
| Pattern Placement | progressMessage | <green>Pattern <white>{progress}/{total} | {progress}, {total} |
| Item Frame | missingFrameMessage | <red>No item frame at <white>{x} {y} {z} | {x}, {y}, {z} |
| Item Frame | wrongItemMessage | <red>That is not the right item in the frame. | 无 |
| Item Frame | insertingMessage | <gray>Inserting the item… | 无 |
| Item Frame | missingItemMessage | <yellow>First place the requested item in the frame. | 无 |
| Item Frame | itemPlacedMessage | <green>Item placed — rotate the frame. | 无 |
| Item Frame | rotationMessage | <gray>Rotation {current}/{total} | {current}, {total} |
hintMessage、completionMessage 和聊天菜单字段发送到聊天栏;其余字段显示在动作栏。其中一些默认值在早期版本中是法语,现在是英语,已保存法语文本的页面会保留原文本。
Snippet
/typewriter puzzle 命令输出的所有内容(包括管理命令)以及所有谜题共用的两条消息,都是 TypeWriter 的 snippet。打开 plugins/Typewriter/snippets.yml,找到 content.puzzle 下的键,修改值并运行 /tw reload。扩展在启动时会以默认文本注册其所有 snippet,因此首次启动后它们会出现在该文件中。
Snippet(位于 content.puzzle. 下) | 默认值 | 占位符 |
|---|---|---|
command.no_menu | <red>No puzzle menu configured. | 无 |
command.list.none | <red>No puzzles configured. | 无 |
command.list.header | <gold>=== Available Puzzles === | 无 |
command.list.line | <green>{puzzle} <gray>- {type} | {puzzle}, {type} |
command.puzzle_not_found | <red>Puzzle not found: <white>{puzzle} | {puzzle} |
command.start.already_completed | <yellow>Puzzle already completed. Use <white>/typewriter puzzle admin reset {player} {puzzle}<yellow> to replay it. | {player}, {puzzle} |
command.start.started | <green>Starting puzzle: <white>{type} | {type} |
command.join.already_completed | <yellow>Puzzle already completed for you: <white>{puzzle} | {puzzle} |
command.join.joined | <green>Joined puzzle: <white>{type} | {type} |
command.stop.not_active | <gray>Puzzle is not active: <white>{puzzle} | {puzzle} |
command.stop.stopped | <yellow>Stopped puzzle: <white>{puzzle} | {puzzle} |
command.stop.all_none | <gray>No active puzzle. | 无 |
command.stop.all_stopped | <yellow>Stopped <white>{count} <yellow>puzzle(s). | {count} |
command.status.header | <gold>=== Puzzle Status === | 无 |
command.status.line | <gray>{puzzle}: {status} <dark_gray>progress={progress} | {puzzle}, {status}, {progress} |
command.status.active | <green>ACTIVE | 无 |
command.status.completed | <aqua>COMPLETED | 无 |
command.status.stopped | <yellow>STOPPED | 无 |
command.status.inactive | <gray>INACTIVE | 无 |
command.chain.not_found | <red>Chain not found: <white>{chain} | {chain} |
command.chain.invalid | <red>Chain is invalid or contains unresolved puzzles: <white>{chain} | {chain} |
command.chain.empty | <red>Chain has no resolvable puzzle entries. | 无 |
command.chain.started | <green>Starting puzzle chain: <white>{chain} | {chain} |
command.leaderboard.header | <gold>=== Puzzle Stats: <white>{puzzle} <gold>=== | {puzzle} |
command.leaderboard.attempts | <gray>Attempts: <white>{attempts} | {attempts} |
command.leaderboard.solves | <gray>Solves: <white>{solves} | {solves} |
command.leaderboard.best_time | <gray>Best time: <white>{seconds}s | {seconds} |
command.leaderboard.rows_header | <gold>--- Leaderboard --- | 无 |
command.leaderboard.row | <gray>#{rank} <white>{player} <gray>- <white>{solves} solves | {rank}, {player}, {solves} |
command.leaderboard.row_timed | <gray>#{rank} <white>{player} <gray>- <white>{solves} solves <gray>{seconds}s | {rank}, {player}, {solves}, {seconds} |
command.admin.reset.player_not_found | <red>Player not found: <white>{player} | {player} |
command.admin.reset.done | <green>Reset puzzle <white>{puzzle} <green>for <white>{player} | {puzzle}, {player} |
command.admin.reset.done_group | <green>Reset puzzle <white>{puzzle} <green>for <white>{player} <green>and its group (<white>{count}<green> online) | {puzzle}, {player}, {count} |
command.admin.reload.done | <green>Puzzle runtime states cleared. | 无 |
play.cooldown_wait | <red>Puzzle on cooldown — <white>{seconds}s | {seconds} |
play.max_attempts | <red>Too many mistakes — restart the puzzle to try again. | 无 |
{type} 是谜题类型的名称(Memory Sequence、Lever Order 等),{status} 是四个 command.status.* snippet 之一,因此两者都保留各自的颜色。其他占位符均为纯文本:玩家在命令中输入的内容无法为回复添加格式。
条目
14 个条目,由扩展源码生成。
动作1
- Puzzle MenuPuzzle Menu
事实2
- Puzzle Active FactPuzzle Active Fact
- Puzzle Solved FactPuzzle Solved Fact
受众9
- Puzzle Block PushPush blocks into target holes -
- Puzzle Combination LockEnter the correct code by pressing buttons/levers in sequence
- Puzzle Coop PressureCooperative pressure plate puzzle -
- Puzzle Item FrameRotate item frames to the correct angles
- Puzzle Laser GridLaser grid puzzle -
- Puzzle Lever OrderPuzzle where levers must be activated in the correct order
- Puzzle MemoryMemory sequence puzzle -
- Puzzle PatternPattern placement puzzle -
- Puzzle PedestalOffer an item on a pedestal to solve the puzzle
构件1
- Puzzle ArtifactPersists puzzle states across server restarts
其他条目1
- Puzzle ChainChain of puzzles -
故障排查
棋盘没有出现。 你必须位于谜题位置所在的世界,并且在其中某个位置的 activationRadius 范围内。保持为 0, 0, 0 的位置会被忽略,因此没有设置任何位置的谜题没有锚点。
点击时没有任何反应。 请检查你是否在受众中(条件必须匹配,并且受众筛选器必须包含你),以及该谜题是否已对你解开:使用 /typewriter puzzle status,并使用 /typewriter puzzle admin reset <player> <id> 重新游玩。失败冷却期间的点击会被忽略;动作栏会显示剩余时间。
/typewriter puzzle start 提示找不到该谜题。 谜题在扩展加载时按条目 id 注册。添加谜题后请重载,并使用条目 id 而不是名称。
记忆谜题始终无法开始。 buttons 至少需要 2 项,并且每一项都需要一个位置。
图案谜题忽略了我放置的方块。 pattern 的长度必须是 columns 的倍数,并且你必须在 gridOrigin 的 Y 高度处的网格上放置方块。错误的材质会被取消。
物品展示框谜题提示没有展示框。 请先在配置的位置放置一个物品展示框。日志中还会输出带有坐标的警告。
重新连接后进度丢失。 请参阅“配置”中的警告:使用 fact 来保留结果。
合作谜题始终无法完成。 每位玩家都必须在受众中(/typewriter puzzle join <id>),站在不同的压力板上,并在上面停留 holdDuration。压力板上的掉落物不计入。