在modmain中我们可以通过
PrefabFiles = { “filename” } 来注册预设物
Assets = { Asset(“type”, “file”)} 来注册所需资源
TUNING 设置数值,显示的三维等, tuning.lua
STRINGS 设置字符串,物品名字/检查等, strings.lua
AddXXXPostInit 添加后构造函数
AddXXX 添加物品制作表、菜谱、动作、状态机等
GetModConfigData(“name”) 获取modinfo的configuration_options中name的data
题外话:
设置环境的流程如下:
mods.lua : CreateEnvironment() ->
modutil.lua:InsertPostInitFunctions() ->
env:当前modmain所在环境
在modmain.lua第一行加入下面这句,直接避免麻烦的local XXX = GLOBAL.XXX,即在modmain环境env下找不到对应的 key 时跑到 GLOBAL 中找。(一键GLOBAL)
--一键GLOBAL
GLOBAL.setmetatable(env,{__index=function(t,k) return GLOBAL.rawget(GLOBAL,k) end})
这里只是简单给个定义,具体的用法,可以看其他mod的modmain,或源代码
注:客户端mod在开游戏时就运行了,服务器mod在开服的时候才运行
函数
参数
预设物
AddPrefabPostInitAny(fn(inst))
AddPrefabPostInit(prefab, fn(inst))
AddMinimapAtlas(atlaspath) --对应 inst.MiniMapEntity:SetIcon( "xxx.tex" )
玩家
AddPlayerPostInit(fn(player))
---gender:"FEMALE/MALE/ROBOT/NEUTRAL/PLURAL" modes:选人动画 loadoutselect.lua skinutils.lua
AddModCharacter(name, gender="NEUTRAL", modes=nil)
组件
AddComponentPostInit(component, fn(self)) --Class中的self,类比其他语言的this
AddReplicableComponent(component) --例如xxx_replica.lua,就写"xxx"
动画、动作
--componentaction客户端判断能否执行,执行运行stategraph,执行完回调acion
AddAction(id:string|Action, str, fn(act)) --actions.lua
---stategraph对应stategraphs文件夹下的文件(无SG),联机版wilson和wilson_cient
---handle: ActionHandler(Action, "anim_name") Action是自己注册的或ACTIONS.XXX
AddStategraphActionHandler(stategraph, handler)
--actiontype: "SCENE/USEITEM/POINT/EQUIPPED/INVENTORY" fn参数由actiontype决定
AddComponentAction(actiontype, component, fn(...)) --componentactions.lua
--参考 stategraph.lua SGwilson.lua SGwilson_client.lua
AddStategraphState(stategraph, state)
AddStategraphEvent(stategraph, event)
AddStategraphPostInit(stategraph, postfn)
类
--class, globalclass,全路径文件名
AddClassPostConstruct(class, fn(self)) --普通class修改
--例 AddGlobalClassPostConstruct("mods","ModManager",function(self) end)
AddGlobalClassPostConstruct(globalclass, classname, fn(self)) --全局的class修改
大脑(AI)
AddBrainPostInit(brain, fn(self))
游戏
--可以修改main.lua中的全局变量,不过最好先判空
AddGamePostInit(fn()) --先
AddSimPostInit(fn()) --后,常用于生成prefab
--AddGameMode(...) 移除,在modinfo里改
物品栏制作
--这里的recipename、name等指Recipe的name,一般也是预设物名
--写法参照recipes.lua repice.lua 即可
AddRecipe2(name, ingredients, tech, config, filters) --添加物品配方
AddCharacterRecipe(name, ingredients, tech, config, extra_filters) --filter是玩家
AddDeconstructRecipe(name, return_ingredients) --分解掉落物
AddRecipeFilter(filter_def, index) --自定义物品栏
AddRecipeToFilter(recipe_name, filter_name) --添加物品配方自定义物品栏
RemoveRecipeFromFilter(recipe_name, filter_name)
AddRecipePostInit(recipename, fn(self))
AddRecipePostInitAny(fn(self))
RegisterInventoryItemAtlas(atlas, prefabname) --tex名要和prefab一样,一般不用
烹饪
--食物的Prefab在prefabs/preparedfoods.lua和prefabs/preparedfoods_warly.lua
---cooker:"cookpot/portablecookpot"
---recipe:参考scripts/preparedfoods.lua和scripts/preparedfoods_warly.lua
AddCookerRecipe(cooker, recipe) --烹饪配方
AddIngredientValues(names, tags, cancook, candry) --肉度/菜度等 参考cooking.lua
声音
RemapSoundEvent(name, new_name)
RemoveRemapSoundEvent(name)
RPC --RPC,服务器和客户端交流的一种方式,另一种是用网络变量(netvar.lua)
---namespace用modname最好
---name:独一无二的函数名
---id_table:GetXXXRPC(...)
AddModRPCHandler(namespace, name, fn(...))
GetModRPCHandler(namespace, name) --> Add时的fn
GetModRPC( namespace, name ) --> id_table
SendModRPCToServer( id_table, ... )
AddClientModRPCHandler(namespace, name, fn(...))
GetClientModRPCHandler(namespace, name) --> Add时的fn
GetClientModRPC( namespace, name ) --> id_table
SendModRPCToClient( id_table, ... )
AddShardModRPCHandler(namespace, name, fn(...))
GetShardModRPCHandler(namespace, name) --> Add时的fn
GetShardModRPC( namespace, name ) --> id_table
SendModRPCToShard( id_table, ... )
命令、提示等
AddUserCommand(command_name, data)
AddVoteCommand(command_name, init_options_fn, process_result_fn, vote_timeout )
AddLoadingTip(stringtable, id, tipstring, controltipdata)
RemoveLoadingTip(stringtable, id)
SetLoadingTipCategoryWeights(weighttable, weightdata)
SetLoadingTipCategoryIcon(category, categoryatlas, categoryicon)
房间 --生成世界时用
AddRoomPreInit(name,function(room) end)
AddTaskPreInit()
...
Shaders --着色器
AddModShadersInit( fn )
AddModShadersSortAndEnable( fn )
...
大概很多新手会在modmain中看到过这样的代码,就感觉到很奇怪,lua中常用require为什么还要加个GLOABL,AddXXX这些函数什么意思,定义在哪呢?让我们进入源码看个究竟。
在main.lua调用了ModManager:LoadMods(),对应mods.lua下的ModWrangler:LoadMods(),进而调用CreateEnvironment()来创建modmain的环境(在加载世界的时候会把isworldgen设为true创建modworldgenmain的环境),并用modutil.lua的函数往环境中插入AddXXX等API
--源文件 mods.lua, 为方便说明,改了下顺序和代码
local ModWrangler = Class(function(self) ... end) -- 单例类
ModManager = ModWrangler() --单例
--加载mod时,会调用 ModManager:LoadMods(),就是下面这函数
function ModWrangler:LoadMods(worldgen)
--遍历全部启用的mod(客户端mod进游戏时就加载了,服务器mod要等到开服时)
--创建modmain或modworldgenmain环境
local env = CreateEnvironment(modname, worldgen)
--在InitializeModMain间接调用 setfenv(mod, env) 来设置环境
self:InitializeModMain(modname, env , "modworldgenmain.lua")
if not worldgen then --在创建地图的时候不加载modmain
self:InitializeModMain(odname, env , "modmain.lua")
end
end
--创建mod环境
function CreateEnvironment(modname, isworldgen, isfrontend)
local modutil = require("modutil")
--env中有的我们可以直接访问,没有的用GLOABL.XXX或一键GLOBAL
local env = {
-- lua
pairs = pairs,
print = print,
...
--utility
GLOBAL = _G, --这就是为什么GLOABL和_G一样的原因
modname = modname,
MODROOT = MODS_ROOT..modname.."/", --mod根目录,就是modmain所在文件夹
}
--让我们在modmain用env访问mod所在环境
env.env = env
--官方提供的require的代替函数,代码环境和modmain一样,把modmian拆开写
--例如加载 scripts/xxx.lua,在modmain中 modimport("scripts/xxx.lua") 后缀可省
env.modimport = function(modulename)
local result = kleiloadlua(env.MODROOT..modulename) --加载代码
setfenv(result, env.env) --设置和modmain一样的环境
result() --运行代码
end
--在modutil.lua中插入函数到env,就是我们常用的官方API,AddXXX等
modutil.InsertPostInitFunctions(env, isworldgen, isfrontend)
return env
end
大部分API都在这里modmain和modworldgenmain用的API都在这
--源代码 modutil.lua
--对应mods.lua中的modutil.InsertPostInitFunctions(env, isworldgen, isfrontend)
--往mod环境中插入函数
local function InsertPostInitFunctions(env, isworldgen, isfrontend)
env.modassert = modassert --(test:bool, message:string)
env.moderror = moderror --(message:string, level:num=1) modsettings中启用了才会报错
--添加modmain,modworldgenmain都可以调用的和创建世界相关的API
env.AddRoom = function(arg1, ...) ... end
env.AddTile = function(...) ... end
...
if isworldgen then return end --modworldgenmain到这就结束了
--下面是仅modmain的API,只是列举了少部分API,建议看源代码
--菜谱
env.AddCookerRecipe = function(cooker, recipe) ... end
--物品制作
env.AddRecipe2 = function(name, ingredients, tech, config, filters) ... end
--预设物
env.postinitfns.PrefabPostInit = {}
env.AddPrefabPostInit = function(prefab, fn) ... end
--组件
env.postinitfns.ComponentPostInit = {}
env.AddComponentPostInit = function(component, fn) ... end
...
end
--require后返回这个table,然后就可以间接调用对应函数
return { InsertPostInitFunctions = InsertPostInitFunctions }
当然大部分Add的都只是把参数存在了env.postinitfns.XXX或env.postinitdata.XXX里,那具体的参数在哪里?那就追根溯源,看看在哪里有postinitfns和postinitdata,用上全局搜索(VS code快捷键Ctrl+Shift+F),你会发现它们在mods.lua中的GetPostInitFns和GetPostInitData
--mods.lua 代码不是这么写的,不过意思是一样的
--type:string,对应 env.postinitfns.XXX 中的 "XXX"
--id:XXX表的key,当id为nil时直接返回表
function ModWrangler:GetPostInitFns(type, id) ... end
--type:string,对应 env.postinitdata.XXX 中的 "XXX"
--id:XXX表的key,当id为nil时直接返回表
function ModWrangler:GetPostInitData(type, id) ... end
显然它们要获取数据就需要调用这两个函数,并至少传个type字符串进去,这样我们可以直接全局搜索type,就可以找到调用的位置!
例如:env.postinitfns.ComponentPostInit,我们就全局搜索ComponentPostInit
注:有些函数虽然插入了表,不过它们在Add的时候就直接调用了,如AddRecipePostInit
→饥荒联机版Mod开发——制作烹饪锅食物(六)
←饥荒联机版Mod开发——Class, Prefab, component,debug(四)