smooth-landing

Jump to content

  • Log In with Google Log In with Steam      Sign In   
  • Create Account

Photo
* * * * * 6 votes

Code Tips and Tricks


  • Please log in to reply
45 replies to this topic

#1 squeek

    squeek
  • Junior Member

  • 511 posts

Posted 12 November 2013 - 02:02 AM

*
POPULAR

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 environment
local 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-safe
local Deployable_CanDeploy_base = Deployable.CanDeploy or function() return true end
function 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_deploy
end
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.

Spoiler



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 ID
function 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 nil
end

-- returns whether or not a mod with the given name or ID is enabled
function IsModEnabled(name_or_id)
    return GetMod(name_or_id) ~= nil
end

-- 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 run
function 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 false
end

Edited by squeek, 23 September 2014 - 01:04 PM.


#2 simplex

    simplex
  • Couldn't be Simpler



  • 4755 posts

Posted 12 November 2013 - 02:45 AM

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.

Edited by simplex, 12 November 2013 - 02:46 AM.

 Avatar by @Willette.

#3 Nycidian

    Nycidian
  • Member

  • 165 posts

Posted 12 November 2013 - 03:45 AM

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


Edited by Nycidian, 12 November 2013 - 10:21 AM.


#4 Malacath

    Malacath
  • Member

  • 713 posts

Posted 12 November 2013 - 04:15 AM

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.



#5 simplex

    simplex
  • Couldn't be Simpler



  • 4755 posts

Posted 12 November 2013 - 06:02 AM

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.

Edited by simplex, 12 November 2013 - 06:04 AM.

 Avatar by @Willette.

#6 Nycidian

    Nycidian
  • Member

  • 165 posts

Posted 12 November 2013 - 08:58 AM

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 declared
LUA 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.


Edited by Nycidian, 12 November 2013 - 09:59 AM.


#7 simplex

    simplex
  • Couldn't be Simpler



  • 4755 posts

Posted 12 November 2013 - 10:15 AM

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


 

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"

 Avatar by @Willette.

#8 Nycidian

    Nycidian
  • Member

  • 165 posts

Posted 12 November 2013 - 10:19 AM

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


Edited by Nycidian, 12 November 2013 - 10:20 AM.


#9 Malacath

    Malacath
  • Member

  • 713 posts

Posted 12 November 2013 - 10:22 AM

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.

 

Spoiler

Wait, TUNING is in the MOD env?


Edited by Malacath, 12 November 2013 - 10:24 AM.


#10 simplex

    simplex
  • Couldn't be Simpler



  • 4755 posts

Posted 12 November 2013 - 10:29 AM

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
 Avatar by @Willette.

#11 Nycidian

    Nycidian
  • Member

  • 165 posts

Posted 12 November 2013 - 10:30 AM

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.



#12 Malacath

    Malacath
  • Member

  • 713 posts

Posted 12 November 2013 - 10:30 AM

Yes, as far as I can remember. ;P

Yes, I needed to check it myself before I believed it though  xD

But why TUNING and not STRINGS? I don't get it...

EDIT: Also Ingredient but not Recipe... I'm confused...


Edited by Malacath, 12 November 2013 - 10:32 AM.


#13 simplex

    simplex
  • Couldn't be Simpler



  • 4755 posts

Posted 12 November 2013 - 10:34 AM

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
 Avatar by @Willette.

#14 Malacath

    Malacath
  • Member

  • 713 posts

Posted 12 November 2013 - 10:38 AM

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.



#15 Nycidian

    Nycidian
  • Member

  • 165 posts

Posted 12 November 2013 - 10:46 AM

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.


Edited by Nycidian, 12 November 2013 - 10:53 AM.


#16 Malacath

    Malacath
  • Member

  • 713 posts

Posted 12 November 2013 - 11:23 AM

Ahhh, okay that makes sense. Though I don't know where tonumber would be of any help  : P



#17 greenglacier

    greenglacier
  • Senior Member

  • 331 posts

Posted 12 November 2013 - 11:49 AM

Hardly,but understandable :D I'd like.


If you want a presonal gif take a look at my --->gif thread<---
Mah steam. Btw, nothing much.
Did someone say anime?!


#18 squeek

    squeek
  • Junior Member

  • 511 posts

Posted 12 November 2013 - 12:06 PM

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.

#19 Nycidian

    Nycidian
  • Member

  • 165 posts

Posted 12 November 2013 - 12:22 PM

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.



#20 simplex

    simplex
  • Couldn't be Simpler



  • 4755 posts

Posted 12 November 2013 - 12:42 PM

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
 Avatar by @Willette.