HyVote - Votifier & 奖励 (NuVotifier 投票监听器)

HyVote - Votifier & 奖励 (NuVotifier 投票监听器)

🧩 一体化 - Votifier (v1&v2)、奖励、投票指令、提醒、颜色、广播、音效、转发、通知和自定义界面!

实用

🗳️ HyVote

一款强大的 Hytale 插件,支持 Votifier V1 和 V2 协议,用于奖励在服务器列表上投票的玩家。

image 需要帮助?加入我们的 Discord!


✨ 功能特性

  • 🎁 奖励指令 - 玩家投票时可执行任意服务器指令
  • 📢 广播消息 - 向所有在线玩家宣布投票,支持多条消息
  • 🎨 颜色支持 - 使用 TaleMessage 标签(<green><gold><aqua> 等)进行完整颜色格式化
  • 🔔 投票通知 - 向投票者发送游戏内 UI 通知
  • 🔊 自定义音效 - 玩家领取投票奖励时播放音效
  • 🔗 投票菜单 - /vote 指令打开一个 GUI,内含可点击的投票网站链接和投票排行榜
  • 🏆 投票排行榜 - /votetop 显示本月和全时段的投票冠军
  • ⏰ 投票提醒 - 提醒长时间未投票的玩家
  • 💾 投票数据库 - JSON 数据库追踪投票记录、月度统计和待领取奖励
  • 🔐 双协议支持 - 同一端口同时支持 Votifier V1 (RSA) 和 V2 (HMAC-SHA256)
  • 📡 投票转发 - 将投票转发至网络中的其他服务器
  • 🎯 事件系统 - 提供 VoteReceivedEvent 供其他插件集成
  • 🌍 完全可翻译 - 所有消息均可通过 JSON 配置文件自定义
  • 🔧 调试模式 - 提供详细日志以便排查问题

🎮 指令

  • /vote - 打开投票菜单,显示投票网站和投票冠军
  • /votetop - 显示本月投票冠军及你的排名
  • /votetop all - 显示全时段投票冠军及你的排名
  • /votetop previous - 显示上个月的投票冠军
  • /hyvote - 显示 HyVote 管理员指令
  • /hyvote test <玩家> - 为某个玩家发送测试投票(权限:hytalist.hyvote.admin
  • /hyvote reload - 重载所有配置文件(权限:hytalist.hyvote.admin
  • /testvote <玩家> - 测试指令的别名,如果未被其他插件占用(权限:hytalist.hyvote.admin

🎨 颜色格式化

所有消息均支持颜色标签:<green><gold><aqua><red><yellow><gray><white> 等。

示例:<green>%player%</green> <gray>在</gray> <gold>%service%</gold> <gray>上投了票</gray>

查看所有选项:TaleMessage CurseForge 页面


📄 config.json

Votifier 服务器和全局投票广播的主配置文件。

image

{
  "Port": 8192,
  "Token": "auto-generated-secure-token",
  "DisableV1Protocol": false,
  "BroadcastMessage": true,
  "BroadcastWhenOffline": true,
  "BroadcastMessages": [
    "<green>%player%</green> <gray>已在</gray> <aqua>%service%</aqua><gray>上投票!</gray>",
    "<gold>使用 <aqua>/vote</aqua> 投票,并用 <aqua>/votetop</aqua> 查看你的统计信息</gold>"
  ],
  "DebugMode": false,
  "CheckForUpdates": true,
  "CustomTokens": {
    "ExampleServerListName": "random-generated-token"
  }
}
  • Port - Votifier 服务器端口(默认:8192)
  • Host - 绑定地址(默认:"0.0.0.0")。默认配置中不显示——仅在需要绑定到特定接口时添加此项。
  • Token - 自动生成的 V2 认证令牌(用作所有服务的默认令牌)
  • DisableV1Protocol - 禁用 V1 (RSA) 协议(默认:false)
  • BroadcastMessage - 启用全局投票广播到所有玩家(默认:true)
  • BroadcastWhenOffline - 即使投票者离线也进行广播(默认:true)
  • BroadcastMessages - 每次投票时向所有玩家广播的消息
  • DebugMode - 详细日志(默认:false)
  • CheckForUpdates - 启动时检查更新(默认:true)
  • CustomTokens - 针对生成了自己令牌的投票网站,设置每个服务的 V2 令牌(见下文)

注意: V1 协议的 RSA 密钥会自动生成在 rsa/public.keyrsa/private.key 文件中。

自定义令牌(按服务)

某些服务器列表会生成它们自己的 V2 令牌,而不是让你提供一个。使用 CustomTokens 可以为每个服务设置不同的令牌。服务名匹配不区分大小写

"CustomTokens": {
  "ExampleServerListName": "paste-token-from-that-site-here",
  "AnotherList.net": "another-custom-token"
}

当收到投票时,HyVote 会先在 CustomTokens 中检查服务名称。如果找到匹配项,则使用该令牌进行 HMAC 验证。否则,使用默认的 Token。如果不需要,请移除示例条目。


🎁 rewards.json

奖励配置文件,包含两个系统:Rewards(独立随机)和 RandomReward(加权选择,单一结果)。将 "Enabled": false 可禁用所有奖励(适用于仅广播投票的枢纽服务器)。

{
  "Enabled": true,
  "Rewards": [
    {
      "WaitForPlayer": true,
      "Commands": [
        "give %player% Rock_Gem_Diamond --quantity 1",
        "give %player% Rock_Gem_Emerald --quantity 5"
      ]
    },
    {
      "Chance": 50.0,
      "WaitForPlayer": true,
      "Commands": [
        "give %player% Ingredient_Bar_Silver --quantity 5"
      ]
    },
    {
      "Permission": "vip.example",
      "Commands": [
        "money give %player% 5000"
      ]
    }
  ],
  "RandomReward": {
    "Enabled": false,
    "Tiers": []
  }
}

Rewards

每个组独立随机,拥有各自的几率。组内的所有指令共享相同的几率和权限检查——要么整个组触发,要么都不触发。

  • Chance - 该组触发的几率,0-100(默认:100)
  • WaitForPlayer - 将指令和玩家消息加入队列,直到玩家上线(默认:false)。建议用于 give 指令。待领取奖励会在 3 天后过期。
  • Permission - 所需权限;如果玩家离线或缺少权限,则跳过该组(可选)
  • Commands - 要执行的指令列表(字符串数组)
  • BroadcastMessages - 该组触发时广播给所有在线玩家的消息(可选)。立即发送,即使投票者离线。
  • PlayerMessages - 该组触发时直接发送给投票者的消息(可选)。如果投票者离线且 WaitForPlayer 为 true,则消息会入队,并在玩家加入时发送。
  • Services - 将该奖励限制在特定的投票服务上(可选,字符串数组)。如果省略或为空,则该奖励适用于所有服务。匹配不区分大小写

服务特定奖励示例

根据玩家使用的投票网站给予不同的奖励:

"Rewards": [
  {
    "Commands": ["give %player% Rock_Gem_Diamond --quantity 1"],
    "Services": ["Hytalist.com"]
  },
  {
    "Commands": ["give %player% Rock_Gem_Emerald --quantity 1"],
    "Services": ["AnotherVoteSite.net", "ThirdSite.org"]
  },
  {
    "Commands": ["give %player% Coin --quantity 100"]
  }
]

在此示例中:

  • Hytalist.com 上投票可获得一颗钻石
  • AnotherVoteSite.netThirdSite.org 上投票可获得一颗绿宝石
  • 最后的奖励(100 枚硬币)没有 Services 过滤器,因此适用于所有投票网站

RandomReward

一个加权层级系统,每次投票恰好触发一个层级(或没有)。适用于箱/战利品表风格的奖励。所有层级的几率相加——如果总和小于 100,则剩余部分为获得任何奖励的几率。

示例:层级总和为 30(70% 的几率什么也得不到):

"RandomReward": {
  "Enabled": true,
  "Tiers": [
    {
      "Chance": 20.0,
      "WaitForPlayer": true,
      "Commands": ["give %player% Rare_Crate --quantity 1"],
      "PlayerMessages": ["<aqua>你获得了一个 <white>稀有</white> 宝箱!</aqua>"]
    },
    {
      "Chance": 10.0,
      "WaitForPlayer": true,
      "Commands": ["give %player% Legendary_Crate --quantity 1"],
      "PlayerMessages": ["<gold>你获得了一个 <white>传说</white> 宝箱!</gold>"],
      "BroadcastMessages": ["<gold>%player%</gold> <gray>发现了一个</gray> <gold>传说</gold> <gray>宝箱!</gray>"]
    }
  ]
}

每个层级都支持与奖励组相同的选项:Chance(权重)、WaitForPlayerPermissionCommandsBroadcastMessagesPlayerMessagesServices

带有 Permission 要求的层级会从缺少该权限的玩家的随机池中排除——其余层级会在不受限制的层级中随机选择。

🏷️ 占位符

在指令和消息中可使用以下占位符(同时支持 %placeholder%{placeholder} 格式):

  • %player% - 投票玩家的用户名
  • %service% - 投票服务的名称
  • %address% - 投票者的 IP 地址
  • %timestamp% - 投票的 Unix 时间戳

🔔 notification.json

配置玩家投票时收到的通知。支持多种通知类型。

image

{
  "Title": {
    "Enabled": false,
    "Title": "<green>奖励已领取!</green>",
    "Subtitle": "<gray>感谢你在</gray> <aqua>%service%</aqua><gray>上投票!</gray>",
    "DurationSeconds": 3,
    "FadeInSeconds": 0.5,
    "FadeOutSeconds": 0.5
  },
  "Notification": {
    "Enabled": true,
    "Title": "<green>奖励已领取!</green>",
    "Description": "<gray>感谢你在</gray> <aqua>%service%</aqua><gray>上投票!</gray>",
    "IconItem": "Rock_Gem_Diamond"
  },
  "Chat": {
    "Enabled": false,
    "Messages": [
      "<green>奖励已领取!</green>",
      "<gray>感谢你在</gray> <aqua>%service%</aqua><gray>上投票!</gray>"
    ]
  },
  "Sound": {
    "Enabled": true,
    "Name": "SFX_Player_Pickup_Item"
  }
}

标题选项(屏幕中央大字)

  • Enabled - 在屏幕上显示标题(true/false,默认:false)
  • Title - 主标题文字,支持颜色和占位符(字符串)
  • Subtitle - 标题下方的副标题文字(字符串)
  • DurationSeconds - 标题停留在屏幕上的时长(整数)
  • FadeInSeconds - 淡入时长(浮点数)
  • FadeOutSeconds - 淡出时长(浮点数)

通知选项(小型弹出窗口)

  • Enabled - 显示通知弹窗(true/false,默认:true)
  • Title - 通知标题,支持颜色和占位符(字符串)
  • Description - 通知描述,支持颜色和占位符(字符串)
  • IconItem - 要显示的物品图标(字符串)

聊天选项

  • Enabled - 向投票者的聊天栏发送消息(true/false,默认:false)
  • Messages - 要发送的消息数组,支持颜色和占位符(字符串数组)

音效选项

  • Enabled - 投票时播放音效(true/false,默认:true)
  • Name - 音效事件名称,例如 SFX_Player_Pickup_Item(字符串)

🔗 voteCommand.json

配置 /vote 指令和投票网站链接。你可以切换启用 UI、聊天或两者同时启用。

image

image

{
  "Enabled": true,
  "VoteLinks": {
    "Hytalist.com": "<a href=\"/linkout?remoteUrl=https%253a%252f%252fhytalist.com%252ftest-server%253fnick%253d%252525player%252525\" target=\"_blank\" rel=\"nofollow\">https://hytalist.com/test-server?nick=%player%</a>",
    "AnotherServerList": "<a href=\"/linkout?remoteUrl=https%253a%252f%252fexample.com%252f\" target=\"_blank\" rel=\"nofollow\">https://example.com</a>"
  },
  "UI": {
    "Enabled": true,
    "Title": "为服务器投票",
    "VoteLinkTitle": "投票链接(复制并在浏览器中打开)",
    "VoteLinkDescription": "点击上方的网站查看投票链接"
  },
  "Chat": {
    "Enabled": true,
    "Header": "<gray>========== <green>投票链接</green> ==========</gray>",
    "Format": "<yellow>#[</yellow><aqua>%iterator%</aqua><yellow>]</yellow> <white><click:%siteUrl%>%siteUrl%</click></white>",
    "Footer": "<gray>===================================</gray>"
  }
}

image

选项

  • Enabled - 完全启用/禁用 /vote 指令(true/false)
  • VoteLinks - 网站名称到投票 URL 的映射(对象)

UI 选项

  • Enabled - 显示投票 GUI(true/false)
  • Title - GUI 顶部显示的标题(字符串)
  • VoteLinkTitle - URL 文本字段上方的标签(字符串)
  • VoteLinkDescription - 选择前的 URL 字段占位符文本(字符串)

聊天选项

  • Enabled - 将投票链接发送到聊天栏(true/false)
  • Header - 链接前显示的消息,支持颜色(字符串)
  • Format - 每个链接的格式,包含占位符:%iterator%(从 1 开始的数字)、%siteUrl%%siteName%(字符串)
  • Footer - 链接后显示的消息,支持颜色(字符串)

URL 中的 %player% 占位符将被替换为玩家的用户名(同时支持 %player%{player} 格式)。


🏆 voteTopCommand.json

配置 /votetop 指令排行榜。你可以切换启用 UI、聊天或两者同时启用(UI 默认启用)。

image

{
  "Enabled": true,
  "UI": {
    "Enabled": true,
    "Limit": 25,
    "Title": "投票冠军",
    "MonthlyTitle": "本月",
    "PreviousMonthTitle": "上月",
    "AllTimeTitle": "全时段",
    "YourRankLabel": "你的排名",
    "NoVotesYet": "暂无投票记录",
    "NoRankYet": "去投票以获得排名!",
    "PlayerDescription": "本月 %votesThisMonth% 票 / 总计 %votes% 票"
  },
  "Chat": {
    "Enabled": false,
    "Limit": 10,
    "Header": "<gray>=========</gray> <green>投票冠军</green> <gray>(%period%)</gray> <gray>=========</gray>",
    "Format": "<yellow>#%rank%</yellow> <aqua>%player%</aqua> <gray>(票数:%votes%)</gray>",
    "Footer": "<gray>-----------------------------------------</gray>",
    "YourRankFormat": "<gray>你的排名:</gray> <aqua>#%rank%</aqua> <gray>(票数:%votes%)</gray>",
    "NoVotesYet": "<gray>暂无投票记录。</gray>",
    "NoRankYet": "<gray>你的排名:</gray> <dark_gray>尚未投票</dark_gray>"
  }
}

选项

  • Enabled - 完全启用/禁用 /votetop 指令(true/false)

UI 选项

  • Enabled - 显示投票排行 GUI,包含 3 个标签页:本月、上月、全时段(true/false,默认:true)
  • Limit - 显示的最大投票冠军数量(整数,默认:25)
  • Title - GUI 顶部显示的标题(字符串)
  • MonthlyTitle - 当前月份标签页的标签(字符串)
  • PreviousMonthTitle - 上个月标签页的标签(字符串)
  • AllTimeTitle - 全时段标签页的标签(字符串)
  • YourRankLabel - 玩家排名上方的标签(字符串)
  • NoVotesYet - 暂无投票记录时显示的消息(字符串)
  • NoRankYet - 玩家无排名时显示的消息(字符串)
  • PlayerDescription - 每个玩家名称下方显示的描述,包含占位符:%votesThisMonth%%votes%(字符串)

聊天选项

  • Enabled - 将排行榜发送到聊天栏(true/false,默认:false)
  • Limit - 显示的最大投票冠军数量(整数,默认:10)
  • Header - 头部消息,包含 %period% 占位符(例如 "2025 年 1 月" 或 "全时段")(字符串)
  • Format - 每个排行条目的格式,包含占位符:%rank%%player%%votes%(字符串)
  • Footer - 底部消息(字符串)
  • YourRankFormat - 显示玩家自身排名的格式(字符串)
  • NoVotesYet - 暂无投票记录时显示的消息(字符串)
  • NoRankYet - 玩家无排名时显示的消息(字符串)

⏰ reminder.json

配置针对近期未投票玩家的投票提醒。

image

image

{
  "Enabled": true,
  "AfterHours": 24,
  "DelayInSeconds": 60,
  "CheckIntervalSeconds": 60,
  "Title": {
    "Enabled": true,
    "Title": "投票提醒",
    "Subtitle": "使用 /vote 支持服务器!",
    "DurationSeconds": 3,
    "FadeInSeconds": 0.5,
    "FadeOutSeconds": 0.5
  },
  "Notification": {
    "Enabled": false,
    "Title": "<gold>投票提醒</gold>",
    "Description": "<gray>你很久没投票了!</gray> <aqua>使用 /vote</aqua>",
    "IconItem": "Rock_Gem_Diamond"
  },
  "Chat": {
    "Enabled": false,
    "Messages": [
      "<gold>你很久没投票了!</gold>",
      "<gray>通过投票支持服务器:</gray> <aqua>/vote</aqua>"
    ]
  },
  "Sound": {
    "Enabled": true,
    "Name": "SFX_UI_Quest_Objective_Complete"
  }
}

选项

  • Enabled - 启用/禁用投票提醒(true/false)
  • AfterHours - 提醒 X 小时内未投票的玩家(整数,默认:24)
  • DelayInSeconds - 玩家加入后等待 X 秒再进行检查(整数,默认:60)
  • CheckIntervalSeconds - 检查提醒的频率(整数,默认:60)

标题选项(屏幕中央大字)

  • Enabled - 在屏幕上显示标题(true/false,默认:true)
  • Title - 主标题文字(字符串)
  • Subtitle - 标题下方的副标题文字(字符串)
  • DurationSeconds - 标题停留在屏幕上的时长(整数)
  • FadeInSeconds - 淡入时长(浮点数)
  • FadeOutSeconds - 淡出时长(浮点数)

通知选项(小型弹出窗口)

  • Enabled - 显示通知弹窗(true/false,默认:false)
  • Title - 通知标题,支持颜色(字符串)
  • Description - 通知描述,支持颜色(字符串)
  • IconItem - 要显示的物品图标(字符串)

聊天选项

  • Enabled - 向聊天栏发送提醒消息(true/false,默认:false)
  • Messages - 要发送的消息数组(字符串数组)

音效选项

  • Enabled - 提醒时播放音效(true/false,默认:true)
  • Name - 音效事件名称(字符串)

📡 forwarding.json

将投票转发至网络中的其他服务器。适用于 BungeeCord/Velocity 风格的设置,例如将在代理服务器上收到的投票转发到后端服务器,或反向操作。

{
  "Enabled": false,
  "Targets": [
    {
      "Host": "127.0.0.1",
      "Port": 8192,
      "Token": "target-server-token"
    },
    {
      "Host": "192.168.1.100",
      "Port": 8192,
      "Token": "another-server-token"
    }
  ]
}

选项

  • Enabled - 启用/禁用投票转发(true/false,默认:false)
  • Targets - 要转发投票的目标服务器数组

目标选项

  • Host - 目标服务器的 IP 地址或主机名(字符串)
  • Port - 目标服务器的 Votifier 端口(整数,默认:8192)
  • Token - 目标服务器的 Votifier V2 令牌(字符串)

工作原理

  1. HyVote 收到投票后,会在本地处理(奖励、广播等)
  2. 如果启用了转发,它会将投票发送到所有配置的目标
  3. 转发使用 Votifier V2 协议和 HMAC-SHA256 认证
  4. 每个目标服务器会像直接从投票网站收到投票一样处理该投票
  5. 转发是异步进行的,不会拖慢本地投票处理

示例用例

  • 代理 → 后端:在代理服务器上接收投票并转发到游戏服务器
  • 枢纽 → 游戏服务器:从枢纽服务器将投票转发到多个游戏模式服务器
  • 冗余:将投票转发到备份服务器以进行日志记录/统计

注意: 目标服务器必须运行 HyVote(或其他兼容 Votifier V2 的插件)。确保令牌与目标服务器的配置匹配。


📊 投票统计

HyVote 在 database/ 文件夹中自动追踪投票统计信息:

  • votes.json - 存储每个玩家的总投票数(全时段)
  • votes-YYYY-MM.json - 月度投票文件(例如 votes-2025-01.json
  • pending_rewards.json - 离线玩家的待领取奖励队列(3 天后自动清理)

这些数据用于:

  • /votetop 指令显示月度排行榜
  • /votetop all 指令显示全时段排行榜
  • /vote UI 菜单中显示的前 3 名投票者
  • 排行榜底部显示的玩家排名

旧的月度文件会保留作为历史记录,如果不需要可以安全删除。


🌐 设置投票网站

针对 Votifier V2(推荐)

  1. 获取你的令牌:从 config.json 中获取(首次运行时自动生成)
  2. 配置你的服务器列表:填写以下信息
    • IP/主机: 你的服务器 IP
    • 端口: 8192(或你配置的端口)
    • 令牌: 从 config.json 复制
    • 协议: Votifier V2

针对 Votifier V1(旧版)

  1. 获取你的公钥:从 rsa/public.key 获取(首次运行时自动生成)

  2. 配置你的服务器列表:填写以下信息

    • IP/主机: 你的服务器 IP
    • 端口: 8192(与 V2 相同端口)
    • 公钥: 复制 public.key 中的全部内容
    • 协议: Votifier V1
  3. 测试设置:启用 DebugMode 并进行投票


🔧 故障排除

无法收到投票

  1. ✅ 在 config.json 中启用 DebugMode
  2. ✅ 检查防火墙——确保 Votifier 端口已开放
  3. ✅ 验证配置和投票网站之间的令牌是否匹配
  4. ✅ 检查服务器日志中是否包含 HyVote 消息

常见问题

  • "无效签名" (V2) - 令牌不匹配。如果投票网站生成了自己的令牌,将其添加到 config.json 的 CustomTokens
  • "解密失败" (V1) - 公钥不匹配,复制 rsa/public.key 中的全部内容
  • "无响应" - 检查防火墙/端口配置

👨‍💻 面向开发者

完整的 API 文档可在 github.com/hytalist/hyvote-api 获取。该 API 允许你检索玩家投票、月度统计、排名等信息。

安装

将 HyVote API 添加到你的项目:

Gradle (Kotlin DSL)

repositories {
    maven { url = uri("<a href=\"/linkout?remoteUrl=https%253a%252f%252fjitpack.io%252f\" target=\"_blank\" rel=\"nofollow\">https://jitpack.io</a>") }
}

dependencies {
    compileOnly("com.github.hytalist:hyvote-api:1.6.0")
}

Gradle (Groovy)

repositories {
    maven { url '<a href=\"/linkout?remoteUrl=https%253a%252f%252fjitpack.io%2527\" target=\"_blank\" rel=\"nofollow\">https://jitpack.io&#x27;</a> }
}

dependencies {
    compileOnly 'com.github.hytalist:hyvote-api:1.6.0'
}

Maven

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url><a href="/linkout?remoteUrl=https%253a%252f%252fjitpack.io%253c%252furl%253e" target="_blank" rel="nofollow">https://jitpack.io&amp;lt;/url&amp;gt;</a>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>com.github.hytalist</groupId>
        <artifactId>hyvote-api</artifactId>
        <version>1.6.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

VoteReceivedEvent

监听来自其他插件的投票事件:

Java

import com.hytalist.hyvote.event.VoteReceivedEvent;
import com.hytalist.hyvote.model.Vote;

@Override
public void setup() {
    eventRegistry.registerGlobal(VoteReceivedEvent.class, event -> {
        Vote vote = event.getVote();
        String player = vote.getUsername();
        String service = vote.getServiceName();

        // 取消默认奖励
        event.setCancelled(true);

        // 使用自定义逻辑处理
    });
}

Kotlin

import com.hytalist.hyvote.event.VoteReceivedEvent

override fun setup() {
    eventRegistry.registerGlobal(VoteReceivedEvent::class.java) { event ->
        val vote = event.vote
        val player = vote.username
        val service = vote.serviceName

        // 取消默认奖励
        event.setCancelled(true)

        // 使用自定义逻辑处理
    }
}

🙏 致谢

  • 协议基于 NuVotifier 规范
  • 颜色格式化由 TaleMessage 驱动

为 Hytale 社区倾情制作 ❤️