摘要
为防止实体丢失开发的监控和找回模块。
关于跨 tick 和一些特性的碎碎念。
发现了通过 execute on origin 可以找到已卸载的实体,这一方法,并进行了简单测试。
测试结果为:至少可以找到五分钟之前卸载的实体,不能在实体已卸载时变更 origin,只能提前锁定。
前言
在数据包的开发中,鸽子们总会担心自己的世界实体出现意外而丢失。譬如被管理员的 kill 批量清除譬如因铸币操作而 tp 到不知道什么犄角旮旯的未加载区块,譬如实体跟随玩家进入某区块后,却因玩家离开而被卸载。
实体意料外进入未加载区块导致无法找到和召唤的问题,鸽子院称之为「区块安全问题」或「泄露问题」。
如果只是被删除,则倒还好,重新召唤即可。但若是被 tp 到未加载区块去,便难以找回,又因 UUID 重复而无法召唤,整个系统便面临着恶性 bug 的威胁。
(虽然可以通过大退来重置UUID表,进而允许重新召唤)
为此,我为我的前置「虚空数据核心」编写了『寻回犬』模块,用于监视和找回重要实体。包括但不限于世界实体、存储重要信息的实体、特殊NPC、宠物等等。
使用教程及功能介绍
监控标记函数(视奸函数)
令被监视实体执行下列函数,即可使其受到监控。
function vdc:built_in/retriever/_monitor
受到监控的实体,坐标信息(不包括维度)会持续记录在记分板中。但仅追踪是无法监测到突发的坐标变动的,需要配合下面的函数使用。
该函数会为执行者创建随机 id /* scb(xid) */ ,将其 UUID 数组转录为记分项,并维护一份数组UUID和十六进制字符串UUID的映射表。
映射表形如
storage vdc:sys retriever[{array:[I;0,0,0,1] ,str:"0-0-0-0-1"}]
病态视奸函数
监视器函数
只需要付出一点额外的性能,就可以让你的目标得到更强有力的视奸监视看护。
让被监视对象执行
function vdc:built_in/retriever/_watcher.creating
即可创建一个专属的监视器。这是一个位于坐标原点的末影珍珠。
当监视器发现目标丢失时,就会执行应急预案。
在执行这个函数前更改目标的 vdc.rtvr.plan 计划,即可修改应急方案。
不同的值对应不同的预案。
- [NULL] - 默认方案,记录最后的坐标后销毁监视器。
- [1] - 强制加载。这个预案会在实体进入未加载区块时强制加载该区块。
// 如果实体一路乱飞,可能会强制加载一大堆区块。好在它还会记录有哪些区块被临时强加载了,方便清理。
- [-1] - 执行动态命令。譬如将实体 tp 回出生点之类的。
// 要执行动态命令,需要将命令记录在映射表里。
// 如果你刚刚标记了这个实体,那么路径为 storage vdc:sys retriever-map[-1].cmd。
// 执行者为监控目标,执行坐标为世界出生点。
- [-2] - 同上,但是执行完动态命令后会销毁监视器。
寻回函数
提供目标的 UUID 数组、执行模式等参数,然后这个函数会将目标所在的区块强制加载出来。
将参数写在路径 storage _ import ,再执行命令,数刻后区块加载完成,就能找到目标了。
参数形如
storage _ import{target:[I;0,0,0,1], mode:0, cmd:"say coo-co cola"}
模式代码对应操作:
- [0] - 仅寻回(找到区块后更新坐标分数并立即卸载区块)
- [1] - 寻回并执行动态命令
- [2] - 寻回,执行动态命令且不卸载区块。
幕后杂谈
寻回函数 与 跨 tick 操作
写数据包的时候,偶尔会遇到需要跨 tick 进行的操作。
在某次更新之后,/forceload 不再是即时操作了。也就是末影珍珠能加载区块的那个版本。
同时,需要进行的操作往往可能是并行的,譬如同时要找回复数个实体。
然而,跨 tick 后,不仅执行者等上下文的保存是个麻烦事。经历了重重判定后决定要执行的命令,要如何传递到数tick之后?在稍旧些的版本,是用命令方块来执行动态命令,然而命令方块的修改和触发本身又是个跨 tick(或者说跨越主循环的)操作。更早些的版本,则只能传递一些信号,再一个个判断。
现在嘛,只需要创建一个函数就可以传递命令了。里面只需要写上 $$(cmd) ,就可以把字符串转化为命令执行。
总之,我的做法是延续了之前编写的任务系统的形式,将执行者、执行模式、ID、动态命令等等参数编写成一个表格。这是一个复合标签列表,或者按 mcfpp 的说法,叫 map。
区块的加载时间是不固定的(取决于服务器性能,一般 跨 1,2tick。),所以和作为强化版 schedule 的任务系统不同,这里的每一行表格都需要执行一遍,检测一下对应的区块有没有加载。一个简单的递归。
恕我跟不上时代,我上一个写完的坑还是 1.20.2 的 meow,所以这里我要再感慨一下 return 能在中间断开函数,少了一半执行量,太优美了。
时序 和 监视器 和 抓取世界外实体
本来,寻回犬写到寻回函数就没有思路了,算是收工了。但没法有效解决泄露问题,价值简直少了一多半。但是我临截稿死线时突然想到,能不能用自定义魔咒的「执行函数」效果,在作为主进程的
#tick 函数之后,区块卸载之前,卡一个 tick 内时间差来把实体拉回来呢?毕竟我们知道,tick 函数总是在每一刻的最开始执行。而以往不能抓到区块外实体,主要是因为只有 @s 能选到世界外的非玩家实体。那让实体通过魔咒自己执行函数,继而在主进程之外,误操作之后区块卸载之前这样一个微妙的时间差中使用 @s 选到实体来将其抓回来呢?
想到就试,然后发现不行。或许是因为超出世界后加载等级太低,实体就不演算了,而魔咒是在实体演算的时候执行的;或许是区块卸载在演算之前。后又试了试 schedule能不能打时间差,发现也不能。于是又想到,末影珍珠主人改成目标实体,落地触发的时候能不能把实体带回来?也不行。
就在打算放弃的时候,突然发现测试用的凋零第一次在 tp 后被 tp 回来了。
一开始还以为是魔咒或 schedule 的功劳。测试了一会儿,发现居然是 on origin 能够抓到区块外实体!又测试了一会儿,发现即使经过五分钟,仍然能寻回目标实体。掉落物实体的 origin 也可以,不是凋零也可以。但,在实体移动到区块外之后再改 Owner 或 Thrower ,就来不及了。不可以在泄露后再指定实体。另外,大退(重启服务器)之后也是不行的。
总之,找到了一个在时间之外、把世界之外的实体拉回世界内的方法。(不过没有测试维度安全问题。)唯一的问题是,需要留一个实体专门监测目标,增加了性能负担。这也是我专门设置「监视器」实体,并将其设为默认不启用的原因之一。
但如果要增加额外的实体,那么直接让末影珍珠骑乘在实体身上不也能解决问题,而且还不用进行复杂的判定。这也是我的思路之一,即 monitor.sick 病态视奸模式。这个模式虽然写了但是还没测试,也没留接口。(虽然就是普通的_monitor函数再加个 tag add vdc.rtvr.monit.sick 的事儿。)
另一个方案是,利用世界中自然存在的掉落物等实体来对目标进行监测,自然实体不足时再补充专用监视器。这个方案因为有可能会污染正常的游戏流程(比如用弓箭击杀时的报幕和统计错误。),所以函数名是 watching.dirty 。是用影响相对最低的掉落物来做的。
事实上我一开始写的就是这个方案,不过最后没投入测试,有些懒。毕竟核心的特性和函数写好了,这些优化也就是细枝末节而已。
其他
这是第一次投稿 Feature。从第一期出之前答应投稿到现在,鸽了四个月。
如果有从香草图书馆摸到过工坊论坛的读者,希望不要因为之前从摆在上面的文章链接摸进来发现写一半鸽了而打我,就这样(跑)