此文章只是记录编写联机版mod时做的记录,不是一篇很正式的文档
想要入门饥荒mod制作,可以看同站的夏湾作者文章,很详细~
直接给代码和结论,目前在饥荒是可用的
修改制作栏中的合成物品时的需要资源
--有需要直接拿来用就行
--可通过AddRecipePostInit方法修改,第一个传入需要合成的物品的prefab名字,第二个是制作栏修改的方法,self不是预制体而是另外一个东西,这不用在意
AddRecipePostInit("armorwood",function(self)
--print("AddRecipePostInit") --log语句
if IsRecipeValid(self.name) then--判断prefab的名字是否可制作(暂时这样理解把)
--ingredients是合成该prefab所需要的资源,在后面以数组(应该)的方式放入
GetValidRecipe(self.name).ingredients={Ingredient("log", 6),Ingredient("rope", 2)}
--这里的意思六个木头和两个绳索合成木甲
end
end)
在制作栏中添加一个prefab的多个合成方式
--向物品栏(合成栏)添加合成方式,这是官方的新的方法,用这个是没问题的
AddRecipe2(
"blowdart_pipe1", --当前Recipe的名称,不能用已经存在的于物品栏的prefab的名字,得另起一个
--合成所需要的材料,按格式添加即可
{Ingredient("cutreeds", 1),Ingredient("rocks", 2),Ingredient("feather_robin_winter", 1)},
--合成所需要的条件,需要解锁科技?解锁魔法?还是远古等等
TECH.SCIENCE_TWO,--SCIENCE_ONE SCIENCE_THREE MAGIC_TWO MAGIC_THREE ANCIENT_TWO等等
--这个是名字为config参数,一般可选有很多,但是在这里,我们只需要把product 设置为已经存在的于物品栏的prefab的名字,即你需要的prefabs,numtogive 的意思是制作后会给你几个该物品
{product ="blowdart_pipe",numtogive = 2},
--最后这个是filters参数,就是制作栏(合成栏)的分累,比如武器建筑生存什么的,这些东西的tag或者flag暂时可以这样理解
{"WEAPONS","MODS"})--不会填就填个MODS,具体的需要看文件\scripts\recipes_filter.lua 中的CRAFTING_FILTER_DEFS 这个table里的第一个字段的内容
放个效果图把
这里如果你想说我自己创建一个新的prefab然后配置同样的参数,添加到物品栏中不是也可以实现这个需要
答案是可以这样做,我也这样做了,我发现这个有几个不好的地方,一个是贴图要配置(物品栏的和背包里面的都需要),另一个是然后你这个预制体其实是一个新的prefab和之前的都不一样,所以和之前的或者新的不能叠加在一起,如果还得非有说一点的话就是为了更好管理mod,你可能需要新起大目录(或者叫模块?)专门放置你新建的预制体,然后在添加进入modmain中去
进阶(如果你还想更深入的了解的话,继续看吧,不保证完全对)
上述的AddRecipe2方法,其实在/scirpts/modutil.lua里面有具体实现
env.AddRecipe2 = function(name, ingredients, tech, config, filters)
require("recipe")--引入scripts\recipe.lua,需要用到里面的Recipe2
mod_protect_Recipe = false --recipe.lua 中的一个变量在Recipe2中有用到
local rec = Recipe2(name, ingredients, tech, config) --结果存下来
--如果不是可分解或者可被锤子敲的物品
if not rec.is_deconstruction_recipe then
--就进入这里主要是给当前合成栏的物品进行分类,把其和某个Filter关联在一起
--也就是我们进游戏后制作栏看到各种物品被分类到不同的地方用的,比如武器/防具/光源等
if config ~= nil and config.nounlock then
env.AddRecipeToFilter(name, CRAFTING_FILTERS.CRAFTING_STATION.name)
else
--AddRecipeToFilter这个方法稍后说
env.AddRecipeToFilter(name, CRAFTING_FILTERS.MODS.name)
end
if filters ~= nil then
for _, filter_name in ipairs(filters) do
env.AddRecipeToFilter(name, filter_name)
end
end
end
mod_protect_Recipe = true --结束再把这个状态置回来
rec:SetModRPCID() --这是和网络相关的,目前我也不太了解
return rec
end
其实到目前为止还没有做什么很明显的动作,重点主要是在Recipe2中
我们看到Recipe2的代码
--scripts\recipe.lua文件
--Class是klei自己写的东西,调用Recipe2()主要是调用function中的内容
Recipe2 = Class(Recipe, function(self, name, ingredients, tech, config)
--最后是调用了Recipe的_ctor函数,在这里可以看到config内的参数有很多
if config ~= nil then
Recipe._ctor(self, name, ingredients, nil, tech, config, config.min_spacing, config.nounlock, config.numtogive, config.builder_tag, config.atlas, config.image, config.testfn, config.product, config.build_mode, config.build_distance)
else
Recipe._ctor(self, name, ingredients, nil, tech)
end
self.is_deconstruction_recipe = false
end)
可以看到最终是调用了Recipe,那继续来看看Recipe,下面代码有点长
Recipe = Class(function(self, name, ingredients, tab, level, placer_or_more_data, min_spacing, nounlock, numtogive, builder_tag, atlas, image, testfn, product, build_mode, build_distance) -- do not add more params here, add them to "placer_or_more_data"
if mod_protect_Recipe then --这个就是之前AddRecipe2中设置为false
print("Warning: Calling Recipe from a mod is now deprecated. Please call AddRecipe from your modmain.lua file.")
end--可以避免输出这句话 其实主要是针对一些过期的接口的警告
--没细看不明白
local placer = nil
local more_data = {}
if type(placer_or_more_data) == "table" then
placer = placer_or_more_data.placer
more_data = placer_or_more_data
else
placer = placer_or_more_data
end
--从这里开始就是设置这个Recipe的属性
--Recipe名字,但是这个名字和最终生成的prefab关联不大
self.name = name
--合成所需要的资源
self.ingredients = {}
--看了下好像是根据某些特定字符串来放入这里面的,暂时不知道有什么用
self.character_ingredients = {}
--和上面类似暂时不知道用处
self.tech_ingredients = {}
self.filter = more_data.filter
--这里通过上面传入的ingredients 来判断将每一个ingredient插入那个上述哪个表中
for k,v in pairs(ingredients) do
table.insert(
(IsCharacterIngredient(v.type) and self.character_ingredients) or
(IsTechIngredient(v.type) and self.tech_ingredients) or
self.ingredients,--表格选择
v--数据
)
end
--这个就是最终生成的prefab的名字(关键),如果传入了product名字就会用product名字不然就是用传入的name名字
--在饥荒里面一般的物品的制作名字和最终的prefab都是一样的
--所以大多数的时候product是空值,这个就是AddRecipe2中config参数里面配置的
--我们上面的"在制作栏中添加一个prefab的多个合成方式" 就是用到了这个参数
self.product = product or name
self.tab = tab -- DEPRECATED
--在制作栏的中的描述
self.description = more_data.description -- override the description string in the crafting menu
self.imagefn = type(image) == "function" and image or nil
--图片什么的
self.image = self.imagefn == nil and image or (self.product .. ".tex")
self.atlas = (atlas and resolvefilepath(atlas))-- or resolvefilepath(GetInventoryItemAtlas(self.image))
self.fxover = more_data.fxover
--self.lockedatlas = (lockedatlas and resolvefilepath(lockedatlas)) or (atlas == nil and resolvefilepath("images/inventoryimages_inverse.xml")) or nil
--self.lockedimage = lockedimage or (self.product ..".tex")
--排序时候用到的,涉及到在制作栏(合成栏)中先后顺序,num是这个文件的全局变量,永不妥协模组就有用这个然后进行重新排序
self.sortkey = num
self.rpc_id = num --mods will set the rpc_id in SetModRPCID when called by AddRecipe()
--需要科技等级
self.level = TechTree.Create(level)
--不懂
self.placer = placer
self.min_spacing = min_spacing or 3.2
self.testfn = testfn -- custom placer test function if default test isn't enough
self.canbuild = more_data.canbuild -- custom test function to see if we should be allowed to craft this recipe, return a build action fail message if false
--不懂
self.nounlock = nounlock or false
--制作一次物品最终会产生几个prefab 上面示例就用到了
self.numtogive = numtogive or 1
--不懂
self.builder_tag = builder_tag or nil
self.sg_state = more_data.sg_state or more_data.buildingstate or nil -- overrides the SG state to use when crafting the item (buildingstate is the old variable name)
--建造模式在陆地上 ,海上之类的,不是很清楚
self.build_mode = build_mode or BUILDMODE.LAND
self.build_distance= build_distance or 1
--都不懂啦
self.no_deconstruction = more_data.no_deconstruction -- function or bool
self.require_special_event = more_data.require_special_event
self.dropitem = more_data.dropitem
self.actionstr = more_data.actionstr
self.hint_msg = more_data.hint_msg
self.manufactured = more_data.manufactured -- if true, then it is up to the crafting station to handle creating the item, not the builder component
--这个值根据不同接口最后会设置不同值
self.is_deconstruction_recipe = tab == nil
--全局num自增
num = num + 1
--这里也是一个比较关键的参数,用一个AllRecipes全局变量存储下来当前的物品制作的Recipe
--其实意味着你可以直接根据名字去修改上述所有数据,永不妥协获取AllRecipes去修改数据的
AllRecipes[name] = self
--不懂看看翻译吧
if self.builder_tag ~= nil and more_data.allowautopick == nil then -- NOTES(JBK): "donotautopick" filtered items should set allowautopick in the recipe if they are to be picked up by things for the player.
AllBuilderTaggedRecipes[name] = self.builder_tag
end
--最后你会发现这里会调用RecipePostInit函数,很重要啊,文章第一个方法就是这里被调用的
if ModManager then
for k,recipepostinit in pairs(ModManager:GetPostInitFns("RecipePostInit")) do
recipepostinit(self)
end
for k,recipepostinitany in pairs(ModManager:GetPostInitFns("RecipePostInitAny")) do
recipepostinitany(self)
end
end
end)
看了这么多要我总结的话就是注意Recipe中name和product可以不一样,
第二点就是ingredients sortkey numtogive实际上作用比较大,
还有一个AllRecipes全局变量很关键存储了所有制作的Recipe
其实这里你还会发现和我们第二个方法的代码还是有点不一样,在我们第二个方法里面用的是IsRecipeValid(self.name) GetValidRecipe(self.name)
这个其实在这个文件里有定义的啦
function GetValidRecipe(recname)
if not IsRecipeValidInGameMode(TheNet:GetServerGameMode(), recname) then
return
end
local rec = AllRecipes[recname]
return rec ~= nil and not rec.is_deconstruction_recipe and (rec.require_special_event == nil or IsSpecialEventActive(rec.require_special_event)) and rec or nil
end
function IsRecipeValid(recname)
return GetValidRecipe(recname) ~= nil
end
其实就是从AllRecipes全局变量根据name取到对应的Recipe就是中间帮我们做了一些有效性的判断,这个当然很重要,所以我就直接用它里面的函数了
好了讲了这么大一块,应该快累了,没事加加油,我们还有最后一块内容
就是上面的AddRecipeToFilter函数,这又是另外一个故事了,从这个名字来说就是把Recipe加入到Filter中去可能会觉得奇怪,Filter难道是一个实际的数据结构么,我告诉你还真是,可能和平常用的Filter不一样,这里按照德语?来说的话应该是一个名词,而不是一个形容词或者动词的用法,说了一些没用的话,那看代码把
--函数传入两个参数,都是字符串,一个是制作物品(recipe_name)的名字,一个是过滤器(filter_name)的名字
env.AddRecipeToFilter = function(recipe_name, filter_name)
initprint("AddRecipeToFilter", recipe_name, filter_name)
--这里你会发现它CRAFTING_FILTERS中根据filter_name取得了一个什么东西,一般饥荒里面全大写加下划线的名字组合用来表示某些全局常量或者一些全局数据结果(不知道说法是不是对的)
local filter = CRAFTING_FILTERS[filter_name]
--先不管CRAFTING_FILTERS继续看,一个判断,取到了这个东西,而且这个东西里面的default_sort_values表中查不到recipe_name的值,说明是一个新的值,要加入,这里主要是判断过滤器是否有效和防止重复加入
if filter ~= nil and filter.default_sort_values[recipe_name] == nil then
--用一个insert把recipe_name插入到上面取到的filter.recipes的末尾
table.insert(filter.recipes, recipe_name)
--给这个赋上一个排序值
filter.default_sort_values[recipe_name] = #filter.recipes
end
end
最后我们就来讲讲CRAFTING_FILTERS是一个什么东西
这个在scripts\recipes_filter.lua文件中
--这里就是数据结构,第一个是名字,然后atlas image是制作栏每个菜单的图标
--custom_pos不晓得 recipes就是当前制作栏过滤器存下的制作物品的Recipe的名字
--把CRAFTING_FILTER_DEFS的数据 根据名字插入CRAFTING_FILTERS表中
CRAFTING_FILTERS = {}
for i, v in ipairs(CRAFTING_FILTER_DEFS) do
CRAFTING_FILTERS[v.name] = v
end
CRAFTING_FILTER_DEFS =
{
{name = "FAVORITES", atlas = GetCraftingMenuAtlas, image = "filter_favorites.tex", custom_pos = true},
{name = "CRAFTING_STATION", atlas = GetCraftingMenuAtlas, image = "filter_none.tex", custom_pos = true},
{name = "SPECIAL_EVENT", atlas = GetCraftingMenuAtlas, image = "filter_events.tex", custom_pos = true},
{name = "MODS", atlas = GetCraftingMenuAtlas, image = "filter_modded.tex", custom_pos = true, recipes = {}},
{name = "CHARACTER", atlas = GetCharacterAtlas, image = GetCharacterImage, image_size = 80},
{name = "TOOLS", atlas = GetCraftingMenuAtlas, image = "filter_tool.tex", },
{name = "LIGHT", atlas = GetCraftingMenuAtlas, image = "filter_fire.tex", },
{name = "PROTOTYPERS", atlas = GetCraftingMenuAtlas, image = "filter_science.tex", },
{name = "REFINE", atlas = GetCraftingMenuAtlas, image = "filter_refine.tex", },
{name = "WEAPONS", atlas = GetCraftingMenuAtlas, image = "filter_weapon.tex", },
{name = "ARMOUR", atlas = GetCraftingMenuAtlas, image = "filter_armour.tex", },
{name = "CLOTHING", atlas = GetCraftingMenuAtlas, image = "filter_warable.tex", },
{name = "RESTORATION", atlas = GetCraftingMenuAtlas, image = "filter_health.tex", },
{name = "MAGIC", atlas = GetCraftingMenuAtlas, image = "filter_skull.tex", },
{name = "DECOR", atlas = GetCraftingMenuAtlas, image = "filter_cosmetic.tex", },
{name = "STRUCTURES", atlas = GetCraftingMenuAtlas, image = "filter_structure.tex", },
{name = "CONTAINERS", atlas = GetCraftingMenuAtlas, image = "filter_containers.tex", },
{name = "COOKING", atlas = GetCraftingMenuAtlas, image = "filter_cooking.tex", },
{name = "GARDENING", atlas = GetCraftingMenuAtlas, image = "filter_gardening.tex", },
{name = "FISHING", atlas = GetCraftingMenuAtlas, image = "filter_fishing.tex", },
{name = "SEAFARING", atlas = GetCraftingMenuAtlas, image = "filter_sailing.tex", },
{name = "RIDING", atlas = GetCraftingMenuAtlas, image = "filter_riding.tex", },
{name = "WINTER", atlas = GetCraftingMenuAtlas, image = "filter_winter.tex", },
{name = "SUMMER", atlas = GetCraftingMenuAtlas, image = "filter_summer.tex", },
{name = "RAIN", atlas = GetCraftingMenuAtlas, image = "filter_rain.tex", },
{name = "EVERYTHING", atlas = GetCraftingMenuAtlas, image = "filter_none.tex", show_hidden = true},
}
下面就是很多插入某个过滤器的Recipe名字的表
CRAFTING_FILTERS.TOOLS.recipes =
{
"axe",
"pickaxe",
"shovel",
"hammer",
...
}
最后设置CRAFTING_FILTERS每个recipe对应的default_sort_values 排序值 table.invert这个没查到啊,只能猜,有没有大佬告知一下是什么意思
for _, filter in pairs(CRAFTING_FILTERS) do
if filter.recipes ~= nil then
filter.default_sort_values = table.invert(filter.recipes)
end
end
--这里就没仔细追了,看名字的含义就是从TheCraftingMenuProfile这里取出:被你设为常用的(喜爱的)物品GetFavorites()
--玩过游戏应该都能猜出来把
CRAFTING_FILTERS.FAVORITES.recipes = function() return TheCraftingMenuProfile:GetFavorites() end
--设置其排序顺序
CRAFTING_FILTERS.FAVORITES.default_sort_values = function() return TheCraftingMenuProfile:GetFavoritesOrder() end