服务器升到 26.1.2,我们给服里塞了一个会说话的幽灵
一天之内完成跨十个版本的核心升级、十五个插件更新,还自己写了一个没有实体的 AI 插件并开源。这是完整的过程记录,包括踩的坑。
今天做完的事
一天里做完了四件事:把服务器从 1.21.1 升到 26.1.2、更新了十五个插件、上线一个新玩法(全版本客户端兼容)、以及自己写了一个 AI 插件并开源。
下面按做的顺序说,重点放在取舍和踩坑上,因为那些才是真正花时间的地方。
一、跨十个版本的核心升级
我们的服务器从 2024 年开服起一直跑在 Purpur 1.21.1 上。这次直接跳到 26.1.2,中间跨了十来个版本,理由是内容积压太久了——铜器时代、坐骑与长矛、幼崽更新,玩家能玩到的东西一直停在上个时代。
为什么不上最新的 26.2?因为 EssentialsX 和 CoreProtect 还没跟上。前者是基础指令套件,后者是防破坏回滚,缺一个都很难受。26.1.2 已经包含到今年三月的全部内容,只差最新一版,等这两个插件发版再走一小步就行。
第一个坑:内存参数写死在启动脚本里。 原来的启动命令硬编码了 -Xms8G -Xmx16G,而实例的内存上限是 16G、cgroup 限制 16.8G。也就是说服务端一旦用满,会被系统直接强杀,而且初始就占 8G 完全没必要。改成按平台给的 SERVER_MEMORY 变量动态计算、预留 1G 给系统。
第二个坑:面板是用 sh 执行启动脚本的。 我一开始用了 bash 的数组语法写启动参数,服务器直接报 Syntax error: "(" unexpected。改成纯 POSIX 写法后正常。
第三个坑,也是最不可逆的一个:世界存档格式变了。 新版本把存档结构整体换掉,原来根目录下的 world_nether、world_the_end 会被合并进 world/dimensions/minecraft/ 下面。升级时服务端会先警告”需要迁移、没有备份请立刻中断”,然后倒计时三十秒开始转换。这一步单向不可逆——一旦转过去,退回旧版本就读不了档了。所以升级前先在面板做了完整备份。
二、插件大扫除
更新了十五个插件,其中有几个是必须换的:
ChestCommands 已经停更五年(最后更新停在 2020 年,api-version 还写着 1.13)。它提供的菜单功能换成了还在维护的 DeluxeMenus。
CMILib 引发了一次连锁故障。 新版服务端启动后,CMILib 不认识 26.1.2 这个版本号,初始化时空指针,导致依赖它的领地插件 Residence 整个加载失败——领地可是玩家的财产保障,不能挂。作者只在自家站点提供下载(Spigot 有防护挡着),最后从官网下载目录拿到了新版本,问题解决。这条经验值得记:插件的依赖链比插件本身更容易出事。
另外装了 ViaVersion 三件套,效果是1.7 到 26.1 的任何客户端都能连进来。国内玩家版本参差不齐,有人懒得升级启动器、有人用整合包、有人习惯了手机版的操作,装了它之后这些都不再是门槛。
顺带把最大在线人数从 20 提到了 30。
三、给服务器塞一个幽灵
这是我们第一次自己做插件:一个住在服务器里的 AI,玩家叫它”幽灵”。
它没有实体。 很多 AI 同伴类插件会生成一个悬浮的球或脑袋跟在玩家后面,我们不要那个——它只通过聊天、音效、屏幕标题和少量白名单指令存在。你在世界里找不到它,但它知道你在干什么。
它的性格是”偶尔帮忙,时常搞怪”。 有人在线时它会隔一段时间在公屏上冒一句嘴;台词库有一百一十句,分五类:阴森、嘴贫、观察玩家、自言自语,以及占五分之一的实用提示(比如”床在下界会炸,别试了”)。真正需要它的时候,它会正经回答问题。
为什么只有被 @ 才回复。 这是成本设计。如果它监听所有聊天再决定要不要接话,每一次判断都要调用一次大模型,玩家聊得越热闹烧得越快。所以我们把规则定死:
- 平时自己冒话:大概率念本地台词,小概率用 AI
- 被
@yl或@幽灵点名:必定调用 AI - 其他所有聊天:完全不检测,零成本
它在公屏说完话后会盯着接下来九十秒里的第一条玩家发言,只要有人接话,就丢一句固定文案告诉对方”想跟我说话直接 @yl”,然后关掉窗口。这样既能把玩家引导到低成本的交互方式,又不会持续监听。
安全做了两层。 模型能触发三类动作:放音效、闪标题、执行指令。第一层是配置里的白名单——音效只能在十个里选,指令只能以 me、say、title、playsound、weather、time 开头。第二层是代码里的硬拦截,op、ban、kick、stop、tp、give、fill、setblock 这些无论配置怎么写都执行不了。把控制台指令交给一个大模型是很危险的事,必须假设它会被诱导。
四、开源,以及运维方式的变化
插件开源了,在 github.com/caicaicat123/mcbot,配置模板里 API Key 是占位符,密钥只存在服务器上。项目里没有任何第三方依赖——JSON 解析是手写的,编译只需要 Paper API。
今天还顺手改了一件事:这台服务器原来只能靠面板手动操作,改个配置要开网页点半天。我们在实例里装了一个自己写的桥接插件,配合 RCON 拿到了一条命令通道——等于把运维从”手工点击”变成了”可编程”。上面那些升级、插件替换、故障排查,全是通过这条通道完成的。
五、几条踩出来的硬知识
写下来给未来的自己:
换 jar 必须重启,改配置才能热重载。 我们用 PlugManX 尝试热更新插件,失败在 Paper 的一行报错上:它不允许加载与已注册插件同名的插件,即使旧的那个已经被卸载。热重载只适合改配置,换版本代码没有捷径。
YAML 里的中文引号是雷。 台词里写了一句「每次有人说”马上就来”,我都会等一会儿」,里面那对引号直接把配置文件的语法搞崩。改成「」才对。
Set.of() 遇到重复元素会在类初始化时抛异常。 我在硬拦截清单里把 restart 写了两遍,结果插件每次想说话都崩一次——它被自己的安全清单绊倒了。
偶发网络超时要有重试。 第一次被玩家点名时 API 连接超时了,插件退回念本地台词。查了半天网络,最后发现网络完全正常(响应三百毫秒),纯粹是一次抖动。现在已经加上重试,并且在开机时自动做一次连通性检查,省得等玩家点了名才发现连不上。
接下来
短期三件事:把权限体系重新梳理一遍(现在只有”默认”和”服主”两个组,管理员的权限其实全靠 OP,这是个隐患)、把官网信息同步到最新、然后开始按计划往社区渠道推。
中期想试试基岩版互通,让手机玩家也能进来。长期还是那句话——把玩法做深,而不是把人数做大。
断了一只爪子的招财猫 2026 年 9 月 12 日