关键词:模块.方法,URL,sessionID,SaveServer.profiles,.hasOwnProperty()
我们的逃离塔科夫离线单机版服务端MOD,如果仅限于对内存中的数据进行直接修改,那么多样性与可玩性将十分有限。如果我们能通过MOD改写服务端中已有的某些方法,改变它们对数据的处理流程或者处理规则,那么将会产生更多新的玩法。这里举两种替换方法的实例,来演示我们如何在MOD中改写服务端中已有的方法。
实例一:我们通过 A模块.方法 = B模块.方法 的方式来改写方法。有人曾向我反映说:“逃离塔科夫线上虽然各种天气都有,但雾天绝对不会像逃离塔科夫离线版一样频繁。我喜欢各种天气都有,但是不喜欢雾天过于频繁。”显然,如果我们只用DNSPY编辑服务端中关于天气部分的配置文件,是并没有办法做到降低雾天的频率的。因为我们只能通过把天气配置中雾天的数值改为0来禁止雾天出现。那么如何才能做到让逃离塔科夫离线单机版雾天以较低的频率出现而不是直接禁止它呢?这时候改写服务端方法就派上用场了。我们打开服务端源码目录/controllers/WeatherController.js,其中generateWeather()方法是生成天气这一功能的核心方法之一。我们把这个方法连同方法名复制粘贴到framework.js中,同时我们在// Code to be added下添加代码
WeatherController.generateWeather = Framework.generateWeather;
如下:
由此一来便完成了对WeatherController.generateWeather()这个方法的替换,接下来便是创造过程。需要注意的是,创造不存在的规则,在制作逃离塔科夫离线单机版MOD的流程当中非常重要。如果所制作的MOD的最终效果完全是修改数据文件可以直接达到的,那么MOD便失去了存在的必要和意义。我们把目光集中在data.weather.fog这一行上。可以看出,雾天是否产生是由一个Boolean类型的值enableFog控制的。我们查看enableFog的定义,发现如下:
const enableFog = RandomUtil.getBool();
在前文中我们提过,RandomUtil.getBool()方法的作用是用来生成一个随机的Boolean类型的值,其中true与false几率各占50%,换言之平均每两局游戏就有一局是雾天,这正是逃离塔科夫离线版雾天几率如此之高的原因。了解原因后,我们对enableFog的定义进行改写,如下:
const enableFog = Math.random() < 0.1;
显然,表达式enableFog = Math.random() < 0.1的结果,在统计学上有10%的概率为true,90%的概率为false。保存文件并运行逃离塔科夫离线单机版服务端进入游戏,发现平均每十局游戏只会出现一局是雾天天气。雾天虽然没有彻底被禁止,但再也不会频繁出现了。
实例二:直接改写某个URL对应的处理方法。很多人喜欢随意安装与删除各种新增武器的MOD,这些MOD中的物品如果在MOD被删除后依然残留于玩家的仓库中,某些物品的特殊属性就会导致服务端报知错,如知名错误“Cannot read property ‘Foldable’ of undefined”。要想解决这个问题,我们可以在玩家登陆成功后检测仓库中是否含有内存数据中所不存在的物品,如果有则删除。首先运行服务端,然后使用启动器登录,在服务端我们可以看到记录信息如下:
容易看到,其中/launcher/profile/info这个URL前面出现了玩家ID。打开塔科夫服务端源码目录/bindings/StaticRoutes.js,搜索/launcher/profile/info,我们看到其对应的处理方法是LauncherCallbacks模块中的getMiniProfile()。我们找到对应的方法,依旧把这个方法连同方法名复制粘贴到framework.js中,同时在// Code to be added下添加代码
HttpRouter.onStaticRoute[“/launcher/profile/info”].aki = Framework.getMiniProfile;
然后开始创造过程。首先我们需要了解,逃离塔科夫离线单机版的服务端启动后,存档会被加载到SaveServer模块的profiles方法中,以对象的形式存在:
profiles: {
”玩家1ID”: {存档数据},
”玩家2ID”: {存档数据},
”玩家3ID”: {存档数据},
…
”玩家NID”: {存档数据}
}
我们的目标是访问、修改和保存已登录逃离塔科夫离线单机版的玩家的存档数据。前文中我们已知/launcher/profile/info这个URL前面出现了玩家ID,观察它对应的处理方法getMiniProfile()所包含的参数:url、info、sessionID,显然url与info并没有被用到,sessionID的值无法确定。我们首先尝试改写这一方法,在其中添加代码:
Logger.info(`url:${url},info${info},sessionID${sessionID}`);
保存文件,运行逃离塔科夫离线版的服务端并使用启动器登录,发现结果如下:
显然sessionID正是我们要找的玩家ID。然后我们便可以利用SaveServer.profiles[sessionID]访问SaveServer模块中profiles方法下当前已登陆玩家的存档数据了。我们首先打开user/profiles中的任意一个存档文件,观察内部的数据结构,不难发现其中玩家物品数据储存于characters.pmc.Inventory.Items和characters.scav.Inventory.Items两个数组中,我们要做的正是检查这两个数组中的物品是否都存在。这里我们需要用到遍历,在
Logger.info(`url:${url},info${info},sessionID${sessionID}`);
下另起新行,输入:
const cleanedItems = [];
try {
for (let character in SaveServer.profiles[sessionID].characters) {
for (let item of SaveServer.profiles[sessionID].characters[character].Inventory.items) {
if(DatabaseServer.tables.templates.items.hasOwnProperty(item._tpl))
cleanedItems.push(item);
else
Logger.error(“Unable to find item: ” + item._tpl + “, item deleted.”);
}
SaveServer.profiles[sessionID].characters[character].Inventory.items = cleanedItems;
}
}
catch { }
保存文件,运行服务端并使用启动器登录,我们发现当仓库中含有不存在的物品时,服务端会显示:
进入游戏,塔科夫单机版的服务端也不会再报“Cannot read property ‘Foldable’ of undefined”错误。
代码解析:玩家角色分为PMC和Scav,因此我们首先遍历characters这个对象,依次访问PMC和Scav两个角色的数据,然后遍历每个角色中Inventory对象中的Items数组。接下来我们对Items数组中的物品数据进行操作,首先声明一个空数组cleanedItems,用来存储存在的物品。接下来我们观察Items数组中物品数据的结构,它们至少都包含了_id和_tpl两个属性。我们分别复制它们的属性值,打开服务端数据目录/templates/items.json并搜索,发现_id的值不存在,_tpl的值存在,可以确认_tpl即为物品ID。然后我们利用.hasOwnProperty()方法来判断物品是否存在于内存数据中,方法的具体用法可自行百度。对存在的物品,我们把它原封不动搬进cleanedItems数组中,对不存在的物品,我们仅输出信息“Unable to find item: 物品ID, item deleted.”。操作完毕之后,我们把cleanedItems这个数组的值直接转移给存档内每个角色中Inventory对象中的Items数组。由于存档会不定期自动更新,我们无需担心后续的保存等问题,在服务端下次出现“[DEBUG] Saved profiles”信息之后,存档文件中不存在的物品就消失了。
另注:值得注意的是,无论采用哪种方式改写方法,都会导致MOD的兼容性变差。在选择需要改写的方法前应慎重考虑必要性。
原文:https://sns.oddba.cn/16676.html
暂无评论内容