Lua - Table with Multiple Values

bane_

Howardianism High-Priest
Joined
Nov 27, 2013
Messages
1,559
I made a research but couldn't get an answer. I can try to test it in-game, but it will be a pain to just try and figure it out blindly, so I came asking help.

Can I do "table.insert(tablewhatever, #t+1, value1, value2)?
Or should it be done like "table.insert(tablewhatever, #t+1, [value1]=value2)"?

I want them to be connected, because I want to link a value that will be increased by a variable to a Civilization ID, so, when I have to update that value I would just look for the right ID (with 'for _, v in ipairs' I suppose) and then change the value setting it to the sum of the old+new value.

The relevant snippet:
Code:
function HHLaBomba(pPlayer, pPlot, iPlotOwner, unit)
	for k, v in pairs(BombBlameCount) do
		if v == iPlotOwner then
			iTotalCS = (k + unit:GetCombatStrength())
			k = iTotalCS
		else
			iTotalCS = (iTotalCS + unit:GetCombatStrength())
			table.insert(BombBlameCount, #t+1, [iTotalCS]=iPlotOwner)
		end
	end
end

Where iTotalCS would be modified every turn when a certain function is fired, though I'm not absolutely sure it is this way (this one I can probably figure out on my on, though).
The table is cleaned every start of a turn.

Was it clear? I hope so. :p


[[Also, I'm a bit confused about the difference between global and local variables; do local only work for the function they are in? Anything trying to call it from the outside will get 'nil'?]]
 
I can't quite figure out what you're trying to do.

But I think you might be confusing things by using the table.insert method. It's documented here and you shouldn't try to use it in any other way. For one thing, you don't ever need #t+1 because insert uses that by default. Just supply table name and value to insert a new element at end. Or supply an index if you want to insert anywhere else (shifting everything else up).

But no need to ever use that library method anyway, since it's so easy (and much faster) to manipulate tables directly.

These two statements are exactly equivalent, except the 2nd will run much faster:
Code:
table.insert(tablewhatever, value1)
tablewhatever[#tablewhatever + 1] = value1

Lua does support multiple assignment, if you like to do that (some would argue that it makes code harder to read):
Code:
local index = #tablewhatever
tablewhatever[index + 1], tablewhatever[index + 2] = value1, value2

-- or the exact equivalent:

local index = #tablewhatever
tablewhatever[index + 1] = value1
tablewhatever[index + 2] = value2


Global Variables
Local Variables

Use local wherever possible, since they can be read and assigned to much faster than global. Use global when you need to access a value from different files. Note: globals aren't truely global in Lua since they are only defined within a state. Your mod should probably all be running in the same Lua state even if you have many files, but the UI lua files are running in different states (there is a "super global" called MapModData available across all Lua states). But even if you define a table as a global, it is optimal to localize it in a particular file:
Code:
myGlobalTable = {}
local myGlobalTable = myGlobalTable
The 2nd line is kind of a lie, since myGlobalTable is now a local variable in the scope where that line occurs (i.e., for the whole file if the line is outside of a function at the top of the file).

You can speed up your Lua code by 30 - 50% or so (depending) by localizing all the tables that happen to be used in a particular file. These include global Tables defined by Firaxis such as GameInfoTypes, ImprovementTypes, GameDefines, Players, Teams, MapModData etc...

Btw, "print" is a global variable. It just happens to hold a function. Lua variables can hold exactly 8 different things: nil, Boolean, Number, String, Table, Function, Userdata and Thread (don't worry about the last two). So it is a perfectly valid statement in Lua to say:
Code:
local print = print
That just assigns the function to a local variable with the same name, which will make it run faster by some small amount (which is silly to do in this case because you shouldn't be printing millions of lines). But if you use a library method or a method on a static object (like Map) in time critical code (i.e., anything that runs 1000s of times a turn such as per plot tests), then absolutely you should localize it:
Code:
local insert = table.insert
local floor = math.floor
local PlotDistance = Map.PlotDistance
etc...


The reason local variables are so much faster in Lua is that they are held in your processor register. Global variables are held elsewhere and require a bit of time to find. There is a limit, however, which is that any of your functions can only access 60 "upvalues" (local variables defined above and outside the function). You will get a Syntax Error if you violate that. But if you run into this limit, then you really should be writing smaller more focused functions (a lesson I'm learning).
 
I believe I understood you perfectly, which is a bit worrisome for me or you are just that good at explaining things - you should join forces with JFD. :P


Ok, here is the thing:

I want to store information on both a Civilization and a variable that CAN change, so I'll need to change it within the table too, which means I need to always know it's position. I thought that I could do that with that code up there, where k would be the civ and v the value I want - which would be easy to find, since Civilizations have unique names while the variable can be equal (although, after reading the code, it seems I did it backwards).

The whole idea is a button that creates a number and this number can be modified X times per turn, then cleaned at the start of your next turn, but if it reaches a certain number it fires a function, so I need to fire the mission/button, find the targeted Civilization, add the number there + the new number and then check to see if it matches or exceeds the target number. I can do all this, except the storing this number and editing it while linking it with the Civilization (in order to later identify it with ease).
I don't even know if the table can hold stuff like this, but in my imagination it would be like this:
k
v​

CIVILIZATION_ASSYRIA 332
CIVILIZATION_AMERICA 80
CIVILIZATION_ZULU 190

Then, with a check to see if k = iPlotOwner I could retrieve v, add to it the new number and get a new v, then add it back to it's old position (maybe removing and inserting that line again)!


Is this something too difficult? I don't mean it like in a pedantic way, maybe it's too complex for my level; I rarely do Lua codes with more than 60 lines, this one haves 347 right now, so maybe I'm giving a step greater than my leg can cover. Not to mention I can't find a decent answer online on google, stack overflow, Lua documentation, etc... So again, possibly isn't a question to make in a forum about a game.

I ask this humbly, but I know how the Internet works and this may be passed as whining or something; it is not. :P


EDIT:
About the Global/Local thing, here is my doubt:

Code:
[U]local var1 = 1[/U]

function myfunction()
	[B]local var1 = var1+1[/B]
end

Will the bolded change the underlined? If I call var1 again in a later code, outside "myfunction" will it be nil or 2? I'm currently using global variables, outside the functions (like the underlined, without the 'local') but I'm not sure that's the proper way.
 
I was editing post above with local/global stuff while you were posting, so take a 2nd look...


The UI for that is a bit of work. Use whoward69's tutorials for that...

On the table side, Lua tables are happy using integers or strings for keys, or even both in the same table. (As an aside: Lua tables can use anything other than nil as a key, including tables or functions, but don't do that unless you are feeling really cocky.) However, don't confuse yourself on which you are using:

"CIVILIZATION_ZULU" --string
local iPlotOwner = plot:GetOwner() --integer (actually playerID)
local iPlotOwnerCiv = Players[iPlotOwner]:GetCivilizationType() --integer
local plotOwnerCivType = GameInfo.Civilizations[iPlotOwnerCiv].Type --string (= "CIVILIZATION_ZULU" or whatever)

I don't bother using strict Hungarian notation. However, there is no reason that your variable name should ever lie about what it is:
iPlayer, playerID, unitTypeID, iPlot, etc are all integers!
In my code, unitType, civType, etc are all strings (Firaxis is entirely inconsistent on this)
unit, unitInfo are both tables (unit is the game object, which really is a table; unitInfo in my code would refer to the table row from Units table, which is also a table)

I often name my tables so I know what they contain and what they are indexed by, for example "specialUnitTypeIDsByCivID" (I can remember that the index is an integer and not a type string).


Yeah, just stay away from table.insert until you really learn how to access and assign table values (then go back and use if if you want). You can update a table value the same way you update any variable:
Code:
i = i + 1
targetNumByCivType.CIVILIZATION_ZULU = targetNumByCivType.CIVILIZATION_ZULU + 1

local civType = "CIVILIZATION_ZULU"
targetNumByCivType[civType] = targetNumByCivType[civType] + 1

Above is an error if i or targetNumByCivType.CIVILIZATION_ZULU is nil.
 
This time I definetively didn't get it. :lol:

Other than the confusion in naming standards (and hungarian notation), is there anything I said that made sense? Can I somehow fixate a key while making the value fluctuating and attached to that key?
I have everything done, all other functions ready to be tested, including buttons, missions, checks, counter-checks and notifications. I now need to be able to store a fluid value for a certain civ (since the action can be used multiple times with multiple civilizations, simply keeping one variable isn't feasible, as far as I know).
 
About the Global/Local thing, here is my doubt:

Code:
[U]local var1 = 1[/U]

function myfunction()
	[B]local var1 = var1+1[/B]
end
Not a syntax error, but also not what you want. You now have two different local variables. They both happen to have the same name, but there are definitely two different variables. Any code inserted before "end" would use the second variable (the one defined in the function). Anything before that or outside the function will interpret "var1" to be the first variable you defined, which happens to hold the value 1. The line local var1 = var1 will create a new local variable every time the function runs, and it will assign it the value 2 every single time (because at that line and before, var1 refers to the first variable that is sitting there with value 1).

I think this is what you want:
Code:
local var1 = 1

function myfunction()
	[B]var1 = var1+1[/B]
end
Here there is one variable called var1 that is seen outside and inside the function. It will increment every time you call the function.

The issue here is "scope". From the manual: "Unlike global variables, local variables have their scope limited to the block where they are declared. A block is the body of a control structure, the body of a function, or a chunk (the file or string with the code where the variable is declared)."


Can I somehow fixate a key while making the value fluctuating and attached to that key?
I still don't quite get what you're trying to do. It sounds like you are describing exactly what tables are supposed to do, which is to hold values (that can be changed) that are associated with a specific key.
 
I still don't quite get what you're trying to do. It sounds like you are describing exactly what tables are supposed to do, which is to hold values (that can be changed) that are associated with a specific key.

It's quite possible, yes. When reading on outside sources, it seemed to me like if you assign no value for the index, it will always be #t+1, BUT, if you try to add more than one value with table.insert, the first will be considered the index (thus, trumpling the default #t+1), which means I would need to put it there so the code isn't confused.

Let's try something simpler:
How would you do to insert two variables into a table and being able to search one by finding the other (the k, v thing that I tried earlier)?
 
BUT, if you try to add more than one value with table.insert, the first will be considered the index (thus, trumpling the default #t+1), which means I would need to put it there so the code isn't confused.
Stop trying to insert two values! You never do this, whether or not you are using table.insert. The "schema" (or whatever you call it) is:

table.insert (tableName, [position,] value)

Brackets mean that position is optional. If you provide 3 args, then the 2nd and 3rd are interpreted as position and value, respectively. If you provide 2 args, then the 2nd will be interpreted as value. (Probably a 4th arg will just be ignored.) There is nothing really unusual about this.

Again, I would strongly advise against using table library methods (e.g., table.insert) until you really understand Lua tables (at which time you won't need it anyway). It's not suitable for any of your example code anyway because you are not using "array keys" (continuous integers from 1 to #t). Using either type strings or civ IDs as keys violates that, so don't use it here.


Let's try something simpler:
How would you do to insert two variables into a table and being able to search one by finding the other (the k, v thing that I tried earlier)?

I still can't tell from code in OP or quote above what you're trying to do. Here is how you assign two values to a table (using strings as keys):

local sounds = {}
sounds.cow = "moo"
sounds.duck = "quack"

...or using sequential integers as keys (below would be same as using [1] and [2]):
local animals = {}
animals[#animals + 1] = "cow"
animals[#animals + 1] = "duck"

You could then use the value from one table as key for the other table:
print(sounds[animals[1]])
--prints "moo"

Is that what you mean by search one by finding the other?
 
[EDIT]Sorry, was writing when you edited.
No, not exactly. Here is my explanation:[/EDIT]

I see, maybe this is impossible to do then.
What I want is to make a variable that is always linked to a Civilization.

The code I have is this... (bolded part is the important part)
Spoiler :
From the mission:
Code:
Action = function(action, unit, eClick)
	if eClick == Mouse.eRClick then
		return
	end
    local pPlayer = Players[Game.GetActivePlayer()];


	[B]local pPlot = unit:GetPlot()
	local iPlotOwner = pPlot:GetOwner()
	local TeamPlot = iPlotOnwer:GetTeam()
	local TeamPlayer = pPlayer:GetTeam()
	
	HHLaBomba(pPlayer, pPlot, iPlotOwner, unit)[/B]
	
	if pPlayer:IsHuman() then
		pPlayer:AddNotification(NotificationTypes.NOTIFICATION_GENERIC, "Knowledge Exchanged" , GameInfo.Civilizations[iPlotOwner:GetCivilizationType()].ShortDescription "traded us their technological secrets for Information. We gained " .. currentRP .. " to Science." , -1, -1);
	elseif iPlotOwner:IsHuman() then
		--iPlotOwner:AddNotification(NotificationTypes.NOTIFICATION_GENERIC, "Knowledge Exchanged" , GameInfo.Civilizations[iPlotOwner:GetCivilizationType()].ShortDescription "traded us their technological secrets for Information. We gained " .. currentRP .. " to Science." , -1, -1);
	end
  end,

Then, the functions...

Code:
BondsTimer = 0
BombBlameCount = {}
iTotalCS = 0


function HHLaBomba(pPlayer, pPlot, iPlotOwner, unit)
	if AddedBlameCount == false then
		GameEvents.PlayerDoTurn.Add(BlameCountBombRes)
		AddedBlameCount = true
	end
	[B]for k, v in pairs(BombBlameCount) do
		if v == iPlotOwner then
			iTotalCS = (k + unit:GetCombatStrength())
			k = iTotalCS
		else
			iTotalCS = (iTotalCS + unit:GetCombatStrength())
			table.insert(BombBlameCount, #t+1, iTotalCS, iPlotOwner)
		end
	end[/B]
end

The 'reset all':
Code:
function BlameCountBombRes()
	BombBlameCount = {}
	AddedBlameCount = false
	iTotalCS = 0
	GameEvents.PlayerDoTurn.Remove(BlameCountBombRes)
end

The ability is:
Assign Blame
Destroy any of your units in another player's borders to make that player be held responsible. He loses Culture based on the unit's Combat strength.
Other Civilizations may Denounce him.

This 'may' is the reason for the code. The other leaders will only denounce if the 'denounce meter' (is not called that...) is high enough, so, if I kill five units in Assyria's territory, I need that figure to be stored somewhere for a turn (the last code resets it all), but I can't simply create a normal variable because I decide to use the same ability on Portugal it would simply break!

The way I thought it worked was because I thought it would assign to 'k' a number and to 'v' the civ (though, in retrospect, the other way around would be better in this hypotethical scenario), so I could simple look at the table (first function, after the mission), find the Civilization and change it's numeric value not disturbing the other civ's numbers.
 
I gotta run make dinner. But fix this line:

table.insert(BombBlameCount, #t+1, iTotalCS, iPlotOwner)

so that I at least know what you're trying to do there. It's invalid (as I explained above) and I can't even know what you want from it. It looks like maybe you are trying to squeeze two values into one table position. Is that it? A table can only hold one value for each key. Of course, that value can be a table.

Why not just use civ (either string or ID... just pick one) as key? Then you don't have to "loop through" the table. Just get what you want (or change it) with the key.
 
Sorry, had to sleep, was exhausted.

so that I at least know what you're trying to do there. It's invalid (as I explained above) and I can't even know what you want from it. It looks like maybe you are trying to squeeze two values into one table position. Is that it? A table can only hold one value for each key. Of course, that value can be a table.

Aha! That's it.

I can't just put in a variable for the reason I stated earlier, this variable will not make a distinction between plot owners; if I pop a guy in Assyria's territory and then Portugal's, it would just add the two, which is not my intention. What I want is to add the Combat Strength of the unit killed to every other I killed that turn IN THE SAME LEADER'S TERRITORY! So, if I kill 9 Warriros (8CS) in Assyria's Territory and 2 in Portugal's, I need Assyria's value to be 72 and Portugal's 16.

Even if I insert a table into the table, I still need a name for it that is not static, otherwise it would just overwrite itself with Portugal's value and CivID after I pop a guy there.

Why not just use civ (either string or ID... just pick one) as key? Then you don't have to "loop through" the table. Just get what you want (or change it) with the key.
That's what I'm trying to do with iPlotOwner being the key and iTotalCS being the value associated with that key (iPlotOwner).
 
That's what I'm trying to do with iPlotOwner being the key and iTotalCS being the value associated with that key (iPlotOwner).
So...
Code:
BombBlameCount[iPlotOwner] = iTotalCS
...?

You don't need to "loop through" the whole table with for k,v in pairs. Just get and set the value directly with that key. Or you can incremented it by value y like this:
Code:
BombBlameCount[iPlotOwner] = BombBlameCount[iPlotOwner] + y

--or to get the value
local iTotalCS = BombBlameCount[iPlotOwner]
Keep in mind that the above is an error if BombBlameCount[iPlotOwner] is nil. So you will need to set the value to 0 for all players first. Or, make the code work with nil like this:
Code:
BombBlameCount[iPlotOwner] = [COLOR="Blue"](BombBlameCount[iPlotOwner] or 0)[/COLOR] + y
The blue part evaluates to 0 if no value has ever been assigned to BombBlameCount[iPlotOwner].


Edit: In above, I'm assuming you want to store one value per civilization. So 8 total values for 8 full civ players. But perhaps you want to store one value per civilization per civilization (so up to 64 values)? You can do that too. You just need a table holding tables:
Code:
local BombBlameCount = {}

--then if I need to put or add to a value specific for a particular [B]pair[/B] of civilizations:
BombBlameCount[iCiv1] = BombBlameCount[iCiv1] or {}
BombBlameCount[iCiv1][iCiv2] = (BombBlameCount[iCiv1][iCiv2] or 0) + y

--then get the value
local iTotalCS = (BombBlameCount[iCiv1] and BombBlameCount[iCiv1][iCiv2]) or 0
The or's and and's may look confusing in above code, but they are there to deal with nil values. BombBlameCount is assigned as a table in the very first line, but BombBlameCount[iCiv1] is nil until something is assigned to it. The line "BombBlameCount[iCiv1] = BombBlameCount[iCiv1] or {}" is a very common Lua trick used to assign a new table to BombBlameCount[iCiv1] only if BombBlameCount[iCiv1] is currently nil. In the last line, the "value" is taken as 0 if nothing was ever assigned to this pair of civs.

To make above code block a little more clear, here it is in a simpler but longer way without all the and's and or's:
Code:
local BombBlameCount = {}

--then if I need to put or add to a value specific for a particular pair of civilizations:
if not BombBlameCount[iCiv1] then
	BombBlameCount[iCiv1] = {}
end
if not BombBlameCount[iCiv1][iCiv2] then
	BombBlameCount[iCiv1][iCiv2] = 0
end
BombBlameCount[iCiv1][iCiv2] = BombBlameCount[iCiv1][iCiv2] + y

--then get the value
local iTotalCS = 0
if BombBlameCount[iCiv1] then
	if BombBlameCount[iCiv1][iCiv2] then
		iTotalCS = BombBlameCount[iCiv1][iCiv2]
	end
end
 
Well... I find this quite ironic. :lol:
After a while I started thinking this was way too hard, too much further from I was, but it turns out it was actually simple and I was making it complicated!

You were right, I want to set the value for one civ, not one civ per civ (it's a UA ability, so only one Civ).

BUT... I'm still a bit confused. How would I set the value?

Code:
function HHLaBomba(iPlotOwner, unit)
	local TotalCS = unit:GetCombatStrenght()
	BombBlameCount[iPlotOwner] = (BombBlameCount[iPlotOwner] or 0) + TotalCS
end

Just like this?
What does the '[]' mean? Position in the table or something? Like "table[position]=value"? <- this is for educational purpouses only, ignore the rest of the thread for this answer.

Then, to compare the value to see if it hit the treshold, how would I call the value?
Would a simple...

Code:
if BombBlameCount[iPlotOwner] >= 100 then 
-- code
end

...suffice?

EDIT:
Fixed the order.
This must be the thread with the higher percentage of edits per post. :lol:
 
That's it. Each time you call HHLaBomba it will increment that value by the amount given by unit:GetCombatStrenght().

As a minor convention issue, use lower case (e.g., totalCS) for variables holding anything other than functions. It's not really "total" in this case anyway, so probably should be called something like "csFromUnit" (I haven't read carefully enough to know what CS is). You probably don't realize it, but HHLaBomba is itself a global variable. It is holding the function you supplied, so it is properly uppercase. By these rules, BombBlameCount should probably also be lower case "bombBlameCount". However, coders are not consistent in the case of tables and Firaxis goes both ways (they typically use uppercase for "super global" tables like MapModData or ImprovementTypes or Players or GameInfo, but lower case for all other tables).

Your second code block (the conditional test) is perfect.

[] is the main way to indicate a key for a table. For string keys, there is a shorthand alternative which is just a dot(.).

table["cow"]
table.cow
--above two are exactly equivalent, both using the string "cow" as key in the table

table[cow]
--This is not the same!!! Lua sees cow here as a variable name, and will try to key the table from whatever value is held in cow. If not defined then cow is nil and you will get an error, since nil is the one thing you can't use as a key.

local cow = 1
table[cow]
table[1]
--the above two are equivalent, both using integer 1 as key.

table[1]
table.1
--Not the same!!! The first is using the integer 1 as key, the second is using the string "1" as key. The second is exactly equivalent to table["1"]


So now you know what you are looking at when you see:
local player = Players[iPlayer]
local improvementID = ImprovementTypes.IMPROVEMENT_FARM
--both of these are simple table access, one using an integer key and the other a string
 
Thank you! :D
Indeed it is not 'total' anymore, and the 'CS' here is short for 'CombatStrength'.

I'll revise my codes and try to be more consistent, other modders probably will never look at my codes (many other better mods available), but it is a good habit to have.

So now you know what you are looking at when you see:
local player = Players[iPlayer]
local improvementID = ImprovementTypes.IMPROVEMENT_FARM
--both of these are simple table access, one using an integer key and the other a string

YES! It makes much more sense now! :goodjob:
Again, thanks!
 
Back
Top Bottom