跳到主要内容

防复制设计

背包类插件常见的复制物品方式有三种:两个子服同时打开同一个背包、存档前一刻当机、点击太快造成前后两次操作重叠。 星月行囊针对每一种都有对应的设计,而且默认就开着,不需要设置。

一个行囊只在一个地方使用​

打开行囊时,子服在数据库里拿下这个行囊的 租约(谁在用、用到什么时候),读取时用 SELECT … FOR UPDATE 锁住那一行,两个子服不可能同时拿到。 开着的子服每隔几秒续约一次;关闭行囊几秒后(或玩家下线时)立刻还回去。另一个子服在租约有效期间打开,会看到「你的行囊正在子服 xxx 使用中」。

子服当机时租约不会还:最多等 server.lease-seconds(默认 30 秒)就自动失效;那个子服重新启动时也会立刻释放自己留下的租约。

每一步都是一笔交易​

行囊菜单里的每一次点击都会被拦下,由星月行囊自己算出结果,然后:

  1. 先拿走 要离开玩家身上的东西(例如要存进行囊的那一组),要给玩家的东西先不给;
  2. 把改到的行囊格子写进数据库,条件是 revision(版本号)没有变、租约还是自己的;
  3. 数据库收下了,才把东西给玩家;数据库拒绝了,就把第 1 步拿走的东西原样还回去。

整个过程行囊处于「存档中」,任何其他点击都会被忽略,所以快速点击、连点外挂都无法让两次操作重叠。 每一步只写改到的那几格,几百格的大行囊也一样快。

答复遗失时不猜​

如果数据库在提交的那一瞬间断线,插件不知道这一步到底存进去了没有。星月行囊 不会猜:每一步都带一个随机编号, 断线后用这个编号去问数据库,确认之前行囊保持锁住、东西既不给也不退。玩家这时下线,确认结果后物品会放回行囊(或下次进服发还)。

当机后自动比对​

最危险的情况:玩家把钻石存进行囊(数据库已收下),服务器在存玩家资料之前当机。重开后玩家资料回到较早的存档 —— 钻石还在身上,行囊里也有一份。

星月行囊在每一步存档时,同时在数据库记下「玩家这边多了 / 少了什么」并给一个序号,玩家资料里也记下「我已经包含到第几步」。 玩家下次进服时比对两边:数据库里有、玩家资料却没包含的那几步,而且是 由当机的那次运行做的,就自动补做 ——

  • 当时存进行囊的:从玩家身上收回(身上没有了就从行囊收回);
  • 当时从行囊取出的:再给玩家一次(放不下就放回行囊)。

玩家会收到一句提示,后台也会写一行记录。正常关服、正常下线的情况不会补做任何东西。详细的判断方式与同步插件的设置见 当机保护。

实测​

发布前的自动化测试(Paper 1.21.11 与 26.3):

测试结果
存入后直接结束服务器进程(玩家资料没存),重开进服身上没有多出来,行囊里有一份
取出后直接结束服务器进程,重开进服物品补回身上,行囊是空的,只有一份
再正常进出一次不会重复补做
40 次各种点击连打物品总数不变
A 服开着行囊,B 服同时打开B 服被拒绝;A 服关闭后 B 服打开,内容相同
用 /item replace 复制一张已用完的扩充卷被拒绝并没收