Jump to content

Code Tips and Tricks


squeek

Recommended Posts

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 Tricks

require 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_deployend
Note: 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 players

credit to simplex for this workaround

This 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 schedules

Sometimes 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 interaction

Some handy functions for detecting if other mods are enabled, getting their

environments (and potentially manipulating them), and checking if they have been

loaded yet or not

local _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

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

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.math

string = GLOBAL.string

table = GLOBAL.table

 

/Sits Quietly in the corner

Link to comment
Share on other sites

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.0

I 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

Really  o.0

I 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 like

debug = GLOBAL.require "debug"
is functionally identical to

debug = GLOBAL.debug
from 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

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 like

debug = GLOBAL.require "debug"
is functionally identical to

debug = GLOBAL.debug
from 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.math

string = GLOBAL.string

table = 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

Ah I see what you mean so instead of doing the require i can do

 

math = GLOBAL.math

string = GLOBAL.string

table = GLOBAL.table

You 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 as

local x = "local value"_G.x = "global value"
Link to comment
Share on other sites

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

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

Can 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 env

end

Wait, TUNING is in the MOD env?

Link to comment
Share on other sites

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

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

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

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

I 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

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

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: 0E3AECA0
Basically, you should never have to use require for default Lua functions or any Lua library.
Link to comment
Share on other sites

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: 0E3AECA0
Basically, 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.require

tonumber = GLOBAL.tonumber

tostring= 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

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

Adding components to all players

credit to simplex for this workaround

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)

This technique no longer works due to how the DLC re-loads all prefabs twice for every mod that is enabled. See: this post

EDIT: No longer the case (as of March 3rd, 2014 patch). This technique still works.

Link to comment
Share on other sites

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

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

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

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

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.

×
  • Create New...