Advertisement
Guest User

custom_tech_tree.lua

a guest
Aug 28th, 2016
467
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. --[[ INSTRUCTIONS:
  2. I. Common stuff:
  3. 1) Copy custom_tech_tree.lua to your mod directory.
  4. 2) Add a line to your modmain:
  5.     modimport "custom_tech_tree.lua"
  6. 3) After that add one of these lines to modmain:
  7.     AddNewTechTree("YOUR_TREE_NAME")
  8.     AddNewTechTree("ANOTHER_TREE_NAME",3) --makes 3 levels, e.g. ANOTHER_TREE_NAME_ONE, also _TWO and _THREE
  9.     etc. The number means number of levels.
  10.  
  11. II. Structure prefab:
  12. 1) Add "prototyper" tag to crafting structure in pristine state (before SetPristine).
  13.     inst:AddTag("prototyper")
  14. 2) Also you may add "giftmachine" tag if you want.
  15. 3) Add prototyper:
  16.     inst:AddComponent("prototyper")
  17.     inst.components.prototyper.trees = TUNING.PROTOTYPER_TREES.YOUR_TREE_NAME_ONE
  18.  
  19. III. Add a recipe:
  20.     AddRecipe("your_item_prefab", { Ingredient("cutgrass", 1), },
  21.     RECIPETABS.TOOLS, TECH.YOUR_TREE_NAME_ONE,
  22.     nil, nil, nil, nil, nil,
  23.     "images/your_altas_name.xml")
  24.  
  25. IV. Add text constants (you can support a few languages if you like):
  26.     STRINGS.UI.CRAFTING.YOUR_TREE_NAME_ONE = "You need a <custom structure name> to make it."
  27.  
  28. --]]
  29. _G=GLOBAL
  30.  
  31. if _G.rawget(_G,"AddNewTechTree") then --Make compatible with other mods.
  32.     AddNewTechTree = _G.AddNewTechTree --adding to the env of the mod.
  33.     return
  34. end
  35.  
  36. --Prepare variables. Save existing environment.
  37.  
  38. local db = {} -- db.MAGIC == 0
  39. local db_new_techs = {} -- Only new trees, e.g. db_new_techs.NEWTREE == 0
  40. local db_new_classified = {} --e.g.  db_new_classified.NEWTREE == "custom_NEWTREE_level"
  41. local db_new_builder_name = {} -- e.g. db_new_builder_name.NEWTREE == "builder.accessible_tech_trees.NEWTREE"
  42.  
  43. for k,v in pairs(_G.TECH.NONE) do --initialize (copy)
  44.     db[k] = 0
  45. end
  46. if TUNING.PROTOTYPER_TREES then --just for sure
  47.     for k,v in pairs(TUNING.PROTOTYPER_TREES.SCIENCEMACHINE) do
  48.         db[k] = 0
  49.     end
  50. end
  51.  
  52. local function UpdateDB(newtree_name) --add new element to our table, create common stuff
  53.     db[newtree_name] = 0
  54.     db_new_techs[newtree_name] = 0
  55.     db_new_classified[newtree_name] = "custom_" .. newtree_name .. "_level"
  56.     db_new_builder_name[newtree_name] = "builder.accessible_tech_trees." .. newtree_name
  57. end
  58.  
  59. --Custom little hack instrument.
  60. local getupvalue, setupvalue, getinfo = _G.debug.getupvalue, _G.debug.setupvalue, _G.debug.getinfo
  61. local function inject_local(fn, local_fn_name)
  62.     print("INJECT... Trying to find",local_fn_name)
  63.     local info = getinfo(fn, "u")
  64.     local nups = info and info.nups
  65.     for i = 1, nups do
  66.         local name, val = getupvalue(fn, i)
  67.         if (name == local_fn_name) then
  68.             return val, i
  69.         end
  70.     end
  71.     print("CRITICAL ERROR: Can't find variable "..tostring(upvalue_name).."!")
  72. end
  73.  
  74.  
  75.  
  76. --Thanks rezecib for the all places in the code:
  77. --http://forums.kleientertainment.com/topic/69813-how-to-make-custom-tech-tree/
  78. --1) constants, TECH.NONE.NEWTREE = 0, also TECH.NEWTREE_ONE, etc (these are optional, but get used in the recipes a lot)
  79.  
  80. local function AddNewTechConstants(newtree_name)
  81.     _G.TECH.NONE[newtree_name] = 0
  82.     _G.TECH.LOST[newtree_name] = 10
  83.     if TUNING.PROTOTYPER_TREES then
  84.         for k,tbl in pairs(TUNING.PROTOTYPER_TREES) do
  85.             tbl[newtree_name] = 0
  86.         end
  87.     end
  88. end
  89.  
  90. local TECH_LEVELS = {'_ONE','_TWO','_THREE','_FOUR','_FIVE'} -- e.g. NEWTREE_ONE
  91. local saved_tech_names = {} --for text hint in GetHintTextForRecipe
  92. local function AddTechLevel(newtree_name, level)
  93.     level = level or 1
  94.     local level_name
  95.     if TECH_LEVELS[level] then
  96.         level_name = newtree_name .. TECH_LEVELS[level]
  97.     else
  98.         level_name = newtree_name .. "_" .. tostring(level)
  99.     end
  100.     --Save new name
  101.     if not saved_tech_names[newtree_name] then
  102.         saved_tech_names[newtree_name] = {}
  103.     end
  104.     saved_tech_names[newtree_name][level] = level_name
  105.     _G.TECH[level_name] = {[newtree_name] = level} --for using in recipes
  106.     --for using in new crafting structures:
  107.     if TUNING.PROTOTYPER_TREES then
  108.         local new_tree = {} --make new instance
  109.         for k,v in pairs(db) do --copy old data to new instance
  110.             new_tree[k] = v
  111.         end
  112.         new_tree[newtree_name] = level --> make structure useful
  113.         TUNING.PROTOTYPER_TREES[level_name] = new_tree
  114.     end
  115. end
  116.  
  117.  
  118.  
  119. --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.
  120.  
  121. --NB: No bonuses for custom tech trees! //star
  122.  
  123. --But we still to check if player goes away (over time).
  124. local prototyper = _G.require "components/prototyper"
  125. local old_TurnOn = prototyper.TurnOn
  126. function prototyper:TurnOn(doer, ...)
  127.     if doer.task_custom_tech then
  128.         doer.task_custom_tech:Cancel()
  129.     end
  130.     doer.task_custom_tech = doer:DoTaskInTime(1.5,function(player)
  131.         local trees_changed = false
  132.         local tech_tree = player.components.builder.accessible_tech_trees
  133.         for tech_name, _ in pairs(db_new_techs) do
  134.             if tech_tree[tech_name] > 0 then
  135.                 trees_changed = true
  136.                 tech_tree[tech_name] = 0
  137.             end
  138.         end
  139.         if trees_changed then
  140.             player:PushEvent("techtreechange", {level = tech_tree})
  141.             player.replica.builder:SetTechTrees(tech_tree)
  142.         end
  143.         player.task_custom_tech = nil
  144.     end)
  145.     return old_TurnOn(self,doer, ...)
  146. end
  147.  
  148.  
  149.  
  150. --3) components/builder_replica has Setters and Getters for each tree's intrinsic bonuses, these are called later in recipepopup
  151.  
  152. local replica = _G.require "components/builder_replica"
  153. local old_SetTechTrees = replica.SetTechTrees
  154. function replica:SetTechTrees(techlevels,...)
  155.     if self.classified ~= nil then
  156.         for tech_name,v in pairs(db_new_techs) do
  157.             self.classified[db_new_classified[tech_name] ]:set(techlevels[tech_name] or 0)
  158.         end
  159.     end
  160.     return old_SetTechTrees(self,techlevels,...)
  161. end
  162.  
  163.  
  164.  
  165. --4) KnowsRecipe explicitly checks each tree in both builder and builder_replica
  166.  
  167. --No custom bonus.
  168. --So we should just restrict custom tech trees as bonus.
  169.  
  170. local AllRecipes = _G.AllRecipes
  171.  
  172. local builder = _G.require "components/builder"
  173. local old_KnowsRecipe = builder.KnowsRecipe
  174. function builder:KnowsRecipe(recname)
  175.     local result = old_KnowsRecipe(self, recname)
  176.     if result then
  177.         if self.freebuildmode or table.contains(self.recipes, recname) then
  178.             return true
  179.         end
  180.         local recipe = AllRecipes[recname]
  181.         for tech_name,v in pairs(db_new_techs) do
  182.             if recipe.level[tech_name] > 0 then
  183.                 return false --no custom bonus
  184.             end
  185.         end
  186.     end
  187.     return result
  188. end
  189.  
  190. local old_KnowsRecipe_replica = replica.KnowsRecipe
  191. function replica:KnowsRecipe(recname)
  192.     if self.inst.components.builder ~= nil then
  193.         return self.inst.components.builder:KnowsRecipe(recname)
  194.     end
  195.     local result = old_KnowsRecipe_replica(self, recname)
  196.     if result then
  197.         if self.classified.isfreebuildmode:value() or
  198.             self.classified.recipes[recname] ~= nil and self.classified.recipes[recname]:value()
  199.         then
  200.             return true
  201.         end
  202.         local recipe = AllRecipes[recname]
  203.         for tech_name,v in pairs(db_new_techs) do
  204.             if recipe.level[tech_name] > 0 then
  205.                 return false --no custom bonus
  206.             end
  207.         end
  208.     end
  209.     return result
  210. end
  211.  
  212.  
  213.  
  214. --5) components/prototyper constructor, self.trees.NEWTREE = 0, and have any prototyper prefabs you make increase that
  215.  
  216. AddClassPostConstruct("components/prototyper", function(this) --increase table used by :GetTechTrees function.
  217.     this.trees = {}
  218.     for k,v in pairs(db) do --db is already prepared for copying.
  219.         this.trees[k] = v
  220.     end
  221. end)
  222.  
  223.  
  224.  
  225. --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.
  226.  
  227. --Need to inject in local function OnTechTreesDirty and replace it.
  228. --Pricey hack, so should be done only once per game session.
  229. local function PlayerClassifiedHack()
  230.     local RegisterNetListeners_fn = inject_local(_G.Prefabs.player_classified.fn, "RegisterNetListeners")
  231.     local OnTechTreesDirty_fn_old, var_num = inject_local(RegisterNetListeners_fn, "OnTechTreesDirty")
  232.     local OnTechTreesDirty_fn_new = function(inst)
  233.         for tech_name,v in pairs(db_new_techs) do
  234.             if inst[db_new_classified[tech_name] ] == nil then
  235.                 print("error: inst."..db_new_classified[tech_name].." == nil")
  236.             else
  237.                 inst.techtrees[tech_name] = inst[db_new_classified[tech_name] ]:value()
  238.             end
  239.         end
  240.         return OnTechTreesDirty_fn_old(inst)
  241.     end
  242.     setupvalue(RegisterNetListeners_fn, var_num, OnTechTreesDirty_fn_new)
  243. end
  244. local player_classified_hacked = false
  245. AddPrefabPostInit("world",function(w)
  246.     if not player_classified_hacked then
  247.         player_classified_hacked = true
  248.         PlayerClassifiedHack()
  249.     end
  250. end)
  251.  
  252. --Create network variables.
  253. local net_tinybyte = _G.net_tinybyte
  254. AddPrefabPostInit("player_classified",function(inst) --print("ADDPREFABPOSTINIT player_classified")
  255.     for tech_name,v in pairs(db_new_techs) do
  256.         inst[db_new_classified[tech_name] ] = net_tinybyte(inst.GUID, db_new_builder_name[tech_name], "techtreesdirty")
  257.     end
  258. end)
  259.  
  260.  
  261.  
  262. --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)
  263.  
  264. AddPrefabPostInit("world",function(w) --Fixing all recipes. Just for sure.
  265.     for rec_name, recipe in pairs(_G.AllRecipes) do
  266.         for tree_name,_ in pairs(db_new_techs) do
  267.             recipe.level[tree_name] = recipe.level[tree_name] or 0
  268.         end
  269.     end
  270. end)
  271.  
  272.  
  273.  
  274. --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)
  275.  
  276. -- (+) Done in AddTechLevel().
  277.  
  278. --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
  279.  
  280. local save_hint_recipe
  281.  
  282. --We need just a link to recipe.
  283. do
  284.     local recipepopup = _G.require "widgets/recipepopup"
  285.     local RecipePopup_Refresh_fn = recipepopup.Refresh
  286.     local old_GetHintTextForRecipe, num_var = inject_local(RecipePopup_Refresh_fn,"GetHintTextForRecipe")
  287.     if old_GetHintTextForRecipe then
  288.         setupvalue(RecipePopup_Refresh_fn, num_var, function(player, recipe)
  289.             save_hint_recipe = recipe
  290.             return old_GetHintTextForRecipe(player, recipe)
  291.         end)
  292.     end
  293. end
  294.  
  295. --Here we can use the link.
  296. local CRAFTING = _G.STRINGS.UI.CRAFTING --See STRINGS.UI.CRAFTING.NEEDSCIENCEMACHINE
  297. AddClassPostConstruct("widgets/recipepopup",function(self)
  298.     local old_SetString = self.teaser.SetString
  299.     function self.teaser:SetString(str)
  300.         --print("Show text",str,tostring(save_hint_recipe))
  301.         if str == "Text not found." and save_hint_recipe ~= nil  then --Probably custom recipe
  302.             local custom_tech, custom_level
  303.             for tech_name, _ in pairs(db_new_techs) do --Check if it's really custom.
  304.                 custom_level = save_hint_recipe.level[tech_name]
  305.                 if custom_level > 0 then
  306.                     custom_tech = tech_name
  307.                     break
  308.                 end
  309.             end
  310.             if custom_tech then
  311.                 str = CRAFTING[saved_tech_names[custom_tech][custom_level] ] or str
  312.             end
  313.         end
  314.         return old_SetString(self,str)
  315.     end
  316. end)
  317.  
  318.  
  319.  
  320. --Main function of the lib.
  321. function AddNewTechTree(newtree_name, num_levels)
  322.     UpdateDB(newtree_name) --update local tables
  323.     AddNewTechConstants(newtree_name) --_G.TECH.NEWTREE
  324.     num_levels = num_levels or 1
  325.     for i = 1, num_levels do --_G.TECH.NEWTREE_ONE and TUNING.PROTOTYPER_TREES.NEWTREE_ONE, two, three etc.
  326.         AddTechLevel(newtree_name, i)
  327.     end
  328. end
  329.  
  330. --Global define
  331. _G.AddNewTechTree = AddNewTechTree
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement