Vote System
概览
Vote System 为你的服务器添加投票功能。一个投票就是一个投票定义:包含标题、选项列表、可选的结束日期,以及投票关闭后显示的消息。每位玩家在每个投票中只能投一次票,可通过 TypeWriter 动作(对话选项、NPC、菜单点击)或命令投票。投票数据存储在 TypeWriter 构件中。
结果以 PlaceholderAPI 占位符的形式提供,因此你可以将其显示在计分板、全息图或菜单中,管理员还可以通过命令查看。这是玩家之间的投票,而不是服务器列表投票网站的链接。
功能
- 多选项投票 — 每个选项都有自己的文本,可包含占位符和颜色。
- 每位玩家一票 — 对同一投票的第二次投票会被拒绝,除非你在 Vote Config 中设置了用于再次投票的
cooldownSeconds。 - 结束日期 — 在指定的日期和时间自动关闭投票。
- 关闭和阻止消息 — 告诉迟到的投票者投票已结束,告诉过早再次投票的玩家需要等待。两者都是 MiniMessage。
- 投票动作 — 为触发的玩家投出一票,并且仅在投票被记录时才继续你的序列。
- 状态消息 — 根据玩家是否已投票发送不同的消息。
- 实时结果 — 总票数、各选项票数以及玩家所选的选项,均以占位符提供。
- 管理员命令 — 代玩家投票、读取结果以及重置投票。
安装
- 如果你想用占位符显示结果,请安装 PlaceholderAPI。
- 将 Vote System 的 JAR 放入
plugins/Typewriter/extensions/并重启服务器。
快速上手
举办一场关于下一个地图主题的投票:
- 在 TypeWriter 网页面板中,打开一个**清单(manifest)**页面,添加一个 Vote Data 构件。保留默认的
artifactId(vote_storage)。 - 添加一个 Vote Definition。将
displayName设为Next map theme,在options中添加三项(例如Desert、Snow、Jungle),并在data中链接该构件。 - 将
endDate设为 UTC 日期和时间,例如2026-12-31T23:00:00Z,并将closedMessage设为一段简短的文本。若投票没有结束时间,请将endDate留空。 - 打开一个**序列(sequence)**页面。为每个选项添加一个 Vote Action:链接
definition,并将optionIndex设为0、1或2。选项索引从 0 开始。 - 将每个动作连接到某个对话选项、NPC 交互或菜单按钮,并把你的感谢消息或奖励放入该动作的
triggers中。它们只会在投票被记录时运行。 - 可选:在选项之前添加一个 Vote Message,用来提醒已经投过票的玩家。
- 发布。在计分板上使用
%typewriter_vote_total_<definition id>%即可显示票数。
投票的数据存储在由其投票定义的条目 id 派生出的键下。如果你删除该条目或更改其 id,就再也找不到已存储的投票。
命令
所有命令都是 /typewriter(别名 /tw)的子命令。<definition> 是投票定义的 id 或名称。
| 命令 | 权限 | 说明 |
|---|---|---|
/typewriter vote <definition> <option> [target] | typewriter.vote.cast | 为某个选项投票。选项从 1 开始。指定 target 时,为所选玩家投票。 |
/typewriter vote stats [definition] | typewriter.vote.stats | 显示某个投票或全部投票中各选项的票数和总数。 |
/typewriter vote reset <definition> | typewriter.vote.reset | 删除某个投票的全部票数。 |
它们同时都需要 typewriter.vote。
可编辑文本
游戏中显示的文本没有任何一条固定在代码里。一次投票的消息是其条目的字段:Vote Definition 的 closedMessage,以及 Vote Message 的两条消息。/typewriter vote 命令的回复是 TypeWriter 的 snippet,因此无需重新构建即可改写、翻译或重新着色。打开 plugins/Typewriter/snippets.yml,找到 content.vote 下的键,修改值并运行 /tw reload。扩展在启动时会以默认文本注册其所有 snippet,因此首次启动后它们会出现在该文件中。保持默认值则不会有任何变化。
命令回复使用 MiniMessage。发送时带有 Typewriter 回复前缀(Typewriter »),该前缀来自引擎,不属于这些 snippet。文本中的 {name} 会被表中所列的值替换;不存在的占位符会原样保留,值中不会再次查找占位符。服务器控制台日志保持英文。
Snippet(位于 content.vote. 下) | 默认值 | 占位符 |
|---|---|---|
usage | <gray>Usage: /tw vote <definition> <option> [target]</gray> | 无 |
option_unavailable | <red>Option {option} is not available for {definition}.</red> | {option} (输入的编号), {definition} (投票的 id) |
already_voted | <red>{player} cannot vote in {definition} yet.</red> | {player}, {definition} |
cast_option_fallback | option {number} | {number} (选项的位置,从 1 开始) |
recorded_for_voter | Your vote for <blue>{definition}</blue> has been recorded as <green>{option}</green>. | {definition}, {option} (选项的文本) |
recorded_by_other | Recorded vote for <blue>{player}</blue> in {definition}: {option}. | {player}, {definition}, {option} |
recorded_for_self | Vote registered for {definition}: {option}. | {definition}, {option} |
cast_failed | <red>Unable to register the vote for {player} in {definition}.</red> | {player}, {definition} |
reset_done | <green>Votes reset for {definition}.</green> | {definition} |
no_definitions | There are no active vote definitions. | 无 |
stats_line | Stats for {name} [{definition}]: {options} | total: {total} | {name} (displayName,为空时为条目名称), {definition}, {options}, {total} |
stats_option | {number}: {votes} ({label}) | {number}, {votes}, {label} (选项的文本) |
stats_option_fallback | Option {number} | {number} |
stats_separator | , | 无 |
entry_not_found | Could not find entry {entry} | {entry} (输入的内容) |
entry_rejected | Entry did not pass filter | 无 |
stats_option 会为每个选项重复一次,结果用 stats_separator 连接,组成 stats_line 的 {options}。stats_option_fallback 是没有文本的选项的 {label},cast_option_fallback 是此类选项在记录投票时所发消息中的名称。最后两个 snippet 是命令错误,由游戏以红色显示:它们是纯文本,不使用 MiniMessage。
投票关闭时,/tw vote 会向工作人员显示该投票的 closedMessage(只有该消息为空时才使用 cast_failed)。cast_failed 也用于缺少数据条目或选项超出范围的情况。
权限
| 权限 | 授予 |
|---|---|
typewriter.vote | 访问 /typewriter vote 命令。 |
typewriter.vote.cast | 通过命令投票。 |
typewriter.vote.stats | 通过命令读取结果。 |
typewriter.vote.reset | 重置投票。 |
投票动作不会检查这些权限:它只需要该条目被执行。
占位符
将 <id> 替换为投票定义的条目 id。它们需要 PlaceholderAPI。
| 占位符 | 返回 |
|---|---|
%typewriter_vote_display_<id>% | 投票的 displayName。 |
%typewriter_vote_option_<id>_<n>% | 第 n 个选项的文本,从 1 开始。 |
%typewriter_vote_total_<id>% | 总票数。 |
%typewriter_vote_stats_<id>% | 每个选项的票数,以逗号分隔。 |
%typewriter_vote_stats_<id>_<n>% | 第 n 个选项的票数,从 1 开始。 |
%typewriter_vote_player_<id>% | 玩家所投选项的文本;如果没有投票则为空。 |
%typewriter_vote_has_voted_<id>% | 玩家暂时无法再次投票(已投票且冷却未结束)时为 1,否则为 0。 |
%typewriter_vote_closed_<id>% | 结束日期已过时为 1,否则为 0。 |
%typewriter_vote_remaining_<id>% | 距结束日期的整秒数;没有结束日期或已过期时为 0。 |
vote_votes_<id> 的作用与 vote_stats_<id> 相同。
条目
7 个条目,由扩展源码生成。
动作2
- Vote ActionVote Action
- Vote MessageVote Message
事件1
- Vote Cast EventVote Cast Event
事实1
- Vote FactVote Fact
构件1
- Vote DataVote Data
清单2
- Vote ConfigVote System Configuration
- Vote DefinitionVote Definition
Vote Cast Event 和 Vote Fact 虽然存在,但目前没有任何功能读取它们:该事件从不触发,事实也不会由投票填充。Vote Config 会被读取:它设置再次投票的冷却时间和调试日志。
故障排查
投票没有被记录。 当缺少某些内容时,动作和命令会静默失败:投票定义必须已链接 data 构件,选项必须存在,玩家不能已经投过票(或其冷却必须已结束,见 Vote Config),并且投票不能已过 endDate。当投票已关闭时,closedMessage 会发送给玩家。玩家仍需等待时,会发送 blockedMessage。在 Vote Config 中开启 debug,可记录每次拒绝的原因。
票投给了错误的选项。 投票动作的选项从 0 开始计数,而命令和占位符从 1 开始计数。
投票始终不会关闭。 endDate 必须是以 UTC 表示、以 Z 结尾的 ISO-8601 时刻,例如 2026-12-31T23:00:00Z。其他格式的值会被忽略,投票将保持开放。
重命名条目后结果为空。 票数与定义的条目 id 绑定。请恢复旧 id,或重置投票后重新开始。
占位符原样显示。 请检查是否已安装 PlaceholderAPI,以及 id 是否为该定义的条目 id。在第一次投票之前请求占位符时,扩展同样能找到对应的定义。
消息中的颜色看起来不对。 Vote Message 的状态消息、closedMessage 和 blockedMessage 会在解析占位符后按 MiniMessage 读取:请写 <red>文本</red>,不要使用 &c 代码。
玩家可以投两次票。 如果 Vote Config 中的 cooldownSeconds 大于 0,冷却结束后这是预期行为。否则,检查按玩家和按投票进行。使用多个选项相同的投票定义会创建彼此独立的投票。