Jump to content

Code Tips and Tricks


squeek

Recommended Posts

oh btw i am looking for some old threads and found this

 

will this work?

local function testcomponent()local p = SpawnPrefab("pig") --test new things on animals, not on players!p:AddComponent("sometestcomponent")local f = (p.components.sometestcomponent ~= nil)p.components.health:Kill() --thx piggy for test, but your time is overreturn fend local status = pcall(testcomponent)if status then--we can add this component for sureend
Link to comment
Share on other sites

That's probably not the best way to do it (spawning a new entity every time is rather wasteful), but using pcall is an interesting technique. This might be better:

 

-- this doesn't need to be spawned to perform the test, it's just here as an examplelocal testprefab = SpawnPrefab("pig")local didsucceed, errormsg = pcall(testprefab.AddComponent, testprefab, "sometestcomponent")if didsucceed then  -- component has been added  print(tostring(testprefab.components.sometestcomponent))else  -- an error occured while adding the component  print("sometestcomponent was not added: "..errormsg)end
Note that

testprefab:AddComponent("sometestcomponent")
is the same as

testprefab.AddComponent(testprefab, "sometestcomponent")
so the pcall in my code is basically doing the second one.
Link to comment
Share on other sites

I'm necroing this because it should've been pinned anyway.

Something others may find useful: checking if the DLC is enabled.

if GLOBAL.IsDLCEnabled(GLOBAL.REIGN_OF_GIANTS) then    -- stuffelse    -- other stuffend
This doesn't work during worldgen, though (IsDLCEnabled calls a method of TheSim, which is nil during worldgen).
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 (just now) revised the code in Mod Testing Toolbox doing this (and uploaded the new version, with some other minor improvements). I replaced the KnownModIndex:GetModsToLoad() call with ModManager:GetEnabledModNames(), which returns the same thing (except that in the latter the table is sorted by mod loading priority). Your snippet would then look like

local function IsModEnabled(name)	for _, moddir in ipairs( GLOBAL.ModManager:GetEnabledModNames() ) do		local its_modinfo = GLOBAL.KnownModIndex:GetModInfo(moddir)		if its_modinfo.name == name then			return true		end	end	return falseendif IsModEnabled( "Name Of The Mod" ) then	-- do stuffend
The reason for this change is that KnownModIndex:GetModsToLoad() does a filesystem iteration through the mods/ directory looking for mod subdirectories (folders with a modinfo.lua inside), which is a relatively slow operation due to filesystem IO. ModManager:GetEnabledModNames(), on the other hand, returns the (sorted) output of KnownModIndex:GetModsToLoad() which was originally obtained before loading the mods, so it avoids this overhead.
Link to comment
Share on other sites

I (just now) revised the code in Mod Testing Toolbox doing this (and uploaded the new version, with some other minor improvements). I replaced the KnownModIndex:GetModsToLoad() call with ModManager:GetEnabledModNames(), which returns the same thing (except that in the latter the table is sorted by mod loading priority). Your snippet would then look like

*snip*

The reason for this change is that KnownModIndex:GetModsToLoad() does a filesystem iteration through the mods/ directory looking for mod subdirectories (folders with a modinfo.lua inside), which is a relatively slow operation due to filesystem IO. ModManager:GetEnabledModNames(), on the other hand, returns the (sorted) output of KnownModIndex:GetModsToLoad() which was originally obtained before loading the mods, so it avoids this overhead.

Unfortunately, this doesn't work as well as it should. ModManager:GetEnabledModNames() gets populated as mods are loaded, not before, so, at mod startup, it will not return any mods that are going to be loaded after the current mod; it will only return mods that have already been loaded.

However, instead of the "enabledmods" table, we can use the "mods" table of ModManager (there's no function to get it, so we'd just directly access it). This is exactly the same as enabledmods (sorted by priority) but is fully populated before any mod loading gets done. Interestingly, it is a table of mod environments, so each entry will have the key "modname" and "modinfo" for easy access; but, having access to each mod's environment also opens up far more possibilities, like a mod exporting variables safely to other mods by defining them in its own environment and then having the other mods retrieve them from there (or injecting things into a different mod's environment before it loads).

Here are some example functions:

local _G = _G or GLOBALlocal function GetMod(name_or_id)	for _, mod in ipairs( _G.ModManager.mods ) do		-- note: mod.modname is the mod directory name		if mod.modinfo.name == name_or_id or mod.modinfo.id == name_or_id then			return mod		end	end	return nilendlocal function IsModEnabled(name_or_id)	return GetMod(name_or_id) ~= nilend
Let me know if you have any ideas to improve the above functions, as I'll be adding this stuff to the OP once it's in a good state.

EDIT: Another useful function to determine if a mod has been loaded yet or not:

local 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 falseend
Link to comment
Share on other sites

Unfortunately, this doesn't work as well as it should. ModManager:GetEnabledModNames() gets populated as mods are loaded, not before, so, at mod startup, it will not return any mods that are going to be loaded after the current mod; it will only return mods that have already been loaded.

However, instead of the "enabledmods" table, we can use the "mods" table of ModManager (there's no function to get it, so we'd just directly access it). This is exactly the same as enabledmods (sorted by priority) but is fully populated before any mod loading gets done. Interestingly, it is a table of mod environments, so each entry will have the key "modname" and "modinfo" for easy access; but, having access to each mod's environment also opens up far more possibilities, like a mod exporting variables safely to other mods by defining them in its own environment and then having the other mods retrieve them from there (or injecting things into a different mod's environment before it loads).

Thanks for noticing that! I'll update simplex testing and wicker shortly.

Here are some example functions:

local _G = _G or GLOBALlocal function GetMod(name_or_id)	for _, mod in ipairs( _G.ModManager.mods ) do		-- note: mod.modname is the mod directory name		if mod.modinfo.name == name_or_id or mod.modinfo.id == name_or_id then			return mod		end	end	return nilendlocal function IsModEnabled(name_or_id)	return GetMod(name_or_id) ~= nilend
Let me know if you have any ideas to improve the above functions, as I'll be adding this stuff to the OP once it's in a good state.

EDIT: Another useful function to determine if a mod has been loaded yet or not:

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

They look good to me. I'd only suggest checking for id first in GetMod(), since (provided the modinfo defines it) it should be more representative of which mod it is, while the name could be changes for aesthetic purposes.

Link to comment
Share on other sites

I saw that you added new component for player to save settings / history / whatever

 

just want to say - i tryed another way to save some random settings, instead of modifying player, i made empty prefab with two components - one for saving setting, second - follower

 

on postsiminit i spawn prefab and make him following to player if his tag not exists.

thats works on usual world and caves, since game put followers in caves too :-)

 

but i guess it would't work for advanture mode, so sad :-), but maybe gives some ideas

 

some crappy code :

component

local SPStats = Class(function(self, inst)    self.inst = inst    self.ishome = falseend)function SPStats:OnSave()        return    {        ishome = self.ishome    }endfunction SPStats:OnLoad(data)    if data then        self.ishome = data.ishome or self.ishome    end    print ("ISHOME = "..tostring(self.ishome))endreturn SPStats

prefab :

local function fn(Sim)    local inst = CreateEntity()    inst:AddComponent("spstats")    inst:AddComponent("follower")    inst:AddTag("spdata")    return instendreturn Prefab( "common/spstorage", fn)

modmain:

local function FindTag(tag) return GLOBAL.TheSim:FindFirstEntityWithTag(tag) endlocal function SPLoadData(player)    spdata = FindTag("spdata")    if not spdata then        spdata = GLOBAL.SpawnPrefab("spstorage")    end    player.components.leader:AddFollower(spdata)endAddSimPostInit(SPLoadData)

this storage can be find by tag, no need to add anything to player directly.

Link to comment
Share on other sites

What's wrong with adding directly to the player? I'm not seeing a benefit to your method.

well i read this in main.lua

 

     --[[    -- This is bad, but it seems to be the only way to properly add components    -- to a generic character WITH the loading of savedata (as stated by    -- Ipsquiggle when _Q_ had the same issue).    --    -- The component should be added to the character so that it'll be    -- transported across cave entry/exit.    --]]

or its outdated comment or its not bad? :)

 

also i want to ask, what if mod will change some standarct component (like sanity) for storing data, then if its will be deleted or disabled, what happened with this stored variables? they will gone after save?

 

just readed about "game save corruption", how its can be happened?

Link to comment
Share on other sites

well i read this in main.lua

 

or its outdated comment or its not bad? :-)

The reasoning for that comment is in the OP of this very thread (see "Adding components to all players"). It's a workaround for the lack of a proper player post init; more info about that can be found here.

It's 'bad' because it shouldn't be necessary, not because it doesn't work right. The game should give modders a better method for adding components to player prefabs, but it doesn't, so we have to use a slightly strange workaround.

 

also i want to ask, what if mod will change some standarct component (like sanity) for storing data, then if its will be deleted or disabled, what happened with this stored variables? they will gone after save?

 

just readed about "game save corruption", how its can be happened?

If your mod changes a component's OnSave to add a new entry in the save data and changes OnLoad to load it, then, if your mod is disabled, the data will not be loaded and the next save will no longer have your entry in the save data. This problem is something that could be (and was going to be) solved by Klei. More info can be found in this thread under "Add mod-specific save data (outside the mod folder and separate from the world's save data)".

I'm not really sure about all the possible causes of save game corruption, but one thing you do need to make sure of is to never save an infinite number (meaning math.huge or the result of 1/0) in the save data, as it will get saved as #.INF (or something like that) which causes a syntax error when it is loaded. As far as I'm aware, save game corruption is caused by syntax errors in the saved data (the data is saved in Lua script, so it needs to be syntactically correct). It's not something you need to be overly worried about unless you're trying crazy stuff with OnSave.

Link to comment
Share on other sites

some question about LUA

 

how LUA works with different types of variables?

 

for example

k = "somekey"a = b[k]

looks like trivial thing, a gets value of b[k]. NOT always.

 

here a trick

if type of b[k] is "array", a will get POINTER to b[k], not copy of values

 

so if you change values inside a, b[k] will be changed too.

 

so there a question when LUA send value and when pointer?

for "number", "string" and "boolean" its will be value, while for "function" and "table" is pointer? i am right?

 

same question for parameters of functions, LUA send pointer for complex types and values for simple?

 

and last one, is it possible to get pointer to "simple" type of variable, like in some languages:

a := @ba^:= 1
Link to comment
Share on other sites

for "number", "string" and "boolean" its will be value, while for "function" and "table" is pointer? i am right?

Yes.

 

same question for parameters of functions, LUA send pointer for complex types and values for simple?

Well, it's not exactly correct that tables/functions are passed by reference. All variables are passed by value, but tables/functions/userdata are reference types. From the Lua 5.1 manual:

Tables, functions, threads, and (full) userdata values are objects: variables do not actually contain these values, only references to them. Assignment, parameter passing, and function returns always manipulate references to such values; these operations do not imply any kind of copy.

so they aren't treated differently, the types just differ in how they are stored/accessed. A good explanation of this can be found here. That whole question thread is a good reference as well.

By the way, if you want to create a copy of a table, check out the function 'deepcopy' in scripts/util.lua.

 

and last one, is it possible to get pointer to "simple" type of variable, like in some languages:

a := @ba^:= 1
No, it's not. Why do you want to get reference to a simple type? Being able to return multiple values should allow you to not need passing simple parameters by reference.

 

function changeValuesOf(a, b)    a = 1    b = 2    return a, bendlocal c = 4local d = 5c, d = changeValuesOf(c, d)print(c, d) -- prints "1    2"
Link to comment
Share on other sites

thx for reply, well i made my own copy function, but probably its not universal like this "deepcopy"

local function CopyArray(t)    local t2 = {}    for k,v in pairs(t) do        if type(v) == "table" then t2[k] = CopyArray(v) else t2[k] = v end    end    return t2end

but i have no idea what is this:

        return setmetatable(new_table, getmetatable(object))

its about class emulation?

Link to comment
Share on other sites

 

No, it's not. Why do you want to get reference to a simple type?

 

dont know tbh :)

 

well only one thing - you dont need to read return values

so instead of your example, thing like

function f (x, @y, @z)x: = 1y^:= 2z^: = 3end

 

but dont really need :)

Link to comment
Share on other sites

thx for reply, well i made my own copy function, but probably its not universal like this "deepcopy"

local function CopyArray(t)    local t2 = {}    for k,v in pairs(t) do        if type(v) == "table" then t2[k] = CopyArray(v) else t2[k] = v end    end    return t2end

local t = {}t[t] = tCopyArray(t)
or simply

CopyArray(_G)
(since _G._G == _G)

but i have no idea what is this:

        return setmetatable(new_table, getmetatable(object))
its about class emulation?

It may be used for class emulation, among other things. A metatable allows defining what it means to index a table, to set a new value to it, to use operators on it, to call it as a function, etc. You can check the reference manual posted by squeek for more information.

Link to comment
Share on other sites

your examples is blow my mind :-)

array as key?

I wrote a lot about this sort of thing in this thread:

 

I said key instead of the standard programming term 'index' because Lua tables are insanely flexible with table indexes. They can literally be anything. For instance, you can define a table like so:

local tbl = {}local nofn = function() return "no" endlocal yesfn = function() return "yes" endtbl[nofn] = "nope"tbl[yesfn] = "yep"
So, in this case, the functions are the keys to the table. To access the value "nope", you'd have to index into the table using the 'nofn' variable. You could also iterate over the table like so:

-- must use pairs here; ipairs is only for array-like tables (sequential integer keys starting at 1)for fn,value in pairs(tbl) do    -- I can call the keys!    print( fn() )end
Also a note on table syntax:

local tbl = {}-- using the var.key syntax below is simply a shortcut to access a key that is a stringtbl.key = nil-- the above is the same as:tbl["key"] = nil

However, I believe that was a typo on simplex's part. He probably meant t["t"] = t to show that your CopyArray function could recurse infinitely (because t.t = t).

EDIT: Actually, it doesn't matter if it's t["t"] = t or t[t] = t, they will both cause infinite recursion. The table just needs to have a reference to itself stored in any key(s) and it will infinitely recurse.

Another good reference for understanding metatables/metamethods: http://lua-users.org/wiki/MetamethodsTutorial

EDIT#2: I wrote a bit about metatables here as well.

Link to comment
Share on other sites

your examples is blow my mind :-)

array as key?

Sure, why not? In Lua, there are only three values that can't be used as table keys: nil, +NaN and -Nan. And even nil works half way (you can use it to get an element, such as in t[nil], but t[nil] = 0 raises an error; so t[nil] always returns nil).

If you want to go down into the specifics of the implementation of the Lua language, table indexing is hashing, and since table values are references they are represented by a pointer, and pointers are numbers. So you're really just using the memory address of the table as key (but there's some special logic builtin to prevent clashes with a number coinciding with the memory address).

 

However, I believe that was a typo on simplex's part. He probably meant t["t"] = t to show that your CopyArray function could recurse infinitely (because t.t = t).

EDIT: Actually, it doesn't matter if it's t["t"] = t or t[t] = t, they will both cause infinite recursion. The table just needs to have a reference to itself stored in any key(s) and it will infinitely recurse.

I just thought t[t] = t looked prettier, but I guess it just ended up obscuring my intentions :razz:.

As you put it in the edit, I was just illustrating that if any self-referential table was passed to DeepCopy (or any table with a reference loop) it'd cause a stack overflow.

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