[Lua] Table Help
Moderator: Defcon moderators
- Ace Rimmer
- level5
- Posts: 10803
- Joined: Thu Dec 07, 2006 9:46 pm
- Location: The Multiverse
[Lua] Table Help
I've been trying to come up with a decent way to identify and control units via tables, which are really just arrays in Lua (right?).
I think (?) one of the biggest problems I have is not being able to remove a unit from a table when it's destroyed due to the fact you can only use table.remove() to remove based on position and not value. That is, if there are 12 Carriers and one dies, you can't say "ah, that was unitID 35, which is in position 3 of the table Carriers, delete/remove/set as nil that one. At least, several weeks of trying to work that out have led me to belive it.
Currently, when I place my units (ground or sea), I place each one in it's own table so I can avoid using GetAllUnits() or GetAllOwnUnits() every time I want to do something. This seems to work just fine, except when it comes to unit movement. The game crashes after a while (orders start at Defcon 4, keep going, crashes during Defcon 2, sometimes after Defcon 1, sometimes not). My guess is that I'm trying to give orders to units that don't exist anymore because I never worked out a way to remove destroyed units from my tables.
Just to see what I would get, I used this SendChat(CVs[1]:GetLongitude()) (CVs is a table of my carriers), every tick. Something really odd happens; at first it gives me the longitude of Carrier 1, but then when that carrier (I assume) is destroyed during Defcon 3, the longitude changes, as if it's giving me a coordinate of Carrier n, and then soon Lua is throwing an error (which I expected) stating that I'm trying to call a value that is nil. The part I don't get is why the value would change instead of going straight to nil and the error showing up.
It could also be happening because I do call GetAllUnits() every tick in a different function to check for enemy units in radar, scan my enemy units table (one for each type of unit) and insert that unit if it's 'new'. I then use those tables to base my fleet movements off of. E.g., if enemy surface-ship is within x distance of myCarrier, move myCarrier.
Once again, I have very little understanding of what I'm doing, but any advice would nice.
I think (?) one of the biggest problems I have is not being able to remove a unit from a table when it's destroyed due to the fact you can only use table.remove() to remove based on position and not value. That is, if there are 12 Carriers and one dies, you can't say "ah, that was unitID 35, which is in position 3 of the table Carriers, delete/remove/set as nil that one. At least, several weeks of trying to work that out have led me to belive it.
Currently, when I place my units (ground or sea), I place each one in it's own table so I can avoid using GetAllUnits() or GetAllOwnUnits() every time I want to do something. This seems to work just fine, except when it comes to unit movement. The game crashes after a while (orders start at Defcon 4, keep going, crashes during Defcon 2, sometimes after Defcon 1, sometimes not). My guess is that I'm trying to give orders to units that don't exist anymore because I never worked out a way to remove destroyed units from my tables.
Just to see what I would get, I used this SendChat(CVs[1]:GetLongitude()) (CVs is a table of my carriers), every tick. Something really odd happens; at first it gives me the longitude of Carrier 1, but then when that carrier (I assume) is destroyed during Defcon 3, the longitude changes, as if it's giving me a coordinate of Carrier n, and then soon Lua is throwing an error (which I expected) stating that I'm trying to call a value that is nil. The part I don't get is why the value would change instead of going straight to nil and the error showing up.
It could also be happening because I do call GetAllUnits() every tick in a different function to check for enemy units in radar, scan my enemy units table (one for each type of unit) and insert that unit if it's 'new'. I then use those tables to base my fleet movements off of. E.g., if enemy surface-ship is within x distance of myCarrier, move myCarrier.
Once again, I have very little understanding of what I'm doing, but any advice would nice.
Tables in Lua aren't arrays, they're associative arrays. What this means is that instead of an array where the key has to be an integer, the key can be anything. In lua to remove an item from the table you can do
And then the value is no longer in the table.
Now that's cleared up, I'd suggest organising your table to map from UnitID -> Table. The table returned can store whatever data from like about a particular unit.
Obviously to remove units form the table, simply monitor the death events and do
when one occurs
Code: Select all
table[key] = nil
And then the value is no longer in the table.
Now that's cleared up, I'd suggest organising your table to map from UnitID -> Table. The table returned can store whatever data from like about a particular unit.
Obviously to remove units form the table, simply monitor the death events and do
Code: Select all
unitTable[UnitID] = nil
when one occurs
GENERATION 22:The first time you see this, copy it into your sig on any forum and add 1 to the generation. Social experiment.
- Ace Rimmer
- level5
- Posts: 10803
- Joined: Thu Dec 07, 2006 9:46 pm
- Location: The Multiverse
martin wrote:Now that's cleared up, I'd suggest organizing your table to map from UnitID -> Table. The table returned can store whatever data from like about a particular unit.
Obviously to remove units form the table, simply monitor the death events and doCode: Select all
unitTable[UnitID] = nil
when one occurs
So, how do you map it that way? (UnitID -> Table) Currently, I just use table.insert(unittypeTable, unit) using a for loop, so the key is 1, 2, etc and value is UnitID, which means I have no way to know which position the unit has in the table.
Also, it's definitely the calling of destroyed units that crashes Defcon. I replaced all table references in ship movement and it works fine. I've even been able to get units to move together (i.e. one carrier moves in response to enemy ship coming into radar, all nearby carriers 'follow'). What I haven't figured out for that bit is how to make them do it in an orderly fashion. Right now, they end up getting all bunched together and/or can't/don't keep good formation lines.
Sorry, I wasn't very clear on how to achieve that was I?
Ok, so when you discover a new node with UnitID ID you do this:
Something, in this case, is whatever data you want to store for this unit. In this case, what I'd recommend doing is storing a new table as the value, and this table would contain unit specific data. Stuff like unit type, position, some kind of abstract state in your AI algorithm for this unit, whatever you like.
Once you've done that, in the future you can do
Ok, so when you discover a new node with UnitID ID you do this:
Code: Select all
UnitTable[ID] = something
Something, in this case, is whatever data you want to store for this unit. In this case, what I'd recommend doing is storing a new table as the value, and this table would contain unit specific data. Stuff like unit type, position, some kind of abstract state in your AI algorithm for this unit, whatever you like.
Code: Select all
if (UnitTable[ID] == nil) --determine if this is a new unit
--create a table for this unit
local unitTable = {}
--now put some useful data into unitTable
--whatever you like really
unitTable.Position = GetPosition(ID)
unitTable.Friendly = GetTeam(ID) == MyTeam
UnitTable[ID] = unitTable
end
Once you've done that, in the future you can do
Code: Select all
--get data
local isUnitFriendly = unitTable[ID].Friendly
local positionAtLastDiscovery = unitTable[ID].Position
--update data
unitTable[ID].Position = GetPosition(ID)
--Add new data at any time
unitTable[ID].Foo = "Bar"
--Delete data at any time
unitTable[ID].Foo = nil
--Delete unit at any time
unitTable[ID] = nil
GENERATION 22:The first time you see this, copy it into your sig on any forum and add 1 to the generation. Social experiment.
- Ace Rimmer
- level5
- Posts: 10803
- Joined: Thu Dec 07, 2006 9:46 pm
- Location: The Multiverse
So, have a master table, made up of tables, each on being the unitID Defcon assigns to each unit:
MyCarriers = {unitID ={stuff/state}, unitID ={stuff/state}, etc} ?
And just delete them as they're destroyed: eventtype = "destroyed", targetID == nil
(targetID actually being a unitID, which is actually a table)
Am I getting this right
Also, ships now move in formation. Whee!
MyCarriers = {unitID ={stuff/state}, unitID ={stuff/state}, etc} ?
And just delete them as they're destroyed: eventtype = "destroyed", targetID == nil
(targetID actually being a unitID, which is actually a table)
Am I getting this right
![Question :?:](./images/smilies/icon_question.gif)
Also, ships now move in formation. Whee!
You have the concept right yeah, except that to remove the unit when it dies it's
NOT
Code: Select all
unitTable[targetID] = nil
NOT
Code: Select all
TargetID = nil
GENERATION 22:The first time you see this, copy it into your sig on any forum and add 1 to the generation. Social experiment.
- Ace Rimmer
- level5
- Posts: 10803
- Joined: Thu Dec 07, 2006 9:46 pm
- Location: The Multiverse
Assuming that you know the unit you're killing is a carrier.
In Joshua I was using a single global entity table for units because any API calls seem to be incredibly slow. So instead of GetUnitType(UnitID) I sped it up vastly by doing that just once when the unit is first encountered, and then I can just look it up in the table. My advice would be to do this, have a single central table of units with *all* unchanging data cached.
If at some point you want a table of units of just one type, then build a new one from your global table for a single use:
This is a really fast operation, so you can do it quite a lot.
This way, all your units are in one place and one place only. So you won't have trouble with dead units hanging around in the table, but you can still access things by type. Obviously you can build filter methods for any number of different filters. If you modified the filter methods to accept a table, rather than use the global entity table you could even chain filters together which is cool.
In Joshua I was using a single global entity table for units because any API calls seem to be incredibly slow. So instead of GetUnitType(UnitID) I sped it up vastly by doing that just once when the unit is first encountered, and then I can just look it up in the table. My advice would be to do this, have a single central table of units with *all* unchanging data cached.
If at some point you want a table of units of just one type, then build a new one from your global table for a single use:
Code: Select all
UnitTable --Contains all units
GetTableFilteredByType(type)
local result = {}
for (k, v in pairs(UnitTable))
if (v.Type = type) then
result[k] = v
end
end
return result --result contains all units of a specific type
end
This is a really fast operation, so you can do it quite a lot.
This way, all your units are in one place and one place only. So you won't have trouble with dead units hanging around in the table, but you can still access things by type. Obviously you can build filter methods for any number of different filters. If you modified the filter methods to accept a table, rather than use the global entity table you could even chain filters together which is cool.
- Ace Rimmer
- level5
- Posts: 10803
- Joined: Thu Dec 07, 2006 9:46 pm
- Location: The Multiverse
Question:
Based on your example above, I created the code below. However, the OnEvent bit doesn't seem to work. The best I've been able to do is get it to show me 'nil' for all values in UnitTable. What exactly am I doing wrong here? If it's unclear, what I'm trying to do is set the 'Condition' of any unit that has been destroyed from "Active" to "Killed".
I know the targetID in the OnEvent function returns the ID of the unit that was killed. That same ID should also be the key in UnitTable, right? Therefore UnitTable[targetID] should equal UnitTable[someID] (vs nil).
Based on your example above, I created the code below. However, the OnEvent bit doesn't seem to work. The best I've been able to do is get it to show me 'nil' for all values in UnitTable. What exactly am I doing wrong here? If it's unclear, what I'm trying to do is set the 'Condition' of any unit that has been destroyed from "Active" to "Killed".
I know the targetID in the OnEvent function returns the ID of the unit that was killed. That same ID should also be the key in UnitTable, right? Therefore UnitTable[targetID] should equal UnitTable[someID] (vs nil).
Code: Select all
UnitTable = {}
function RadarSweep()
local units = GetAllUnits ()
for i, unit in ipairs(units) do
if UnitTable[unit] == nil then --create a table for this unit
local unitTable = {}
--user defined variables
unitTable.Condition = "Active" --vs "Killed" or "Invalid" (alive but not visible)
unitTable.Mode = "Unknown" --[[vs Attack, Defend, <snip>]]
--game defined variables
unitTable.Team = unit:GetTeamID() --etc, etc
UnitTable[unit] = unitTable
end
end
end
function OnEvent(eventType, sourceID, targetID, unitType, longitude, latitude)
if (eventType == "Destroyed") then
UnitTable[targetID].Condition = "Killed"
end
end
- Ace Rimmer
- level5
- Posts: 10803
- Joined: Thu Dec 07, 2006 9:46 pm
- Location: The Multiverse
I strongly encourage you to NEVER use two variables with the same name but differentiated by case, especially when the upper and lower case versions of a character don't look very different from each other. You should probably also use a naming convention which makes it easy to spot the difference between a function and a variable, something like, variables lower cased and underscore_separated, functions CamelCased, and attributes or keys lower case.
Also, when and how is RadarSweep called?
Also, when and how is RadarSweep called?
- Ace Rimmer
- level5
- Posts: 10803
- Joined: Thu Dec 07, 2006 9:46 pm
- Location: The Multiverse
RadarSweep is called every tick.
Alright, I 'fixed' the naming a bit and discovered the reason I was having trouble; I didn't (and still don't) fully understand how tables work, but apparently asking it to tell me how many values were in MainUnitTable was silly for two reasons...
1. All keys are light userdata and Lua was looking for a start (i.e. numeric 1 or it's equivalent) and didn't see it.
2. All keys are also tables.
I guess. Anyway, this is what I've gotten so far:
As you can see, I am now accessing the MainUnitTable (formerly UnitTable, not to be confused with unitTable) just fine, but the onEvent = "Destroyed" doesn't quite work, er, all the time. You can clearly see it working, but I can't figure out why it would say line 109 is a nil value. unitID <93> is most certainly there, as the chat shows.
Could it be because on some occasions multiple gunshots hit the same target and more than one unit (e.g. enemy BattleShip, enemy Bomber, enemy Fighter) is getting credit as sourceID and therefore the onEvent is trying multiple times to change the value of MainUnitTable[targetID].Condition?
In short, why does it work some of the time? /me is confused
Alright, I 'fixed' the naming a bit and discovered the reason I was having trouble; I didn't (and still don't) fully understand how tables work, but apparently asking it to tell me how many values were in MainUnitTable was silly for two reasons...
1. All keys are light userdata and Lua was looking for a start (i.e. numeric 1 or it's equivalent) and didn't see it.
2. All keys are also tables.
I guess. Anyway, this is what I've gotten so far:
![Image](http://img404.imageshack.us/img404/5205/conditionchange.png)
As you can see, I am now accessing the MainUnitTable (formerly UnitTable, not to be confused with unitTable) just fine, but the onEvent = "Destroyed" doesn't quite work, er, all the time. You can clearly see it working, but I can't figure out why it would say line 109 is a nil value. unitID <93> is most certainly there, as the chat shows.
Could it be because on some occasions multiple gunshots hit the same target and more than one unit (e.g. enemy BattleShip, enemy Bomber, enemy Fighter) is getting credit as sourceID and therefore the onEvent is trying multiple times to change the value of MainUnitTable[targetID].Condition?
In short, why does it work some of the time? /me is confused
Code: Select all
104 function OnEvent(eventType, sourceID, targetID, unitType, longitude, latitude)
105 if (eventType == "CeasedFire") then
106 elseif (eventType == "Destroyed") then
107 SendChat(" ")
108 SendChat(tostring(targetID) .. " Change Condition")
109 MainUnitTable[targetID].Condition = "Killed" --change the condition from "Active" to "Killed"
110 SendChat(tostring(targetID) .. MainUnitTable[targetID].Condition) --make sure it actually changed
111 SendChat(tostring(targetID) .. "Condition Changed")
112 SendChat(" ")
113 elseif <etc, etc, blah>
127 end
128 end
Ace Rimmer wrote:unitID <93> is most certainly there, as the chat shows.Code: Select all
108 SendChat(tostring(targetID) .. " Change Condition")
109 MainUnitTable[targetID].Condition = "Killed" --change the condition from "Active" to "Killed"
110 SendChat(tostring(targetID) .. MainUnitTable[targetID].Condition) --make sure it actually changed
111 SendChat(tostring(targetID) .. "Condition Changed")
The chat shows that targetID is being assigned the value 93 when passed to OnEvent but the error shows that no unit with that ID is in your table. Only had a quick glance but I think I would need to see more of the overall code, in particular how and when RadarSweep is called in relation to OnEvent, before being able to tell why that unit isn't being added (or prematurely removed) from your MainUnitTable. At what point do you remove units from that table?
- Ace Rimmer
- level5
- Posts: 10803
- Joined: Thu Dec 07, 2006 9:46 pm
- Location: The Multiverse
Code: Select all
function OnTick()
if (DefconLevel ~= GetDefconLevel()) then
DefconLevel = GetDefconLevel()
if (DefconLevel == 5) then
--CircleTest()
OnFirstTickD5()
--WhiteboardDraw(-180, 0, 180, 0)
--WhiteboardDraw(0, -90, 0, 90)
elseif (DefconLevel == 4) then
OnFirstTickD4()
elseif (DefconLevel == 3) then
OnFirstTickD3()
elseif (DefconLevel == 2) then
OnFirstTickD2()
elseif (DefconLevel == 1) then
OnFirstTickD1()
end
end
if (DefconLevel == 5) then
TickD5()
elseif (DefconLevel == 4) then
TickD4()
elseif (DefconLevel == 3) then
TickD3()
elseif (DefconLevel == 2) then
TickD2()
elseif (DefconLevel == 1) then
TickD1()
end
WorkOnLongTasks()
end
Code: Select all
function TickD5()
RadarSweep()
end
Every function TickDn RadarSweep is called
Code: Select all
function RadarSweep()
local units = GetAllUnits ()
for i, unit in pairs(units) do
if MainUnitTable[unit] == nil then --create a table for this unit
local unitTable = {}
--user defined variables
unitTable.Condition = "Active" --vs "Killed" or "Invalid" (alive but not visible)
unitTable.Mode = "Unknown" --[[vs Attack, Defend, Scout, Patrol,
NukeWhales (naval nuking),
IBeam (bomber formation vs city), IBeamLeader,
Idle, Leader, Follower]]
--game defined variables
unitTable.Team = unit:GetTeamID()
unitTable.Type = unit:GetUnitType()
unitTable.Long = unit:GetLongitude()
unitTable.Lat = unit:GetLatitude()
unitTable.Speed = unit:GetVelocity()
unitTable.FirstSeen = GetGameTime()
local us = GetOwnTeamID()
if unitTable.Team == us then
unitTable.State = unit:GetCurrentState()
unitTable.Orders = unit:GetActionQueue()
unitTable.Timer = unit:GetStateTimer()
if (unitTable.Type == "Carrier" or unitTable.Type == "AirBase" or unitTable.Type == "Bomber" or unitTable.Type == "Silo") then
unitTable.Target = unit:GetCurrentTargetID()
unitTable.Nukes = unit:GetNukeCount()
end
end
MainUnitTable[unit] = unitTable
SendChat(tostring(unit) .. " " .. MainUnitTable[unit].Type .. " " .. MainUnitTable[unit].Condition)
end
end
I don't plan on removing any units (none are removed now). Other than placement, no other code is active.
What does the chat say if you change:
to be:
Just curious if it's the same unit type that causes the problem i.e. ground installations or fighter. Silly question (as I haven't used the API much) but does GetAllUnits() return placed structures as well as planes and fleets?
Code: Select all
108 SendChat(tostring(targetID) .. " Change Condition")
to be:
Code: Select all
SendChat(tostring(targetID) .. " : " .. tostring(unitType) .. " : Change Condition")
Just curious if it's the same unit type that causes the problem i.e. ground installations or fighter. Silly question (as I haven't used the API much) but does GetAllUnits() return placed structures as well as planes and fleets?
Who is online
Users browsing this forum: No registered users and 3 guests