制作 Mod.zip
- 给自己的mod命名
- 以mod名字组织自己的mod
- 编写mod的引导描述文件 boot.json 文件
- 格式如下(样例 src/insertTools/MyMod/boot.json):
{ "name": "MyMod", // (必须存在) mod名字 "version": "1.0.0", // (必须存在) mod版本 "scriptFileList_inject_early": [ // (可选) 提前注入的 js 脚本 , 会在当前mod加载后立即插入到dom中由浏览器按照script的标注执行方式执行 "MyMod_script_inject_early_example.js" ], "scriptFileList_earlyload": [ // (可选) 提前加载的 js 脚本 , 会在当前mod加载后,inject_early脚本全部插入完成后,由modloader执行并等待异步指令返回,可以在这里读取到未修改的Passage的内容 "MyMod_script_earlyload_example.js" ], "scriptFileList_preload": [ // (可选) 预加载的 js 脚本文件 , 会在引擎初始化前、mod的数据文件全部加载并合并到html的tw-storydata中后,由modloader执行并等待异步指令返回, 可以在此处调用modloader的API读取最新的Passage数据并动态修改覆盖Passage的内容 "MyMod_script_preload_example.js" // 注意 scriptFileList_preload 文件有固定的格式,参见样例 src/insertTools/MyMod/MyMod_script_preload_example.js ], "styleFileList": [ // (必须存在) css 样式文件 "MyMod_style_1.css", "MyMod_style_2.css" ], "scriptFileList": [ // (必须存在) js 脚本文件,这是游戏的一部分 "MyMod_script_1.js", "MyMod_script_2.js" ], "tweeFileList": [ // (必须存在) twee 剧本文件 "MyMod_Passage1.twee", "MyMod_Passage2.twee" ], "imgFileList": [ // (必须存在) 图片文件,尽可能不要用容易与文件中其他字符串混淆的文件路径,否则会意外破坏文件内容 "MyMod_Image/typeAImage/111.jpg", "MyMod_Image/typeAImage/222.png", "MyMod_Image/typeAImage/333.gif", "MyMod_Image/typeBImage/111.jpg", "MyMod_Image/typeBImage/222.png", "MyMod_Image/typeBImage/333.gif" ], "additionFile": [ // (必须存在) 附加文件列表,额外打包到zip中的文件,此列表中的文件不会被加载,仅作为附加文件存在。 // 请注意,这里的文件会以被当作文本文件以utf-8编码读取并保存 "readme.txt" // 第一个以readme(不区分大小写)开头的文件会被作为mod的说明文件,会在mod管理器中显示 ], "additionBinaryFile": [ // (可选) 附加二进制文件 "xxxx.zip" // 如果有需要附加的二进制文件,编写在这里时 `packModZip.ts` 会将其以二进制格式保存 ], "additionDir": [ // (可选) 附加文件夹 "xxxx" // 如果有需要附加的文件夹,编写在这里时 `packModZip.ts` 会将其下所有文件以二进制格式保存 ], "addonPlugin": [ // (可选) 依赖的插件列表,在此声明本mod依赖哪些插件,在此处声明后会调用对应的插件,不满足的依赖会在加载日志中产生警告 { // 需要首先由提供插件的mod在EarlyLoad阶段注册插件,否则会找不到插件 "modName": "MyMod2", // 插件来自哪个mod "addonName": "addon1", // 在那个mod中的插件名 "modVersion": "1.0.0", // 插件所在mod的版本 "params": [] // (可选) 插件参数 } ], "dependenceInfo": [ // (可选) 依赖的mod列表,可以在此声明此mod依赖哪些前置mod,不满足的依赖会在加载日志中产生警告 { "modName": "ModLoader DoL ImageLoaderHook", // 依赖的mod名字 "version": "^2.0.0" // 依赖的mod版本 // 对于版本号声明格式的简单说明: // 版本号是以逗号为分隔的数字,比较时从左往右逐个逗号进行比较。 // 通常是3个数字组成,第一个数字表示大版本,出现破坏性(不向前兼容的)变更时数值加一,第二个数字表示小版本,有新功能时数值加一,第三个数字表示修订版本,修复bug时数值加一。 // 以"^"开头表示从此大版本开始到下一个大版本结束的范围,这是推荐的默认依赖写法。 // 以"="或不带任何前缀表示只依赖指定的版本号。 // 以"> < >= <="这种不等式写法符合对应的数学语义。 // }, { "modName": "ModLoaderGui", "version": "^1.0.8" // 依赖的mod版本,使用(https://www.npmjs.com/package/semver)检查版本号,符合`语义化版本控制规范` (https://semver.org/lang/zh-CN/) }, // 除了以上的方法可以声明对普通Mod的依赖,还有下面两个特殊的对 ModLoader 版本和 游戏版本 的依赖声明 { "modName": "ModLoader", // 部分Mod可能需要依赖从特定ModLoader版本开始才添加进ModLoader的API,例如大部分AddonMod,可以像这样声明对ModLoader版本的依赖 "version": "^1.6.0" }, { "modName": "GameVersion", // 部分Mod只能在特定游戏版本下才能正常工作,可以像这样声明对游戏版本的依赖。特别注意这里以等号开头表示只匹配特定版本的游戏。此处比较时只会比较游戏的本体版本号,忽略第一个"-"开始的所有后缀。 "version": "=0.4.2.7" } ]}最小 boot.json 文件样例:
{ "name": "EmptyMod", "version": "1.0.0", "styleFileList": [ ], "scriptFileList": [ ], "tweeFileList": [ ], "imgFileList": [ ], "additionFile": [ "readme.txt" ]}注意事项
- boot.json 文件内的路径都是相对路径,相对于zip文件根目录的路径。
- 图片文件的路径是相对于zip文件根目录的路径。
- 同一个mod内的文件名不能重复,也尽量不要和原游戏或其他mod重复。与原游戏重复的部分会覆盖游戏源文件。
- 具体的来说,mod会按照mod列表中的顺序加载,靠后的mod会覆盖靠前的mod的passage同名文件,mod之间的同名css/js文件会直接将内容concat到一起,故不会覆盖css/js等同名文件。
- 加载时首先计算mod之间的覆盖,(互相覆盖同名passage段落,将同名js/css连接在一起),然后将结果覆盖到原游戏中(覆盖原版游戏的同名passage/js/css)
- 当前版本的mod加载器的工作方式是直接将css/js/twee文件按照原版sc2的格式插入到html文件中。
对于一个想要修改passage的mod,有这么4个可以修改的地方
scriptFileList_inject_early, 这个会在当前mod读取之后,“立即”插入到script脚本由浏览器按照script标签的标准执行,这里可以调用ModLoader的API,可以读取未经修改的SC2 data (包括原始的passage)scriptFileList_earlyload,这个会在当前mod读取之后,inject_early 脚本插入完之后,由modloader执行并等待异步指令返回,这里可以调用ModLoader的API,可以执行异步操作,干一些远程加载之类的活,也可以在这里读取未经修改的SC2 data(包括原始的passage)tweeFileList,这个是mod的主体,会在modloader读取所有mod之后,做【1 合并所有mod追加的数据,2 将合并结果覆盖到原始游戏】的过程应用修改到原始游戏SC2 data上scriptFileList_preload, 这个会在mod文件全部应用到SC2 data之后由modloader执行并等待异步操作返回,这里可以像earlyload一样做异步工作,也可以读取到mod应用之后的SC2 data
上面的步骤结束之后SC2引擎才会开始启动,读取SC2 data,然后开始游戏,整个步骤都是在加载屏幕(那个转圈圈)完成的。
另,由于SC2引擎本身会触发以下的一些事件,故可以使用jQuery监听这些事件来监测游戏的变化
// 游戏完全启动完毕:storyready// 一个新的 passage 上下文开始初始化:passageinit// 一个新的 passage 开始渲染:passagestart// 一个新的 passage 渲染结束:passagerender// 一个新的 passage 准备插入到HTML:passagedisplay// 一个新的 passage 已经处理结束:passageend可以以下方法监听jQuery事件
$(document).one(":storyready", () => { // ....... 触发一次});$(document).on(":storyready", () => { // ....... 触发多次});