饥荒联机版Mod开发——modmain(五)

前言

在modmain中我们可以通过
PrefabFiles = { “filename” } 来注册预设物
Assets = { Asset(“type”, “file”)} 来注册所需资源
TUNING 设置数值,显示的三维等, tuning.lua
STRINGS 设置字符串,物品名字/检查等, strings.lua
AddXXXPostInit 添加后构造函数
AddXXX 添加物品制作表、菜谱、动作、状态机等
GetModConfigData(“name”) 获取modinfo的configuration_options中name的data

题外话:

  1. 在mods文件夹下有个modsettings.lua,打开看看,对写mod有帮助。
    (ForceEnableMod的参数是modinfo中的name还是文件夹名不是很清楚,同名就完事了)
  2. 如果steam的找不到下载的mod,去找找 \Steam\steamapps\workshop\content\322330

modmain环境

设置环境的流程如下:
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在开服的时候才运行

函数

  • XXX函数对应 modutil.lua 中的 env.XXX
  • XXXPostInitAny:所有XXX的后构造函数,调用于构造函数之后
  • XXXPostInit:XXX的后构造函数,调用于构造函数之后(例如: Prefab中的fn之后)

参数

  • fn(…):对应参数的函数,如 function (…) end
  • inst、player:Entity
  • atlaspath:string,xml文件的路径,如 “images/xxx.xml”
  • =xxx:该参数可省,默认参数为xxx
  • prefab/component/brain/stategraph(无SG):一般对应文件夹下的文件名,实际是对应构造函数中的name,如Prefab(name, …)中的name。
预设物
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这些函数什么意思,定义在哪呢?让我们进入源码看个究竟。

mods.lua

在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

modutil.lua

大部分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(四)

猜你喜欢

转载自blog.csdn.net/weixin_46068322/article/details/126352181