Putting together a 3D view

Posted by Henry Tanner on Fri 03 Sep 2010 09:33 AM — 14 posts, 39,287 views.

Finland #0
For a good amount of time now I have worked on creating a plugin that will enhance the space battle experience in the MUD "Legends of the Jedi", and other similiar MUDs a great deal.

In the MUD the space is viewed through simple commands:
"proximity" for checking your distance from other stellar objects,
"proximity velocity" to check the objects' velocity
and "radar" or the more elaborate "fleetradar" for their x y z coordinates.

A sample of fleetradar output:

L[None    ]:P[Central ] Venator-Class Star Destroyer 'Resolute'  0 0 0
L[None    ]:P[Outer   ] G-Type Light Shuttle 'MatteBlackTaxi13'  0 0 0
L[None    ]:P[Outer   ] A Pirated Scurrg H-6 Bomber 'Krakana'  -1182 -1754 -446
L[None    ]:P[Outer   ] Mark I Bulk Freighter 'Imperceptus'  -231 -231 231
L[None    ]:P[Central ] Victory-Class Star Destroyer 'Exitus'  0 0 0
L[Victory-Class Star Destroyer 'Exitus']:P[Midguard] Victory-Class Star Destroyer 'Jomi'  0 0 0


In the script, the above is captured and turned into a visual representation: http://i.imgur.com/280X9.png
(If you have ever played Elite the image needs no explaining)

So far I've managed to automate listing new shiptypes (i.e Victory-Class Star Destroyer, Mark I Bulk Freighter and such) as the script comes across with them. It is also possible to change the color each shiptype is drawn in.

The table looks like this:

require "tprint"
tprint (shiptypes)
-->
"Venator-Class_Star_Destroyer"=16711680
"Victory-Class_Star_Destroyer"=16711680
"G-Type_Light_Shuttle"=16711680





Before I can move to other features of the script I will have to make the script capture the stellar objects into tables in a table that'd look something like this:

stellarobjects = {
1 = { "name"="resolute", "type"="Venator-Class Star Destroyer", ""leader"="None", "position"="Central", "x"="0", "y"="0", "z"="0"},
2 = { "name"="krakana", "type"="A pirated scurrg h-6 bomber", ""leader"="None", "position"="outer", "x"="-1182", "y"="-1754", "z"="-446"}
}


Despite studying the Tables guide, I haven't manged to figure out how to manage, delete, get and add entries in that table of tables.

Help?
Amended on Fri 03 Sep 2010 09:34 AM by Henry Tanner
Australia Forum Administrator #1
Very briefly, for numbered tables (like in your example) - where the keys are just numbers starting at 1, you can use table.remove and table.insert.

For other keys (like names) you do something like this:


stellarobjects ["resolute"] = {   ... some stuff ...  }  -- add an item
stellarobjects ["resolute"] = nil  -- delete an item


For situations where you may have many things which are not otherwise unique (eg. multiple swords in an inventory) you probably want numeric keys, and you can just have item 1, item 2 etc.

For situations where everything has a unique name or identifier, then that unique thing is a sensible thing to use as the table key.
Australia Forum Administrator #2
BTW I presume you just typed in your test data, it seems to have too many quotes in places, and also I personally would not put numbers in strings.

eg.

x="-1182"

might be better as

x = -1182

That saves converting them to numbers later to do things like addition, or comparison.
Finland #3
I'm not quite that far yet.

In metacode i'm trying to do this:

output: L[None    ]:P[Central ] Venator-Class Star Destroyer 'Resolute'  0 0 0
trigger: L[*]:P[*] * '*'  * * *

check amount of tables within table StellarObjects
if StellarObjects has a table with a 'name' value that matches %4 in it
then
delete the whole old table and create a new one with the values that are in the wildcards
else
create a new table into the StellarObjects table


Does that make any sense at all?
I might be trying to swallow a too big bite again like I've done a dozen times before with this script before.

I've never played with tables within tables and I just can't figure out how to conveniently get information out of and in to a tabled table.

I've so far only managed to get that shiptypes table to work semi-conveniently.

Could I get some syntax? :/
Australia Forum Administrator #4
Well that part is simple, since you want to add or replace based on a name. Something like:



StellarObjects ["%4"] = {  .... the data ..... }


With a table, inserting an entry replaces an existing one, if any.
Finland #5

shiptype = string.gsub ("%3", " ", "_")
if not stellarobjects then
stellarobjects = {}
elseif StellarObjects ["%4"] then
stellarobjects["%4"] = { "type"="shiptype", "leader"="%1", "position"="%2", "x"="%5", "y"="%6", "z"="%7" }
else
stellarobjects.%4 = { "type"="shiptype", "leader"="%1", "position"="%2", "x"="%5", "y"="%6", "z"="%7" }
end


-->

Compile error
World: Legends of the Jedi
Immediate execution
[string "Trigger: "]:24: '}' expected near '='

with 24 being the 5th line in the pasted code


I can't see anything wrong with the syntax there, what am I doing wrong here? :S
USA #6
Lua doesn't like using quotes around keynames. The "always works" syntax would be {["foo"] = 42, [100] = "a number"}, that is, with brackets around the key. Lua provides a shorthand that allows you to drop both the quotes and the brackets around a string key, so long as it's not something unreasonable like f$_!* or multiple words.

I'm also not sure what you're doing with the second and third branches of the if statement. The following statements are equivalent:

foo["bar"] = 42
foo.bar = 42


This goes back to what I was saying about Lua's special case for string keys. It's just syntatic sugar, they don't actually mean anything different. I think you're using one if the entry exists and one if it doesn't? It doesn't matter. :)

Lastly, you're defining a shiptype variable but you never use it. You do have type="shiptype", but that's a string literal. It won't pull the value out of the variable.

([EDIT]: Also, carefully consider what happens when there is no stellarobjects table. Only one branch of an if-statement is run every time it's executed, so when stellarobjects doesn't exist, it creates it, but doesn't add the current line to it.)

What I think you really want is this:

if not stellarobjects then
  stellarobjects = {}
end

stellarobjects["%4"] = {
  type = string.gsub ("%3", " ", "_"),
  leader = "%1",
  position = "%2",
  x = "%5",
  y = "%6",
  z = "%7",
}


Code styled for clarity. :)
Amended on Mon 06 Sep 2010 07:26 AM by Twisol
Australia Forum Administrator #7
Or to put it slightly differently, don't quote numbers, and don't quote keys.

So:


stellarobjects ["%4"] = { type=shiptype, leader="%1", position="%2", x=%5, y=%6, z=%7 }



[EDIT] Quoted position.
Amended on Mon 06 Sep 2010 08:46 AM by Nick Gammon
USA #8
Nick Gammon said:
don't quote numbers

Strictly speaking, that's up to how you want to treat your data, rather than a syntax issue. ;) You're right that that's probably what he wants though.

Position isn't a number, by the way: see the first post.
Amended on Mon 06 Sep 2010 08:15 AM by Twisol
Australia Forum Administrator #9
OK I fixed that. As for the numbers, well, quoted numbers are generally not that useful. I mentioned it further up.
Finland #10
Alright. I picked Twisol's layout because it is pretty and grabbed the xyz as values rather than strings, because they'll be used for calculating things rather than printing out to the output.

Now that stuff goes into the tables painlessly, I need to get them out of it too. I've never done "for blah blah do" sentences before and after checking out a few older posts I've managed to come up with this:

for t, x in stellarobjects do
print (stellarobjects.t [x])
end

-->

Run-time error
World: Legends of the Jedi
Immediate execution
[string "Trigger: "]:6: attempt to call a table value
stack traceback:
        [string "Trigger: "]:6: in main chunk


I am testing it out with a "print" for now to keep it simple until it works. What it should do is to get the xyz's out of the first entry, do calculations and draw and move on to the next one until the end of table.

I understand that "for t" means it does it until it comes across with a "nil", the x after it is used (?) as a variable in the syntax and "in stellarobjects" it is designated to work in that table. I couldn't find a good solid thread about "for" or anything else that I'd need to know in order to pull off this step. Ideas?
Australia Forum Administrator #11
You want:


for t, x in pairs (stellarobjects) do
  print (t, x)
end


You don't need to mention stellarobjects inside the loop. The for loop is pulling out the key and value (t is the key, x is the value) for each item in the table.
Australia Forum Administrator #12
And in your case the value is a table, so it would be something like:


for t, x in pairs (stellarobjects) do
  print (t, x.type, x.leader, x.position)
end


That is because the value "x" is really another table, so you can index into it to pull out the named items.
USA #13
Here's how the generic-for works in a nutshell:

local t = {'a', 'b', 'c', 'd', 'e'}

for key, value in ipairs(t) do
  print(key, value)
end


A table is simply a set of key/value pairs. The generic for lets you access these pairs as you iterate over each pair in the table.

They key concept to understand here is that of an "iterator". Above, ipairs() is an iterator. You give it a table, it does some stuff (see non-nutshell explanation below), and it returns new values to your left-side variables for each iteration until you're done. ipairs() goes over consecutive integer keys (i.e. 1, 2, 3, 4) until there's no entry for that key (i.e. t[num] is nil). pairs() goes over absolutely every entry in a table, but you have no guarantee it will come out in any particular order.

Your code above would be:
for name, details in pairs(stellarobjects) do
  print(name, " -- ", details)
end

--[[ Output: (example)
Resolute -- table: 11111111
MatteBlackTaxi13 -- table: 222222
Krakana -- table: 33333333
--]]


Notice that it won't print out the contents of the details value. To inspect a table, I recommend using require("tprint") like so:
require("tprint")
tprint(stellarobjects)


Note that tprint() is not an iterator, so you don't use it as for k,v in tprint(t) do end. It's just a regular function, like print(). :)



If you're still reading and want more, here's an extended explanation. I suggest reading PiL chapter 7 for more details on iterators [1], but here's a cursory overview of the details.

An iterator like pairs() and ipairs() is really an iterator factory. Iterator factories return a function, and it's that function that's called every iteration by the generic-for loop. For example:
local iter, tbl, state = pairs(t)
for key, value in iter, tbl, state do
  print(key, value)
end


Now things are getting interesting, eh? An iterator factory return three values: an iterator function, the object being iterated over, and some state. Lets simulate what a loop that uses pairs() does:
-- pairs() returns a function that already exists, called next().

local t = {foo = 1, bar = 42, baz = 50}

local key, value = nil, nil
while true do
  key, value = next(t, key)
  if key == nil then
    break
  end
  
  print(key, value)
end


Do you see what's happening? This is almost exactly what happens inside the for-loop implementation, except it's not limited to next().

I highly recommend PiL if you want to learn more about iterators, or just Lua in general. :)

[1] http://www.lua.org/pil/7.html