Sign in to follow this  
rodrix

LUA scripts

Recommended Posts

rodrix    122
Hi there! I am new to LUA and I am embbeding it into my engine. Please answer me this two questions: 1) Can I define a function name with dots in the name? In my C++ functions I have defined for Lua: "Mesh3D.New" and so I want to add another function in LUA called "Mesh3D.PaintRed". Can I do that in LUA, like this? Note that Mesh3D is not a class in LUA.
function Mesh3D.PaintRed(x)
        return 2*x;
end
2) I am working with pointers in LUA to find my objects and I am scared that the guys who writes the script (or even myself) passes a wrong type of pointer to the parameter (e.g: he passes a Texture pointer to an Object3d pointer parameter). If that occurs I will get a C++ access violation error and everything will crash, and it will be very difficult to find the bug on the LUA script. Therefore I proposed the following solution:
function SafePointer(myPointer, myPointerType)
      return {Pointer = myPointer, PointerType = myPointerType);
end

TYPE_3ds =1000; -->Constant

function Mesh3D.New3ds(Filepath,p1,p2)
     
local     tempPointer = Mesh3D.Unsafe_New3ds(p1,p2);
      return SafePointer(tempPointer ,  TYPE_3ds};

end

function Render.3ds(My3ds, p1,p2,p3)
if (My3ds.PointerType= TYPE_3ds) then
Render.3ds(My3ds.Pointer,p1,p2,p3);
else
     Debug.MessageBox("Render.3ds was called by passing incorrect object. Only My3ds object should be passed. A pointer of type " .. My3ds.PointerType .. "was passed.");       

     end

end

And my question is: if the user passes to the Render.3ds function a parameter that is not a table (like SafePointer), will the statement (My3ds.PointerType= TYPE_3ds) return NIL or will LUA crash with an error? If it's the second case, how do I check if My3ds is of SafePointer type? Thank you so much in advance! I really appreciate all your feedback! Rod [Edited by - Kylotan on March 7, 2007 5:28:00 AM]

Share this post


Link to post
Share on other sites
Roots    1625
(1): No I don't believe you can do that in Lua. The reason is that Lua uses "." as an operator, to access elements of a table for instance (table1.table2.my_key = 5). I believe Lua function syntax is: first letter must be a case-insensitive letter, followed by any number of letters, numbers, or underscores.


(2): This seems like overkill to me. You want to check each and every function parameter to ensure its correctness? That adds a small penalty cost for one function, but to put it in all your functions the penalty cost will add up linearly to the # of calls you make.

I think the answer depends on what binding library (if any) you are choosing to use in your game. For example, Luabind uses an exception handling mechanism: link. Using existing functionality like that seems like a much less painful idea to me.



Hope that some of what I said helps.

Share this post


Link to post
Share on other sites
Namatsu    198
1.) Yes this is possible Mesh3d would be a table in lua and because a function is a valid data type in lua this would be legal. The code below would also give you the same results

function chode(x)
return 2*x
end

Mesh3D.PaintRed = chode

Hopefully this helps.

2.)

I'm slightly confused as your safe pointer function doesn't actually check anything. I would actually suggest looking into a c++ solution where you have a class that encapsulates your pointer and the type and then all the objects that you actually pass around in lua will be pointers to a single class type. Then when you get the object in c++ you can do something like this.

// suto code below

CLuaSafePointer * pLSP = getdata();
if(pLSP.type == LSP_TYPEIWANT)
{
// do something
}
else
// throw error or ignore

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
Quote:
Original post by Namatsu
1.) Yes this is possible Mesh3d would be a table in lua and because a function is a valid data type in lua this would be legal. The code below would also give you the same results

function chode(x)
return 2*x
end

Mesh3D.PaintRed = chode

Hopefully this helps.

2.)

I'm slightly confused as your safe pointer function doesn't actually check anything. I would actually suggest looking into a c++ solution where you have a class that encapsulates your pointer and the type and then all the objects that you actually pass around in lua will be pointers to a single class type. Then when you get the object in c++ you can do something like this.

// suto code below

CLuaSafePointer * pLSP = getdata();
if(pLSP.type == LSP_TYPEIWANT)
{
// do something
}
else
// throw error or ignore


Thanks! The C++ encapsulation class seems like a great solution! I will implement that, it seems excellent.

About No. 1) Could you please explain the table approach in order to define my functions with a dot in the middle like Mesh3D.PaintRed ? I am new to lua and I don't really get what you are saying...

did you previously define Mesh3D = {}? What about PaintRed?
or did you define Mesh3D = {PaintRed= myFunction} ?

Thanks so much everyone!
Cheers!
Rpd

Share this post


Link to post
Share on other sites
rodrix    122
THinking again... it's not a so good idea, since even though it's easy to pass from LUA the pointer of the encapsulation class to C++, I have some C++ that push poitners to LUA. In order for this to work, I would have to maintain new instances of these "encapsulation" objects, and that would consume a lot of memory. I don't want to duplicate my data.

Therefore my idea of implementing SafePointer was that it returns a table with two values, the pointer, and the pointer type, so that i make the structure in LUA for every C++ pointer returning function, and then check the pointer parameters in LUA checking if the PointerType of that structure is the one that corresponds.

Quote:

2): This seems like overkill to me. You want to check each and every function parameter to ensure its correctness? That adds a small penalty cost for one function, but to put it in all your functions the penalty cost will add up linearly to the # of calls you make.

I think the answer depends on what binding library (if any) you are choosing to use in your game. For example, Luabind uses an exception handling mechanism: link. Using existing functionality like that seems like a much less painful idea to me.


Thanks Roots for your answer :). No I just want to check the parameters that are passed as pointers, no more. All the other parameters are left unchecked, and it is used only in functions that use pointer parameters.

Could you explain me more what do you mind by "binding library"? I am using LUA 5.1. Are you proposing me to catch C++ errors with LUA? the thing is that if I pass a bad pointer to the C++ function, there is no check in C++ to see if the parameter is right, so the code would "throw" no error, but crash the program instead. This is because I would never expect in C++ to pass a parameter of other type to a pointer (in C++ you would get compile error, while from LUA as I am using pMYObject* = (static_cast) LuaGetuserData(s,1), anything can be passed).
Note: I don't have my project with me now, the syntax of the previous function surely is different, but you get the idea :)

What do you suggest?!

Thanks so much in advance!
Rod

P.S: The use of my solution would be like this:


TYPE_3ds =1000; -->Constant
function SafePointer(myPointer, myPointerType)
return {Pointer = myPointer, PointerType = myPointerType);
end


function Mesh3D.New3ds(Filepath,p1,p2)

local tempPointer = Mesh3D.Unsafe_New3ds(p1,p2);
return SafePointer(tempPointer , TYPE_3ds};

end

function Render.3ds(My3ds, p1,p2,p3)
if (My3ds.PointerType= TYPE_3ds) then
Render.3ds(My3ds.Pointer,p1,p2,p3);
else
Debug.MessageBox("Render.3ds was called by passing incorrect object. Only My3ds object should be passed. A pointer of type " .. My3ds.PointerType .. "was passed.");

end

end

AirplaneMesh= Mesh3D.New3ds("MyPlane.3ds",34,2);
Render.3ds(AirplaneMesh, 5,2,3);
--> I am sure that AirplaneMesh is of type 3ds since I can check it's "PointerType" parameter.



Share this post


Link to post
Share on other sites
Namatsu    198
Quote:
Original post by Anonymous Poster

About No. 1) Could you please explain the table approach in order to define my functions with a dot in the middle like Mesh3D.PaintRed ? I am new to lua and I don't really get what you are saying...

did you previously define Mesh3D = {}? What about PaintRed?
or did you define Mesh3D = {PaintRed= myFunction} ?

Thanks so much everyone!
Cheers!
Rpd


as far as I know you cant have a '.' in a variable name, you are actually accessing a variable inside a table when you call Mesh3D.new in your script. having defined Mesh3D.new in c++, Mesh3D should be a table already. but if you were defining something purely in lua you would have to do the Mesh3D = {} first. Hopefully this explains things better.

Share this post


Link to post
Share on other sites
rodrix    122
Quote:
Original post by Namatsu

as far as I know you cant have a '.' in a variable name, you are actually accessing a variable inside a table when you call Mesh3D.new in your script. having defined Mesh3D.new in c++, Mesh3D should be a table already. but if you were defining something purely in lua you would have to do the Mesh3D = {} first. Hopefully this explains things better.


Yes Mesh3D.New is already defined in C++, so as you say Mesh3D it should be a table already. Since it is a table, then we agree that I don't have to define Mesh3D = {}, but how do I tell LUA that the new function PaintRed (not defined in C++) is a member of Table Mesh3D?

Like previous example : Mesh3D.PaintRed()?


Thanks so much!
Cheers!
Rod

Share this post


Link to post
Share on other sites
rodrix    122
Quote:
Original post by Namatsu
correct

function Mesh3d.PaintRed(x)
return 2*x
end

should work


wow that's what I wanted! thanks!

Oh, one more thing!:
In the following example, would LUA asume a.Fruit= nil, and call DoSomething or would LUA produce a runtime error?

local NewStructure = {Fruit = "banana", Color = "yellow");
local a = 4; --> simple number

if(a.Fruit ~= "banana")
DoSomething()
end

What about if a was not defined at all?

Thanks so much in advance!!!! This is all really helpful! Thanks :)

Cheers!
Rod

Share this post


Link to post
Share on other sites
Namatsu    198

local a = 4
if(a.Fruit ~= "banana") then
a=7
end



This will give you a error similar to this at runtime.

'attempting to index 'a' (a number)' or
'attempting to index 'a' (a nil value)'
if a was a nil value

I believe you would need to do something similar to this.


if type(a) == "table" then
if a.Fruit ~= "banana" then
a=7
end
end

Share this post


Link to post
Share on other sites
rodrix    122
Quote:
Original post by Namatsu


This will give you a error similar to this at runtime.

'attempting to index 'a' (a number)' or
'attempting to index 'a' (a nil value)'
if a was a nil value

I believe you would need to do something similar to this.
***


Thanks! :) I didn't know about type().
Is there a way to check the table type, like for example what happens if


a = {Fruit= "banana", Color= "Yellow"} -->Fruit table/structure
b = {Animal = "Rat", NumberOfLegs = 4} --> Animal Table/structure


if type(b) == "table" then
if b.Fruit ~= "banana" then
b=7
end
end


In this case b is also a table, but does not have the member Fruit. Will LUA return a runtime error or nil? How do I check if a is a Fruit table/structure or an Animal table/Structure?!

Thanks so much!!!! :)
Cheers!
Rod

Share this post


Link to post
Share on other sites
Namatsu    198

a = {Fruit= "banana", Color= "Yellow"} -->Fruit table/structure
b = {Animal = "Rat", NumberOfLegs = 4} --> Animal Table/structure


if type(b) == "table" then
if b.Fruit ~= "banana" then
b=7
end
end



yes this code is valid. the second 'if' statement would fail because b.Fruit is a nil value. You could do something like this to let you know if an invalid table was passed in. assuming you have a logger class bound to lua.


a = {Fruit= "banana", Color= "Yellow"} -->Fruit table/structure
b = {Animal = "Rat", NumberOfLegs = 4} --> Animal Table/structure


if type(b) == "table" then
if b.Fruit ~= "banana" then
b=7
else
Logger.Error("blah")
end
end

Share this post


Link to post
Share on other sites
rodrix    122
Oh.. by the way:

Should I call from C++


RenderElements::Mesh3D_ASE_Node *tempASE_Node = static_cast<RenderElements::Mesh3D_ASE_Node*>(lua_touserdata(s,1));

OR

RenderElements::Mesh3D_ASE_Node *tempASE_Node = (RenderElements::Mesh3D_ASE_Node*)(lua_touserdata(s,1));


In other words, should I call a static cast, or a simple cast?!
I saw it both ways on the internet.

Thanks so much!
Cheers!
Rod

Share this post


Link to post
Share on other sites
rodrix    122
Thanks Namatsu! :)

I managed that to work! Thank you so much!

However, I got another problem now...

Quote:

-->Constants for Pointer Types
TYPE_ASE = 1000;
TYPE_3ds=1001;
TYPE_Texture=1002;
TYPE_DisplayList=1003;
TYPE_Object3D=1004;
-->

function SafePointer(myPointer, myPointerType)
if (myPointer~= nil) then
return {Pointer = myPointer, PointerType = myPointerType};
else
--> Debug.MessageBox("Problem!");

return nil;
end

end

function CheckPointer(FunctionName, DesiredPointerType, VariableToTest)
if (type(VariableToTest) == "table") then
if (VariableToTest.PointerType == DesiredPointerType) then
return true;
else
--> Debug.MessageBox("Incorrect parameter passed in ".. FunctionName .. ". A Safe Pointer must be passed of type " .. DesiredPointerType .. ". Parameter passed was of type ' " .. type(VariableToTest) .. " '.", "Incorrect parameter passed in " .. FunctionName);

return false;

end

else
--> Debug.MessageBox("Incorrect parameter passed in ".. FunctionName .. ". A Safe Pointer was NOT passed", "Incorrect parameter passed in " .. FunctionName);

return false;

end

end

function Mesh3D.NewASE(Filepath,TextureFilter,p1,p2,p3)

local tempPointer =Mesh3D.UnsafeNewASE(Filepath, TextureFilter, p1,p2,p3,done);

LastSafePointer = SafePointer(tempPointer , TYPE_ASE);
return LastSafePointer;


end



function Render.ASE(SafePointer_ASE,p1, p2, p3, p4)

if CheckPointer("Render.ASE", TYPE_ASE, SafePointer_ASE) then
Render.UnsafeASE(SafePointer_ASE.Pointer, p1, p2,p3,p4,done);

end
end


(This is not really a quote but I haven't found how to do the code tag. [code] does not work ;)


So I first call:

mySafePointerTableASE =Mesh3D.NewASE("MyFile",1,2,3)
Render.ASE(mySafePointerTableASE, 4,5,6,7);

expecting that mySafePointerTableASE gets stored a table of type:
{Pointer = myPointer, PointerType = myPointerType}.
However when I call Render.ASE I get the following error:

"Attempt to index local 'SafePointer_ASE' (a userdata value)"

function Render.ASE(SafePointer_ASE,p1, p2, p3, p4)

if CheckPointer("Render.ASE", TYPE_ASE, SafePointer_ASE) then
Render.UnsafeASE(SafePointer_ASE.Pointer, p1, p2,p3,p4,done);

end
end



So yes... it's a userdata value (so the SafePointer structure got stored), but what's the problem with trying to access SafePointer_ASE.Pointer?!!?

Thanks so much in advance!
Cheers
Rod

Share this post


Link to post
Share on other sites
Namatsu    198
try changing it to this just to test.


function SafePointer(myPointer, myPointerType)
if (myPointer ~= nil) then
local retval = {Pointer = myPointer, PointerType = myPointerType}
return retval
else
...

Share this post


Link to post
Share on other sites
Dwiel    365
[ source ] is what you want

I hate to arrive to the party a little late, but I think all of these safe wrappers are a bit overkill.

There is actually a safe way to do this built into the language. The idea is that you create a userdata for each of your objects, like you are doing now. However, you also assign each a metatable. There would be a metatable for each object type. Then to check the type of an object, you simply check the userdata's metatable. This may be a bit over the top, however, it does allow you to treat your userdata like real objects. I think it is going to let you use Mesh3D.PaintRed act the way you want. I assume that Mesh3D is a class in C++ and you have mutlipule instances of this class. If this is not the case, then what I am suggesting is most likely over kill, otherwise, it is most likely the cleanest solution. So what exactly are you trying to do here? Are there many instances of the same C++ class that you would like to expose to Lua? Or is it mostly singleton type classes with only one instance?

Share this post


Link to post
Share on other sites
rodrix    122
Quote:
Original post by Namatsu
try changing it to this just to test.


function SafePointer(myPointer, myPointerType)
if (myPointer ~= nil) then
local retval = {Pointer = myPointer, PointerType = myPointerType}
return retval
else
...



Thanks so much Namatsu!!!
I got it fixed! I found another error in my code too, I was passing a wrong variable too. However applying this two fixes now it works :)


Cheres!
Rod

Share this post


Link to post
Share on other sites
rodrix    122
Quote:
Original post by Dwiel
[ source ] is what you want

I hate to arrive to the party a little late, but I think all of these safe wrappers are a bit overkill.

There is actually a safe way to do this built into the language. The idea is that you create a userdata for each of your objects, like you are doing now. However, you also assign each a metatable. There would be a metatable for each object type. Then to check the type of an object, you simply check the userdata's metatable. This may be a bit over the top, however, it does allow you to treat your userdata like real objects. I think it is going to let you use Mesh3D.PaintRed act the way you want. I assume that Mesh3D is a class in C++ and you have mutlipule instances of this class. If this is not the case, then what I am suggesting is most likely over kill, otherwise, it is most likely the cleanest solution. So what exactly are you trying to do here? Are there many instances of the same C++ class that you would like to expose to Lua? Or is it mostly singleton type classes with only one instance?


Hi Dwiel!
Thanks for joining the party! :)

I read your design proposal, and it seems interesting, although I am not understanding some points: How do I do this "Then to check the type of an object, you simply check the userdata's metatable."?

I guess that what you are proposing is to use "classes" in LUA using metatables and assigning functions to each of these classes right? I read a lot about this but didn't get it well yet. Could you provide me a simple example?

By the way, this is what I am trying to do:
I got in C++ many linked lists that contain objects. For example I have a linked list of 'ASE' objects. In LUA I am trying to create a new nodes and then pass this pointer nodes to another functions to manipulate them. Sometimes I need to call object's member functions and sometimes I have to pass the object's pointer to another functions. Most of the times I don't need to delete the nodes explicitly since all the linked list gets deleted at the end of the program, so I don't really need to explicitly call the deconstructor as it's managed by the linked list. So if I have to delete a node I don't want it to be implicit, but explicitly through another function like DeleteBlah()...

Maybe your class simple example will enlighten me... ;)

Thanks so much in advance!
Cheers!
Rod

Share this post


Link to post
Share on other sites
Dwiel    365
ok, so even a simple example ends up not being too simple. Here is an example which creates OO datatypes in LUA without C:


local Stick = {}

function newStick()
local stick = {}
setmetatable(stick, Stick)
return stick
end

function isStick(stick)
if getmetatable(stick) == Stick then
return true
else
return false
end
end

function Person_throwStick(person)
assert( isPerson(person) )
if person.stick ~= nil then
print(person.name .. " threw a stick")
person.stick = nil
else
print(person.name .. " must hold a stick before throwing it")
end
end

function Person_holdStick(person, stick)
assert( isPerson(person) )
assert( isStick(stick) )

person.stick = stick
print( person.name .. " is now holding a stick ")
end

local Person = {__index = {throwStick = Person_throwStick, holdStick = Person_holdStick}}

function isPerson(person)
if getmetatable(person) == Person then
return true
else
return false
end
end

function newPerson(name)
assert( type(name) == "string" )

local person = {}
setmetatable(person, Person)
person.name = name
return person
end


-- how it can be used
local p = newPerson("bob")
local s = newStick()
p:holdStick(s)
p:throwStick()
p:throwStick()

-- results in:

-- bob is now holding a stick
-- bob threw a stick
-- bob must hold a stick before throwing it



So, this is a nice sample using just lua. In your case, the Person table, the Stick table, the Person_ functions, etc would all be done in C. This way, the metatable would point to cfunctions instead of lua functions. Keep in mind that the p:holdStick(s) <==> p.holdStick(p, s)

define the metatables like this:

// these are the methods that MidiOut objects have
static const luaL_reg MidiOut_meta[] =
{
{"__gc", MidiOut_gc},
{"noteOn", MidiOut_noteOn},
{"noteOff", MidiOut_noteOff},
{"sendMessage", MidiOut_sendMessage},
{NULL, NULL}
};

static int MidiOut_register(lua_State* L)
{
luaL_newmetatable(L, MIDIOUT);
lua_pushliteral(L, "__index");
lua_newtable(L);
luaL_openlib(L, 0, MidiOut_meta, 0);
lua_rawset(L, -3);
lua_pop(L, 1);
return 1;
}



this is from my midi module which had a MidiOut object with the member functions noteOn, noteOff, and sendMessage. MidiOut_noteOn, etc are all defined and coded as you would expect as a typical C/Lua wrapper function.

here are some wrapper functions for creating new objects, pushing them onto the stack, safely pulling them off of the stack, etc.


#define MIDIOUT "MidiOut"

// some helper methods for accessing RtMidiOut:
static RtMidiOut* toMidiOut(lua_State *L, int index)
{
RtMidiOut **midiout = (RtMidiOut**)lua_touserdata(L, index);
if(midiout == NULL)
luaL_typerror(L, index, MIDIOUT);
return *midiout;
}

static RtMidiOut* checkMidiOut(lua_State* L, int index)
{
RtMidiOut** midiout;
RtMidiOut* ret;
luaL_checktype(L, index, LUA_TUSERDATA);
midiout = (RtMidiOut**)luaL_checkudata(L, index, MIDIOUT);
if(midiout == NULL)
luaL_typerror(L, index, MIDIOUT);
ret = *midiout;
if(!ret)
luaL_error(L, "null RtMidiOut*");
return ret;
}

static RtMidiOut** pushMidiOut(lua_State* L, RtMidiOut* mo)
{
RtMidiOut** midiout = (RtMidiOut**)lua_newuserdata(L, sizeof(RtMidiOut*));
*midiout = mo;
luaL_getmetatable(L, MIDIOUT);
lua_setmetatable(L, -2);
return midiout;
}


static int MidiOut_noteOn(lua_State *L)
{
RtMidiOut* midiout = checkMidiOut(L, 1);

midiout->noteOn( ... );

return 0;
}



Let me know if you have any more questions. I only recently figured all of this stuff out, so it will definitely help me get a clearer understanding of it all.

Share this post


Link to post
Share on other sites
rodrix    122
Quote:
Original post by Dwiel
ok, so even a simple example ends up not being too simple. Here is an example which creates OO datatypes in LUA without C:

*** Source Snippet Removed ***

So, this is a nice sample using just lua. In your case, the Person table, the Stick table, the Person_ functions, etc would all be done in C. This way, the metatable would point to cfunctions instead of lua functions. Keep in mind that the p:holdStick(s) <==> p.holdStick(p, s)

define the metatables like this:
*** Source Snippet Removed ***

this is from my midi module which had a MidiOut object with the member functions noteOn, noteOff, and sendMessage. MidiOut_noteOn, etc are all defined and coded as you would expect as a typical C/Lua wrapper function.

here are some wrapper functions for creating new objects, pushing them onto the stack, safely pulling them off of the stack, etc.

*** Source Snippet Removed ***

Let me know if you have any more questions. I only recently figured all of this stuff out, so it will definitely help me get a clearer understanding of it all.


Hi Dwiel!
Thanks so much for your answer!!! :)

I understood really well how LUA example code! Thank you very much!

Regarding the C++ example I found it more complicated.
I am not familiar with the commands:
lua_pushliteral(L, "__index");
lua_rawset(L, -3);
lua_pop(L, 1);
or lua_setmetatable(L, -2);

Could you point me as to where did you learn this? Information seems to be very scarce and succinct.

Thank you so much!
CHeers
Rod

Share this post


Link to post
Share on other sites
Dwiel    365
Yeah, no problem. I get most of my information from a few sources:

I use this many times a day as a good reference:
http://www.lua.org/manual/5.1/

I use this if I want something more than a reference. It's got some techniques and more explanation:
http://www.lua.org/pil/

Here is a little tutorial about the C/lua API:
http://lua-users.org/wiki/SimpleLuaApiExample

there are quite a few good tutorials on the wiki:
http://lua-users.org/wiki/
and tutorial section:
http://lua-users.org/wiki/TutorialDirectory

Glad to be able to help!

Edit: forgot to linkify

[Edited by - Dwiel on March 22, 2007 8:31:54 PM]

Share this post


Link to post
Share on other sites
rodrix    122
Dwiel thanks for all the info!!! :)
I have been using your links and they are really useful.

Thanks Namatsu, Roots, and Dwiel for all the help received up to now :)

I have a problem now I can't find the solution anywhere..
Let's see if anyone can help me:

I am trying to use a function that returns multiple parameters (with no success yet!):

this is my code in C++
Quote:

static int LUA_Object3D_GetPosition(lua_State* s)
{
Object3D * tempObject = (Object3D *) (lua_touserdata(s,1));

lua_pushnumber(s,tempObject->Position_Now.x);
lua_pushnumber(s,tempObject->Position_Now.y);
lua_pushnumber(s,tempObject->Position_Now.z);

return (3);
}


and this is my code in LUA:
Quote:

local x;
local y;
local z;
x,y,z = Object3D.GetPosition( Iterator1_Object3D);
Font.PrintCentered(0.1,"Position x:" .. x .."y:" .. y .. "z:" ..z );


.. and I am receiving that x, y , and z contain nil values.
"Cannot concatenate nil values"
What am I doing wrong?

Thanks so much everyone in advance!!!
Cheers,
Rod

Share this post


Link to post
Share on other sites
Dwiel    365
Thats really strange. That is exactly how you should do it as far as I can tell. I don't see any obvious bugs. Have you tried adjusting that exact code without the sendond and third pushnumber and returning 1 just to test to make sure that nothing else is causing problems?

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this