rons0n Posted September 9, 2017 Share Posted September 9, 2017 (edited) So there isn't a lot of mods with custom tech trees and i guess there's a reason why for this? It seems all custom tech trees ive seen on mods all use the same method of making a tech tree, including my new character. I tested 3 mods with custom tech trees, my Shallotte Mod, Windy Plains Mod, and the felix mod. They all lead to this(Or something closely similar): [00:04:15]: [string "../mods/workshop-923823873/custom_tech_tree..."]:134: attempt to compare number with nil LUA ERROR stack traceback: ../mods/workshop-923823873/custom_tech_tree.lua:134 in (field) fn (Lua) <130-144> scripts/scheduler.lua:177 in (method) OnTick (Lua) <155-207> scripts/scheduler.lua:371 in (global) RunScheduler (Lua) <369-377> scripts/update.lua:170 in () ? (Lua) <149-228> I dont know who was the originator of the custom_tech_tree.lua but i was wondering if anyone can fix this common bug with all custom tech trees? Supposedly it was originally compatible but i keep encountering this error when i enable two mods with tech trees. --[[ INSTRUCTIONS: I. Common stuff: 1) Copy custom_tech_tree.lua to your mod directory. 2) Add a line to your modmain: modimport "custom_tech_tree.lua" 3) After that add one of these lines to modmain: AddNewTechTree("YOUR_TREE_NAME") AddNewTechTree("ANOTHER_TREE_NAME",3) --makes 3 levels, e.g. ANOTHER_TREE_NAME_ONE, also _TWO and _THREE etc. The number means number of levels. II. Structure prefab: 1) Add "prototyper" tag to crafting structure in pristine state (before SetPristine). inst:AddTag("prototyper") 2) Also you may add "giftmachine" tag if you want. 3) Add prototyper: inst:AddComponent("prototyper") inst.components.prototyper.trees = TUNING.PROTOTYPER_TREES.YOUR_TREE_NAME_ONE III. Add a recipe: AddRecipe("your_item_prefab", { Ingredient("cutgrass", 1), }, RECIPETABS.TOOLS, TECH.YOUR_TREE_NAME_ONE, nil, nil, nil, nil, nil, "images/your_altas_name.xml") IV. Add text constants (you can support a few languages if you like): STRINGS.UI.CRAFTING.YOUR_TREE_NAME_ONE = "You need a <custom structure name> to make it." --]] _G=GLOBAL if _G.rawget(_G,"AddNewTechTree") then --Make compatible with other mods. AddNewTechTree = _G.AddNewTechTree --adding to the env of the mod. return end --Prepare variables. Save existing environment. local db = {} -- db.MAGIC == 0 local db_new_techs = {} -- Only new trees, e.g. db_new_techs.NEWTREE == 0 local db_new_classified = {} --e.g. db_new_classified.NEWTREE == "custom_NEWTREE_level" local db_new_builder_name = {} -- e.g. db_new_builder_name.NEWTREE == "builder.accessible_tech_trees.NEWTREE" for k,v in pairs(_G.TECH.NONE) do --initialize (copy) db[k] = 0 end if TUNING.PROTOTYPER_TREES then --just for sure for k,v in pairs(TUNING.PROTOTYPER_TREES.SCIENCEMACHINE) do db[k] = 0 end end local function UpdateDB(newtree_name) --add new element to our table, create common stuff db[newtree_name] = 0 db_new_techs[newtree_name] = 0 db_new_classified[newtree_name] = "custom_" .. newtree_name .. "_level" db_new_builder_name[newtree_name] = "builder.accessible_tech_trees." .. newtree_name end --Custom little hack instrument. local getupvalue, setupvalue, getinfo = _G.debug.getupvalue, _G.debug.setupvalue, _G.debug.getinfo local function inject_local(fn, local_fn_name) print("INJECT... Trying to find",local_fn_name) local info = getinfo(fn, "u") local nups = info and info.nups for i = 1, nups do local name, val = getupvalue(fn, i) if (name == local_fn_name) then return val, i end end print("CRITICAL ERROR: Can't find variable "..tostring(upvalue_name).."!") end --Thanks rezecib for the all places in the code: --http://forums.kleientertainment.com/topic/69813-how-to-make-custom-tech-tree/ --1) constants, TECH.NONE.NEWTREE = 0, also TECH.NEWTREE_ONE, etc (these are optional, but get used in the recipes a lot) local function AddNewTechConstants(newtree_name) _G.TECH.NONE[newtree_name] = 0 _G.TECH.LOST[newtree_name] = 10 if TUNING.PROTOTYPER_TREES then for k,tbl in pairs(TUNING.PROTOTYPER_TREES) do tbl[newtree_name] = 0 end end end local TECH_LEVELS = {'_ONE','_TWO','_THREE','_FOUR','_FIVE'} -- e.g. NEWTREE_ONE local saved_tech_names = {} --for text hint in GetHintTextForRecipe local function AddTechLevel(newtree_name, level) level = level or 1 local level_name if TECH_LEVELS[level] then level_name = newtree_name .. TECH_LEVELS[level] else level_name = newtree_name .. "_" .. tostring(level) end --Save new name if not saved_tech_names[newtree_name] then saved_tech_names[newtree_name] = {} end saved_tech_names[newtree_name][level] = level_name _G.TECH[level_name] = {[newtree_name] = level} --for using in recipes --for using in new crafting structures: if TUNING.PROTOTYPER_TREES then local new_tree = {} --make new instance for k,v in pairs(db) do --copy old data to new instance new_tree[k] = v end new_tree[newtree_name] = level --> make structure useful TUNING.PROTOTYPER_TREES[level_name] = new_tree end end --2) components/builder:EvaluateTechTrees makes specific mention to each of the trees in order to bring in self.bonus. Looks like it might involve a messy override in order to add intrinsic character bonuses to a tree, because it does both the check and the result without any functions in between to hook into... You could maybe do it by replacing self.accessible_tech_trees with a proxy table that has a metatable so you can intercept the assignment? That's pretty gross, though. --NB: No bonuses for custom tech trees! //star --But we still to check if player goes away (over time). local prototyper = _G.require "components/prototyper" local old_TurnOn = prototyper.TurnOn function prototyper:TurnOn(doer, ...) if doer.task_custom_tech then doer.task_custom_tech:Cancel() end doer.task_custom_tech = doer:DoTaskInTime(1.5,function(player) local trees_changed = false local tech_tree = player.components.builder.accessible_tech_trees for tech_name, _ in pairs(db_new_techs) do if tech_tree[tech_name] > 0 then trees_changed = true tech_tree[tech_name] = 0 end end if trees_changed then player:PushEvent("techtreechange", {level = tech_tree}) player.replica.builder:SetTechTrees(tech_tree) end player.task_custom_tech = nil end) return old_TurnOn(self,doer, ...) end --3) components/builder_replica has Setters and Getters for each tree's intrinsic bonuses, these are called later in recipepopup local replica = _G.require "components/builder_replica" local old_SetTechTrees = replica.SetTechTrees function replica:SetTechTrees(techlevels,...) if self.classified ~= nil then for tech_name,v in pairs(db_new_techs) do self.classified[db_new_classified[tech_name] ]:set(techlevels[tech_name] or 0) end end return old_SetTechTrees(self,techlevels,...) end --4) KnowsRecipe explicitly checks each tree in both builder and builder_replica --No custom bonus. --So we should just restrict custom tech trees as bonus. local AllRecipes = _G.AllRecipes local builder = _G.require "components/builder" local old_KnowsRecipe = builder.KnowsRecipe function builder:KnowsRecipe(recname) local result = old_KnowsRecipe(self, recname) if result then if self.freebuildmode or table.contains(self.recipes, recname) then return true end local recipe = AllRecipes[recname] for tech_name,v in pairs(db_new_techs) do if recipe.level[tech_name] > 0 then return false --no custom bonus end end end return result end local old_KnowsRecipe_replica = replica.KnowsRecipe function replica:KnowsRecipe(recname) if self.inst.components.builder ~= nil then return self.inst.components.builder:KnowsRecipe(recname) end local result = old_KnowsRecipe_replica(self, recname) if result then if self.classified.isfreebuildmode:value() or self.classified.recipes[recname] ~= nil and self.classified.recipes[recname]:value() then return true end local recipe = AllRecipes[recname] for tech_name,v in pairs(db_new_techs) do if recipe.level[tech_name] > 0 then return false --no custom bonus end end end return result end --5) components/prototyper constructor, self.trees.NEWTREE = 0, and have any prototyper prefabs you make increase that AddClassPostConstruct("components/prototyper", function(this) --increase table used by :GetTechTrees function. this.trees = {} for k,v in pairs(db) do --db is already prepared for copying. this.trees[k] = v end end) --6) prefabs/player_classified OnTechTreesDirty checks each of them explicitly. You could add a listener for "techtreesdirty" and make your check there. Also inst.newtreelevel would need to be added. --Need to inject in local function OnTechTreesDirty and replace it. --Pricey hack, so should be done only once per game session. local function PlayerClassifiedHack() local RegisterNetListeners_fn = inject_local(_G.Prefabs.player_classified.fn, "RegisterNetListeners") local OnTechTreesDirty_fn_old, var_num = inject_local(RegisterNetListeners_fn, "OnTechTreesDirty") local OnTechTreesDirty_fn_new = function(inst) for tech_name,v in pairs(db_new_techs) do if inst[db_new_classified[tech_name] ] == nil then print("error: inst."..db_new_classified[tech_name].." == nil") else inst.techtrees[tech_name] = inst[db_new_classified[tech_name] ]:value() end end return OnTechTreesDirty_fn_old(inst) end setupvalue(RegisterNetListeners_fn, var_num, OnTechTreesDirty_fn_new) end local player_classified_hacked = false AddPrefabPostInit("world",function(w) if not player_classified_hacked then player_classified_hacked = true PlayerClassifiedHack() end end) --Create network variables. local net_tinybyte = _G.net_tinybyte AddPrefabPostInit("player_classified",function(inst) --print("ADDPREFABPOSTINIT player_classified") for tech_name,v in pairs(db_new_techs) do inst[db_new_classified[tech_name] ] = net_tinybyte(inst.GUID, db_new_builder_name[tech_name], "techtreesdirty") end end) --7) recipe, self.level.NEWTREE = self.level.NEWTREE or 0 needs to be added, could be done by overriding Recipe._ctor, or doing an AddClassPostConstruct (maybe? the recipe.lua file doesn't return the class though, so maybe not) AddPrefabPostInit("world",function(w) --Fixing all recipes. Just for sure. for rec_name, recipe in pairs(_G.AllRecipes) do for tree_name,_ in pairs(db_new_techs) do recipe.level[tree_name] = recipe.level[tree_name] or 0 end end end) --8) tuning, PROTOTYPER_TREES needs to have each of its entries modified (although I think if you don't they should default to zero from the other changes) -- (+) Done in AddTechLevel(). --9) widgets/recipepopup GetHintTextForRecipe takes into account player intrinsic bonuses to trees for the help text. This looks like it might need to be overridden local save_hint_recipe --We need just a link to recipe. do local recipepopup = _G.require "widgets/recipepopup" local RecipePopup_Refresh_fn = recipepopup.Refresh local old_GetHintTextForRecipe, num_var = inject_local(RecipePopup_Refresh_fn,"GetHintTextForRecipe") if old_GetHintTextForRecipe then setupvalue(RecipePopup_Refresh_fn, num_var, function(player, recipe) save_hint_recipe = recipe return old_GetHintTextForRecipe(player, recipe) end) end end --Here we can use the link. local CRAFTING = _G.STRINGS.UI.CRAFTING --See STRINGS.UI.CRAFTING.NEEDSCIENCEMACHINE AddClassPostConstruct("widgets/recipepopup",function(self) local old_SetString = self.teaser.SetString function self.teaser:SetString(str) --print("Show text",str,tostring(save_hint_recipe)) if str == "Text not found." and save_hint_recipe ~= nil then --Probably custom recipe local custom_tech, custom_level for tech_name, _ in pairs(db_new_techs) do --Check if it's really custom. custom_level = save_hint_recipe.level[tech_name] if custom_level > 0 then custom_tech = tech_name break end end if custom_tech then str = CRAFTING[saved_tech_names[custom_tech][custom_level] ] or str end end return old_SetString(self,str) end end) --Main function of the lib. function AddNewTechTree(newtree_name, num_levels) UpdateDB(newtree_name) --update local tables AddNewTechConstants(newtree_name) --_G.TECH.NEWTREE num_levels = num_levels or 1 for i = 1, num_levels do --_G.TECH.NEWTREE_ONE and TUNING.PROTOTYPER_TREES.NEWTREE_ONE, two, three etc. AddTechLevel(newtree_name, i) end end --Global define _G.AddNewTechTree = AddNewTechTree Edited September 9, 2017 by rons0n Link to comment Share on other sites More sharing options...
Lumina Posted September 9, 2017 Share Posted September 9, 2017 I'm not sure, but i think i saw Maris post a fix about this problem some months ago. Link to comment Share on other sites More sharing options...
rons0n Posted September 10, 2017 Author Share Posted September 10, 2017 Is it this one? The one Maris posted here is the one everyone is using within their own custom tech trees. There was another modder while I was searching through the fourms called Zarklord who made his own custom tech tree but when testing it in a non-dedicated server it didnt work.(I think anyway, the way I used it didnt work). @Maris Is there a way to update the custom_tech_tree.lua, as it seems to no longer be compatible with other custom tech tree mods. Link to comment Share on other sites More sharing options...
Lumina Posted September 10, 2017 Share Posted September 10, 2017 I think i saw something else but i could be wrong. Link to comment Share on other sites More sharing options...
rons0n Posted September 10, 2017 Author Share Posted September 10, 2017 (edited) Alright just turns out the windy plains mod doesnt seem to like other Prototypers. If anyone out here plans on making a custom prototyper I suggest using Felix, <Defaults>, or mine(which is pretty much Felix) way of making prototypers. just giving an FYI Edited September 11, 2017 by rons0n Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now