squeek Posted November 12, 2013 Share Posted November 12, 2013 I just felt like documenting a few things I've picked up along the way while modding Don't Starve. These are things I feel modders need to have in their arsenal, but that may be somewhat hidden, unintuitive, not documented, and/or hard to find the answers to.If you have things to add, please post about them here and I might add them to this list.A working example of all the stuff mentioned can be found here:Example: Code Tips And Tricksrequire is required The require function of Lua is an extremely handy tool if taken advantage of. Whenever it loads a file, it stores the result (whatever the file returns) in a table (package.loaded), and any subsequent require calls with the same module name (you can think of module name as filename for now if you're unfamiliar with Lua modules) will give you a reference to the previous result.This becomes very useful specifically for components, screens, widgets, and basically anything that defines a Class (because they will almost always return the Class at the end of the file). Since require caches its results and gives you a reference to that cached result, if you retrieve the result and modify it, you then also modify the result of any future require calls (and Don't Starve will usually use require to load these files).With this, you can do things like:-- if in the mod environment (modmain.lua), you need to get require from the global environmentlocal require = GLOBAL.require-- Deployable becomes a reference to the cached result stored in package.loaded["components/deployable"]local Deployable = require "components/deployable"-- Store the existing function if it exists, otherwise store a dummy function as a fail-safelocal Deployable_CanDeploy_base = Deployable.CanDeploy or function() return true endfunction Deployable:CanDeploy( pt ) -- Get the result of the base CanDeploy function -- Note: Deployable_CanDeploy_base is called with the self parameter because x:fn() is simply a shortcut for x.fn(x) with an implied self parameter local can_deploy = Deployable_CanDeploy_base( self, pt ) -- Add in our own deployment requirement can_deploy = can_deploy and pt == GLOBAL.Point(0,0,0) return can_deployendNote: This is way faster than using AddComponentPostInit, as instead of overwriting function(s) for each and every component that comes into existence, this just does that modification one time up front and that's it. As far as I'm concerned, this should be the preferred method of extending/overwriting any Don't Starve Class definition.Adding components to all playerscredit to simplex for this workaroundThis workaround is no longer necessary. Use AddPlayerPostInit instead.If you add a component to a player prefab using AddSimPostInit, OnSave and OnLoad for that component will simply not work (note: this is only the case for SimPostInit, not PrefabPostInit). I have not investigated the reason why, but to get around it you should use the following workaround: -- need to add the component in here, otherwise OnSave doesn't work rightAddPrefabPostInit("world", function(inst) GLOBAL.assert( GLOBAL.GetPlayer() == nil ) local player_prefab = GLOBAL.SaveGameIndex:GetSlotCharacter() -- Unfortunately, we can't add new postinits by now. So we have to do -- it the hard way... GLOBAL.TheSim:LoadPrefabs( {player_prefab} ) local oldfn = GLOBAL.Prefabs[player_prefab].fn GLOBAL.Prefabs[player_prefab].fn = function() local inst = oldfn() -- Add components here. inst:AddComponent("yourcomponent") return inst endend)Utilizing zero-time schedulesSometimes you may need access to something that doesn't quite exist yet. For example, a prefab's Transform will not yet be initialized in a PrefabPostInit function or a prefab's fn constructor function. So, let's say you wanted to do something based on what tile type something spawned on. The easy way to do this is to create a task that will be executed in 0 time, which essentially waits one frame before executing the callback function you provide it (usually enough for what you want to start existing). AddPrefabPostInit( "berrybush", function( inst ) -- inst.Transform:GetWorldPosition() will return 0,0,0 here -- this will wait one frame, which is enough time to have inst.Transform initialized inst:DoTaskInTime( 0, function() -- inst.Transform:GetWorldPosition() will give you the correct position now end )end )Cross-mod interactionSome handy functions for detecting if other mods are enabled, getting theirenvironments (and potentially manipulating them), and checking if they have beenloaded yet or notlocal _G = _G or GLOBAL-- returns the mod environment of the mod with the given name or IDfunction GetMod(name_or_id) for _, mod in ipairs( _G.ModManager.mods ) do -- note: mod.modname is the mod directory name if mod.modinfo.id == name_or_id or mod.modinfo.name == name_or_id then return mod end end return nilend-- returns whether or not a mod with the given name or ID is enabledfunction IsModEnabled(name_or_id) return GetMod(name_or_id) ~= nilend-- returns whether or not a mod with the given name or mod environment has been loaded yet-- 'loaded' means that its modmain.lua has been runfunction IsModLoaded(name_or_mod) local mod = type(name_or_mod) == "string" and GetMod(name_or_mod) or name_or_mod if mod then return table.contains( _G.ModManager:GetEnabledModNames(), mod.modname ) end return falseend Link to comment Share on other sites More sharing options...
simplex Posted November 12, 2013 Share Posted November 12, 2013 require is required So true, so true... If you add a component to a player prefab using AddSimPostInit, OnSave and OnLoad for that component will simply not work (note: this is only the case for SimPostInit, not PrefabPostInit). I have not investigated the reason why [...] The reason is when the SimPostInit is fired, the savedata has already been loaded and passed to the existing OnLoad methods at the time, so it's too late for an added component to catch any savedata. OnSave will actually work, but since OnLoad doesn't it is pointless. Utilizing zero-time schedules One more importantant use case of zero-time schedules in prefab post inits is that when they run the savedata for the prefab and its components has already been loaded, which ensures both that you'll have access to the proper loaded state of the prefab and that OnLoad/OnLoadPostPass/LoadPostPass methods won't override changes you make. Very useful tips, that will certainly be essential for many modders. Link to comment Share on other sites More sharing options...
Nycidian Posted November 12, 2013 Share Posted November 12, 2013 In addendum to your mention of require... Many lua functions will not work without loading the correct requirements first, for example... math = require "math"string = require "string"table = require "table" These allow you to do some very important functions such as math.floor() or string.find() or table.insert() Apparently I'm an idiot, you do not need to require these though you do need to access them through the GLOBAL variable, like so. math = GLOBAL.mathstring = GLOBAL.stringtable = GLOBAL.table /Sits Quietly in the corner Link to comment Share on other sites More sharing options...
Malacath Posted November 12, 2013 Share Posted November 12, 2013 In addendum to your mention of require... Many lua functions will not work without loading the correct requirements first, for example... math = require "math"string = require "string"table = require "table" These allow you to do some very important functions such as math.floor() or string.find() or table.insert()Really o.0I never "require" any of these and they always work. I assumed that they were either required in a very basic vanilla file or are just standard. Link to comment Share on other sites More sharing options...
simplex Posted November 12, 2013 Share Posted November 12, 2013 Really o.0I never "require" any of these and they always work. I assumed that they were either required in a very basic vanilla file or are just standard.Those are all pre-required by Lua itself* when it starts, and they are placed in the mod environment by the game. So you don't need to require() them, but at the same time it is harmless to do so. For other modules of the standard library, writing things likedebug = GLOBAL.require "debug"is functionally identical todebug = GLOBAL.debugfrom within the mod environment (in the global one, once again it is redundant/harmless).* Aaaaactually, since Lua is embedded in the engine, it is it that calls luaL_openlibs(L) to load them, but I digress, the point is they have been require()'d already before any code runs. Link to comment Share on other sites More sharing options...
Nycidian Posted November 12, 2013 Share Posted November 12, 2013 Those are all pre-required by Lua itself* when it starts, and they are placed in the mod environment by the game. So you don't need to require() them, but at the same time it is harmless to do so. For other modules of the standard library, writing things likedebug = GLOBAL.require "debug"is functionally identical todebug = GLOBAL.debugfrom within the mod environment (in the global one, once again it is redundant/harmless).* Aaaaactually, since Lua is embedded in the engine, it is it that calls luaL_openlibs(L) to load them, but I digress, the point is they have been require()'d already before any code runs. weird cause when i don't require them i get errors... Ah I see what you mean so instead of doing the require i can do math = GLOBAL.mathstring = GLOBAL.stringtable = GLOBAL.table That will teach me to offer any knowledge about lua, I know what works but I honestly rarely know why as all my knowledge has been earned by looking at code and messing with my code until it runs. Sometimes like the above I come up with solutions that work but are not the correct solutions. @simplex&@squeek Speaking of GLOBAL do you you guys know why sometimes I can't access the GLOBAL variable from files for example I built a component file and I could not access it I ended up having to create a funtion that passed GLOBAL to my compnent and stored it so my functions could use it. ../mods/amanager/scripts/components/amanager_2.lua:258: variable 'GLOBAL' is not declaredLUA ERROR stack traceback: =[C] in function 'error'../data/scripts/strict.lua(23,1)../mods/amanager/scripts/components/amanager_2.lua(258,1) in function 'EvalExplored'This function isn't running while loading it happens during the game so GLOBAL definitely exists but for some reason the component can't access the variable GLOBAL. Link to comment Share on other sites More sharing options...
simplex Posted November 12, 2013 Share Posted November 12, 2013 Ah I see what you mean so instead of doing the require i can do math = GLOBAL.mathstring = GLOBAL.stringtable = GLOBAL.tableYou don't need that. Those are already in the mod environment. Not every module of the standard library is, but those are.From mods.lua:function CreateEnvironment(modname) local modutil = require("modutil") require("recipe") -- for Ingredient local env = { TUNING=TUNING, CHARACTERLIST = CHARACTERLIST, modname = modname, pairs = pairs, ipairs = ipairs, print = print, math = math, table = table, type = type, string = string, tostring = tostring, Class = Class, GLOBAL = _G, MODROOT = "../mods/"..modname.."/", Prefab = Prefab, Asset = Asset, Ingredient = Ingredient, } env.env = env --install our crazy loader! env.modimport = function(modulename) print("modimport: "..env.MODROOT..modulename) local result = kleiloadlua(env.MODROOT..modulename) if result == nil then error("Error in modimport: "..modulename.." not found!") elseif type(result) == "string" then error("Error in modimport: "..ModInfoname(modname).." importing "..modulename.."!\n"..result) else setfenv(result, env.env) result() end end modutil.InsertPostInitFunctions(env) return envend Speaking of GLOBAL do you you guys know why sometimes I can't access the GLOBAL variable from files for example I built a component file and I could not access it I ended up having to create a funtion that passed GLOBAL to my compnent and stored it so my functions could use it.Component code already runs in the global environment. Instead of writing GLOBAL.some_var just write some_var. The global environment is also stored in a variable within the global environment, called _G, but this is rarely relevant. Usually, you'd only use it in circumstances such aslocal x = "local value"_G.x = "global value" Link to comment Share on other sites More sharing options...
Nycidian Posted November 12, 2013 Share Posted November 12, 2013 You don't need that. Those are already in the mod environment. Not every module of the standard library is, but those are. Can you explain then why without requiring the string or math or table some of my mods will crash saying no such function exists? That is why i started using require on them as it happened and that is what fixed it? Thanks for the tip about GLOBAL btw Link to comment Share on other sites More sharing options...
Malacath Posted November 12, 2013 Share Posted November 12, 2013 Can you explain then why without requiring the string or math or table some of my mods will crash saying no such function exists? That is why i started using require on them as it happened and that is what fixed it? Thanks for the tip about GLOBAL btwCan you tell me one of your mods which crashes without it? 'cause I'm quite curious what's going on there. From mods.lua:function CreateEnvironment(modname) local modutil = require("modutil") require("recipe") -- for Ingredient local env = { TUNING=TUNING, CHARACTERLIST = CHARACTERLIST, modname = modname, pairs = pairs, ipairs = ipairs, print = print, math = math, table = table, type = type, string = string, tostring = tostring, Class = Class, GLOBAL = _G, MODROOT = "../mods/"..modname.."/", Prefab = Prefab, Asset = Asset, Ingredient = Ingredient, } env.env = env --install our crazy loader! env.modimport = function(modulename) print("modimport: "..env.MODROOT..modulename)local result = kleiloadlua(env.MODROOT..modulename) if result == nil then error("Error in modimport: "..modulename.." not found!") elseif type(result) == "string" then error("Error in modimport: "..ModInfoname(modname).." importing "..modulename.."!\n"..result) else setfenv(result, env.env)result()end end modutil.InsertPostInitFunctions(env) return envendWait, TUNING is in the MOD env? Link to comment Share on other sites More sharing options...
simplex Posted November 12, 2013 Share Posted November 12, 2013 Can you explain then why without requiring the string or math or table some of my mods will crash saying no such function exists? That is why i started using require on them as it happened and that is what fixed it?]Can you tell me one of your mods which crashes without it? 'cause I'm quite curious what's going on there. This. Wait, TUNING is in the MOD env? Yes, as far as I can remember. ;P Link to comment Share on other sites More sharing options...
Nycidian Posted November 12, 2013 Share Posted November 12, 2013 Can you tell me one of your mods which crashes without it? 'cause I'm quite curious what's going on there.When I can find it I will. Like I said it happened so I made assumptions because doing the require trick fixed it I then started doing it in all my files since that is what I thought fixed it though I just noticed that in some files I missed declaring those and still used the functions so had I been paying attention I would have known there was something up. Its also possible I made two changes and the other change fixed it but I almost always make one change at a time when I'm tracking down an error as multiple changes won't help me find out whats wring so honestly I doubt it but who knows. I'm going to go through my mods and remove all the requires for those and see what happens. Link to comment Share on other sites More sharing options...
Malacath Posted November 12, 2013 Share Posted November 12, 2013 Yes, as far as I can remember. ;PYes, I needed to check it myself before I believed it though xDBut why TUNING and not STRINGS? I don't get it...EDIT: Also Ingredient but not Recipe... I'm confused... Link to comment Share on other sites More sharing options...
simplex Posted November 12, 2013 Share Posted November 12, 2013 Yes, I needed to check it myself before I believed it though xD But why TUNING and not STRINGS? I don't get it... I don't know... When the modding API roadmap was being designed, I argued for the presence of STRINGS (along with quite a few other things), but the only thing that was ever added to the mod environment other than post/pre init hooks was Ingredient. ;P Link to comment Share on other sites More sharing options...
Malacath Posted November 12, 2013 Share Posted November 12, 2013 I don't know... When the modding API roadmap was being designed, I argued for the presence of STRINGS (along with quite a few other things), but the only thing that was ever added to the mod environment other than post/pre init hooks was Ingredient. ;PI think I remember... I remember people saying "either everything or nothing" which makes sense for me, because I know that within a week I will have forgotten that Ingredient is there and will call it like I usually do. Link to comment Share on other sites More sharing options...
Nycidian Posted November 12, 2013 Share Posted November 12, 2013 I removed all the requires and they still work so no clue how it happened I distinctly remember that fix working so my guess is I made the stupid mistake of doing two potential fixes at once one of which worked and the other being adding the require which was redundant and covered up what ever else actually worked. Alright I figure it out In one file at some point I was using tostring and tonumber I had delclared tonumber first so my assumption is as tonumber does have to be required (I just checked it) that to fix it it I require'd tonumber and tostring at the same time and then for some reason the tostring require stuck in my head and the next time I need a string function I for some reason thought I needed to require that as well and then it just kept snowballing and since the requires didn't stop anything from running I never questioned it. Link to comment Share on other sites More sharing options...
Malacath Posted November 12, 2013 Share Posted November 12, 2013 Ahhh, okay that makes sense. Though I don't know where tonumber would be of any help : P Link to comment Share on other sites More sharing options...
greenglacier Posted November 12, 2013 Share Posted November 12, 2013 Hardly,but understandable I'd like. Link to comment Share on other sites More sharing options...
squeek Posted November 12, 2013 Author Share Posted November 12, 2013 Alright I figure it out In one file at some point I was using tostring and tonumber I had delclared tonumber first so my assumption is as tonumber does have to be required (I just checked it) that to fix it it I require'd tonumber and tostring at the same time and then for some reason the tostring require stuck in my head and the next time I need a string function I for some reason thought I needed to require that as well and then it just kept snowballing and since the requires didn't stop anything from running I never questioned it.tonumber is a basic Lua function not a part of any library. Not even sure what require you'd even use to get it (require "tonumber" gives me an module 'tonumber' not found error).In modmain.lua:print("tonumber mod env", tostring(tonumber))print("tonumber global env", tostring(GLOBAL.tonumber))outputs:../mods/CodeTipsAndTricks/modmain.lua(1,1) tonumber mod env nil ../mods/CodeTipsAndTricks/modmain.lua(2,1) tonumber global env function: 0E3AECA0Basically, you should never have to use require for default Lua functions or any Lua library. Link to comment Share on other sites More sharing options...
Nycidian Posted November 12, 2013 Share Posted November 12, 2013 tonumber is a basic Lua function not a part of any library. Not even sure what require you'd even use to get it (require "tonumber" gives me an module 'tonumber' not found error).In modmain.lua:print("tonumber mod env", tostring(tonumber))print("tonumber global env", tostring(GLOBAL.tonumber))outputs:../mods/CodeTipsAndTricks/modmain.lua(1,1) tonumber mod env nil ../mods/CodeTipsAndTricks/modmain.lua(2,1) tonumber global env function: 0E3AECA0Basically, you should never have to use require for default Lua functions or any Lua library. Ok I know I had that require in my mod... NM I though that is what I had done but checking an old version what I had done was actually require = GLOBAL.requiretonumber = GLOBAL.tonumbertostring= GLOBAL.tostring So basically as you can see this whole thing is a comedy of errors with me the fool as I said much earlier I'm not entirely sure where I cam across this misconception I just know I did. Obviously it was my mistake. Link to comment Share on other sites More sharing options...
simplex Posted November 12, 2013 Share Posted November 12, 2013 Ahhh, okay that makes sense. Though I don't know where tonumber would be of any help : P Very, very rarely it's useful. For example, I used it here to fix a faulty Spriter (.scml) file. But that was an external script, not part of a mod. ;P Link to comment Share on other sites More sharing options...
squeek Posted March 3, 2014 Author Share Posted March 3, 2014 Adding components to all playerscredit to simplex for this workaroundIf you add a component to a player prefab using AddSimPostInit, OnSave and OnLoad for that component will simply not work (note: this is only the case for SimPostInit, not PrefabPostInit). I have not investigated the reason why, but to get around it you should use the following workaround:-- need to add the component in here, otherwise OnSave doesn't work rightAddPrefabPostInit("world", function(inst) GLOBAL.assert( GLOBAL.GetPlayer() == nil ) local player_prefab = GLOBAL.SaveGameIndex:GetSlotCharacter() -- Unfortunately, we can't add new postinits by now. So we have to do -- it the hard way... GLOBAL.TheSim:LoadPrefabs( {player_prefab} ) local oldfn = GLOBAL.Prefabs[player_prefab].fn GLOBAL.Prefabs[player_prefab].fn = function() local inst = oldfn() -- Add components here. inst:AddComponent("yourcomponent") return inst endend)This technique no longer works due to how the DLC re-loads all prefabs twice for every mod that is enabled. See: this postEDIT: No longer the case (as of March 3rd, 2014 patch). This technique still works. Link to comment Share on other sites More sharing options...
iWitch Posted March 3, 2014 Share Posted March 3, 2014 Oh one of this guys who really understand how lua works is still here Can i ask something from guru? whether there is any possibility to determine the existence of a custom(non vanilla) component?like PrefabExists but for components. Link to comment Share on other sites More sharing options...
squeek Posted March 3, 2014 Author Share Posted March 3, 2014 Oh one of this guys who really understand how lua works is still here Can i ask something from guru? whether there is any possibility to determine the existence of a custom(non vanilla) component?like PrefabExists but for components.You can check if a file exists in any of the search paths (main game files, any mod folders) like so:softresolvefilepath("scripts/components/componentfiletofind.lua")It'll return nil if it's not found, and the filename/filepath if it is (see util.lua for where softresolvefilepath is defined).As an example, here's what a ComponentExists function would look like:function ComponentExists(component_name) return softresolvefilepath("scripts/components/"..component_name..".lua") ~= nilend Link to comment Share on other sites More sharing options...
iWitch Posted March 3, 2014 Share Posted March 3, 2014 hm but mod, which contain this components can be disabled, but i got your idea. i was hope for some lua magic with getmetatable or such for testing local variable Components inside entityscript.lua Link to comment Share on other sites More sharing options...
squeek Posted March 3, 2014 Author Share Posted March 3, 2014 If you want to test for a specific mod being enabled, you can use this: -- checks if a mod with the given name is enabled (name parameter needs to match what's in the mod's modinfo.lua)local function IsModEnabled(name) for _, moddir in ipairs( _G.KnownModIndex:GetModsToLoad() ) do local its_modinfo = _G.KnownModIndex:GetModInfo(moddir) if its_modinfo.name == name then return true end end return falseendif IsModEnabled( "Name Of The Mod" ) then -- do stuffend(the above code was adapted from some code that simplex wrote for his Mod Testing Toolbox mod)I'm not familiar with any methods of extracting local variables from a file. I'm also not really sure why that Components table in entityscript.lua is local (or why there's no global method of accessing it). Even if there was, though, it wouldn't be that useful because components only get loaded into it after they've been used (so if a components/customcomponent.lua existed, it wouldn't be added into the Components table until after it was added to a prefab). Link to comment Share on other sites More sharing options...
Recommended Posts
Archived
This topic is now archived and is closed to further replies.
Please be aware that the content of this thread may be outdated and no longer applicable.