ModLoader开发及修改方法
以下是关于如何修改ModLoader本体、如何将ModLoader本体以及插入到游戏中、如何将预装Mod嵌入到Html中的方法。若仅仅制作Mod,仅需按照以上方法打包Mod即可在Mod管理界面加载zip文件。
ModLoader开发及修改方法
本项目由于需要在SC2引擎启动前注入Mod,故对SC2引擎做了部分修改,添加了ModLoader的引导点,以便在引擎启动前完成Mod的各项注入工作。
修改过的 SC2 在此处:sugarcube-2 ,使用此ModLoader的游戏需要使用此版本的SC2引擎才能引导本ModLoader。
可在SC2游戏引擎项目中执行 node build.js -d -u -b 2 来编译SC2游戏引擎,编译结果在SC2游戏引擎项目的 build/twine2/sugarcube-2/format.js ,
将其覆盖 [Degrees-of-Lewdity] 游戏的原版 devTools/tweego/storyFormats/sugarcube-2/format.js ,编译原版游戏本体获得带有ModLoader引导点的Html游戏文件,
随后按照下方的方法编译并注入此ModLoader到Html游戏文件,即可使用此ModLoader。
编译脚本
yarn run webpack:BeforeSC2yarn run ts:ForSC2yarn run webpack:insertTools如何插入Mod加载器以及将预装Mod内嵌到html文件:
编写 modList.json 文件,格式如下: (样本可参见 src/insertTools/modList.json )
[ "mod1.zip", "mod2.zip"]切换到 modList.json 所在文件夹
cd ./src/insertTools/modList.jsonnode "<insert2html.js 文件路径>" "<Degrees of Lewdity VERSION.html 文件路径>" "<modList.json 文件>" "<BeforeSC2.js 文件路径>"例如:
node "H:\Code\sugarcube-2\ModLoader\dist-insertTools\insert2html.js" "H:\Code\degrees-of-lewdity\Degrees of Lewdity VERSION.html" "modList.json" "H:\Code\sugarcube-2\ModLoader\dist-BeforeSC2\BeforeSC2.js"会在原始html文件同目录下生成一个同名的html.mod.html文件,例如:
Degrees of Lewdity VERSION.html.mod.html打开Degrees of Lewdity VERSION.html.mod.html文件, play
附NodeJs及Yarn环境安装方法
- 从 NodeJs 官网 下载NodeJs并安装,例如 node-v18.18.0-x64.msi
- 在命令行运行
corepack enable来启用包管理器支持 - 结束
有关SC2注入点
ModLoader所需的唯一一个注入点是 sugarcube.js 文件中的jQuery启动处
/* eslint-enable no-unused-vars */
/* Global `SugarCube` object. Allows scripts to detect if they're running in SugarCube by testing for the object (e.g. `"SugarCube" in window`) and contains exported identifiers for debugging purposes.*/window.SugarCube = {};
/* Main function, entry point for the story.*/jQuery(() => { 'use strict';
const mainStart = () => { // 原来的 `jQuery(() => {}) `的内容 };
// inject ModLoader on there if (typeof window.modSC2DataManager !== 'undefined') { window.modSC2DataManager.startInit() .then(() => window.jsPreloader.startLoad()) .then(() => mainStart()) .catch(err => { console.error(err); }); } else { mainStart(); }});需要使用异步等待的方式,让原本的引擎启动逻辑等待ModLoader的初始化完毕,并等待ModLoader完成加载所有mod、执行mod注入脚本等待等的工作,之后才能执行原本的引擎启动逻辑。
技术说明
加载顺序
ModLoader会从4个地方加载mod
- html文件内嵌的【local】
- 远程web服务器【remote】 (如果是使用web服务器打开并且能读取到服务器上的modList.json)
- localStorage旁加载,上传文件(限制大小,所以现在没有使用)【localStorage】
- IndexDB旁加载,上传文件(现在用的)【IndexDB】
按照1234的顺序加载Mod,如果有同名Mod,后加载的会替代先加载的
有关 modList.json
这个 ModLoader 项目文件中的 modlist 只适用于预加载进html的mod【local】。 而对于远程加载的【remote】,modList.json 文件的格式与项目中的这个相同,唯一需要注意的是路径需要满足 fetch 的路径语法。远程加载的源码参见:
其中modFileZipPath
就是在 modList.json 文件中列出的路径,RemoteLoader 会直接使用fetch访问服务器的这个路径来下载zip文件并加载mod。
举例:
[ "aaa.mod.zip", // 从html同一目录加载 aaa.mod.zip "/rrr.mod.zip", // 从web服务器根目录加载 rrr.mod.zip "./ddd/ccc.mod.zip", // 从html所在目录下的ddd文件夹中加载 ccc.mod.zip "../../uuu.mod.zip", // 从html所在目录的父目录的父目录加载 uuu.mod.zip "http://aaa.bbb.ccc/mmm.mod.zip" // 从网站 aaa.bbb.ccc 加载 mmm.mod.zip]有关 modList.json 的设计是这样的。
- 对于需要打包自己的“整合包”的用户,可以直接在打包时指定一个自定义的
modList.json来将想要的mod全部打包进html。【local】 - 对于自己设立了web服务器的用户,可以在web服务器上的与html同一目录下放置一个
modList.json文件,这样在启动时就会由RemoteLoader去加载这个modList.json文件中指定的mod。【remote】 - 对于终端用户(玩家),在打开游戏后就会默认得到以上两个【local】+【remote】的mod,玩家自己可以通过mod管理器的界面再加载自己想要的mod。【IndexDB】
以上三个中,如果存在同名mod,则会存在覆盖关系,【remote】会覆盖【local】中的同名mod,而【IndexDB】会覆盖【local】+【remote】的同名mod。
也就是说,如果【local】和【remote】都存在mod A ,则最终生效的是【remote】的mod A。如果用户再自己加载一个mod A,则最终生效的是用户加载的这个mod A。
Mod、ModLoader、引擎、游戏 三者的结构
ModLoader和游戏的关系大约是 ((sc2引擎 + 游戏本体)[游戏] + (ModLoader + Mod)[Mod框架]) 这个结构
其中的 Mod框架 又细分为
( ( ModLoader + ( ModLoaderGui[Mod管理器界面] + Addon[扩展插件] )[预置Mod] )[植入到html] + 其他Mod[上传或者远程加载] )要使用ModLoader玩游戏,需要使用经过修改的SC2引擎,例如 这里,
其中最关键的部分就是上方的SC2注入点,ModLoader需要这个注入点才能实现在引擎启动前修改和注入Mod的工作
因为关系较为复杂,这里使用了GithubAction来实现自动编译
预编译版的修改版的SC2引擎 其中注入了ModLoader的引导点
预编译好的ModLoader以及Mod打包器(packModZip.js)和注入器(insert2html.js)和几个Addon
自动打包的包含ModLoader和Addon的原版DoL游戏
打包后的结构
((定制sc2引擎 + 原版游戏) + ModLoader)因为ModLoader需要在sc2引擎启动之前把事情全部做完,但是引擎的启动事件是挂在jq的页面onLoaded事件上的,这个没法正常推迟, 所以最直接的解决办法就是把sc2引擎的启动代码单独放到一个函数里面,然后让ModLoader在jq的事件里面先启动,等启动完再调用原来的启动sc2引擎的代码
使得本来是 jq → sc2 的过程,变成 jq → ModLoader → SC2
如果需要手动打包,需要按照下面的步骤进行
- 构建
修改版的SC2引擎获得 format.js , - 覆盖到游戏项目的 devTools\tweego\storyFormats\sugarcube-2\format.js ,再编译游戏就可以生成带修改版的SC2引擎的游戏
- 用ModLoader的注入器(insert2html.js)将ModLoader注入到游戏的html里。
以上的第三步在注入时会将 modList.json 中列出的Mod作为【Local】类型的Mod作为预置Mod一起注入到html中,默认情况下 modList.json 中会包含ModLoaderGui和一些Addon
有关Addon
由于将所有功能都实现在ModLoader中既不现实也不合理,特别是,有关特定游戏的功能更不应该实现在与SC2引擎绑定的ModLoader中,故ModLoader提供了Addon的功能。
Addon是一种特殊的Mod,作为一种功能扩展的形式存在,通过将常用功能集中在一个Mod中供其他ModLoader调用,这样的Mod就可以成为Addon。
例如在RimWorld中的 身体扩展、战斗扩展、发饰扩展 等等,这些Mod作为一种中间层,通过直接注入和修改游戏并扩展出新功能来供其他Mod调用,以此来方便其他mod的编写。
例如项目中的 ImageLoaderHook 就是一个Addon, 这个Mod通过挂钩DoL游戏中的图片加载器,实现了把Mod中的图片加载到游戏中的功能,游戏在加载图片时就会去读取Mod中的图片。
项目中的 CheckDoLCompressorDictionaries 是另一个不同功能的Addon, 这个Mod仅仅检查DoLCompressorDictionaries数据结构,并在发现数据结构变化后发出警示,来提示Mod开发者和使用者避免修改DoLCompressorDictionaries,以避免影响存档有效性。
项目中的 ReplacePatch 和 TweeReplacer 则实现Passage和JS/CSS的字符串替换功能, 大部分需要修改游戏逻辑的Mod就不需要自己编写修改的代码,可以直接使用这两个Mod来实现字符串替换功能, 将这个功能独立出来,避免ModLoader过于臃肿的同时,也方便了对替换功能的快速更新和升级,在必要时Mod作者可以自行folk来实现更复杂的替换功能而不需要修改ModLoader的代码。