12 Nov, 2009, David Haley wrote in the 41st comment:
Votes: 0
ch:sendText("hello")
already is Lua code; 'ch' is a full user datum with a metatable such that it looks up sendText to know what it means. I don't have a quick example at the moment – I've been meaning to distill it from all the mess that is the full MUD system – but you can start by looking into userdata and metatables.

The idea is this:
ch:sendText("hello") <=> ch.sendText(ch, "hello")
this tries to find "sendText" in ch's metatable
sendText is a C function that takes a user datum and a string
sendText converts the user datum (which contains the globally unique ch id) to a Character* object.
It then called ch->sendText(<args>) – from the C++ side of things.
12 Nov, 2009, JohnnyStarr wrote in the 42nd comment:
Votes: 0
Great! I've read up on userdata and that makes perfect sense.
I'm gonna get my hands dirty and see how it goes.

BTW, I've decided to practice on Merc 2.1. It has everything I want from ROM without all bugs.
Plus, I'm going to use Lua for a ton of stuff like Pfiles, Areas, Scripts, Events, etc. So, I figure I might as well start
out with the least amount C I can get away with, without starting a brand new project.
13 Nov, 2009, JohnnyStarr wrote in the 43rd comment:
Votes: 0
After adding a global lua state, I'm starting to have all these cool ideas
about doing 'this' or 'that' with Lua. I guess the question I have now is:
Is there a line that I shouldn't cross? For example, there are a ton of good
color routines out there, and I'm sure I could take the time to make my own
in C, but it would be way easier in Lua, and with more flexibility. But, is
there any risk in speed? Based on some older conversations, I know this isn't
so much an issue now with our fast computers. Some feel that you can overuse
things like syntactic sugar. Would this fall into the same category?
14 Nov, 2009, David Haley wrote in the 44th comment:
Votes: 0
It's kind of hard to predict the performance impact that an unspecified task will have. :wink: In general, I would be a fan of "try it and see what happens". Don't rewrite stuff just because you feel like it; rewrite it if you see that you can do something new and better that you couldn't do before. Then, if it's too slow, you can try to improve the performance in native Lua, or port parts of it to C/C++, but honestly I don't think this will happen often at all (since you are unlikely to be doing things that really hurt performance).

IMO you have overused syntactic sugar when it's no longer easy to tell what exactly is going on. It's my usual theme of "confusing –> bad".
16 Nov, 2009, JohnnyStarr wrote in the 45th comment:
Votes: 0
What is the equivalent to \e escape character in Lua?
I came up with the simplest color converter today, but the final issue is the escape character:

function do_color(txt)
buf = txt
colors = {}
colors["{x"] = "\e = "\e[0m"
colors["{r"] = "\e[0;31m"
colors["{g"] = "\e[0;32m"
colors["{y"] = "\e[0;33m"
colors["{b"] = "\e[0;34m"
colors["{m"] = "\e[0;35m"
colors["{c"] = "\e[0;36m"
colors["{w"] = "\e[0;37m"
colors["{d"] = "\e[1;30m"
colors["{R"] = "\e[0;31m"
colors["{G"] = "\e[0;32m"
colors["{Y"] = "\e[0;33m"
colors["{B"] = "\e[0;34m"
colors["{M"] = "\e[0;35m"
colors["{C"] = "\e[0;36m"
colors["{W"] = "\e[0;37m"
colors["{D"] = "\e[1;30m"

– gsub takes care of everything using key(k) to value(v)
for k,v in pairs(colors) do buf = string.gsub(buf, k, v) end
return buf
end
[/code]
16 Nov, 2009, kiasyn wrote in the 46th comment:
Votes: 0
try \033

see this thread: http://www.gammon.com.au/forum/?id=6180
16 Nov, 2009, David Haley wrote in the 47th comment:
Votes: 0
There isn't one; you need to use the direct character code (27 in decimal). This would be written: "\27There isn't one; you need to use the direct character code (27 in decimal). This would be written: "\27[0m" for example. See also the [url=http://www.lua.org/manual/5.1/manual.htm... on lexical conventions[/url] from the Lua manual.

EDIT: Since Lua does not use octal codes in this case, \033 would not be correct.
16 Nov, 2009, JohnnyStarr wrote in the 48th comment:
Votes: 0
Thanks David. I'm so stoked now :)
It took me 3 minutes to write my color converter. I would probably be messing with pointer arithmetic still in C.
24 Nov, 2009, JohnnyStarr wrote in the 49th comment:
Votes: 0
@David
In Nick's example, he uses the light user data to store the ch pointer in the virtual stack, which is fine.
But, because I want an object oriented approach (ch:send("Hey you guys!")) I am going to use a metatable to add methods.

I remember you had mentioned that you use the 'actor' as an argument to your scripts. I would like to assess how to go about this.
How would I go about passing the CHAR_DATA pointer as the actor argument, and after doing so, how would it construct itself as "ch"
in the local scope of the function? Can you provide a brief example please?
24 Nov, 2009, David Haley wrote in the 50th comment:
Votes: 0
The basic idea is the following:

- actors/characters in C++ have a method "pushBinding" that, given a Lua state, pushes a user datum with appropriate metatable to act as an actor in Lua with methods etc.

- pushBinding, internally, will only create this binding object once. After creating it, it adds it to the registry using luaL_ref. (The table index in question is LUA_REGISTRYINDEX.) This way, the next time the binding is requested, the C++ actor object simply grabs it from the registry instead of creating another one. This is good because you can test character equality in Lua using == instead of having to get the underlying C++ pointer.

- the user datum that is created for Lua internally stores the character ID number. This number has two important guarantees: (a) it is globally unique, (b) it will never be repeated for another character. (The user datum is simply storage space for the ID number.)

- the user datum's metatable methods all take the user datum and translate it to its ID number, and then translate the ID number to the C++ character object pointer. But, if the ID is no longer valid, it raises a lua_error which may be caught safely in Lua and handled gracefully – or not, in which case it propagates to whatever C++ code called the Lua code, where you Really Want to handle it one way or another. This process is sort of analogous to detecting a null pointer dereference in C++, I suppose.



I use some template and C++ magic to make this process very smooth for writing new methods. I can probably post an example soon (although, it won't be an example that actually compiles without the rest of the framework). That will happen later tonight, probably, as I'm about to go off to visit Paris some more. :wink:
25 Nov, 2009, JohnnyStarr wrote in the 51st comment:
Votes: 0
I was thinking about writing area files in Lua instead of making .are files.
However, I'm not entirely certain how to go about it to where it is more useful.
So far, I've determined it is more safe in Lua, here's what I've figured so far:
– new area
a = area.new()
a:set_name("The Shire")
a:set_desc("A super cool area.")

– add room
r = room.new()
r:set_vnum("shire.bagend-entrance")
r:set_name("Bagend")
r:set_desc("You are in bagend, it's really cool.")

– exits
r:add_exit("west", "shire.bagend-bedroom", 0)
r:add_exit("east", "shire.bagend-kitchen", 0)

– resets
r:add_reset("mob", "shire.darkelf", 5, 10)
r:add_reset("obj", "mordor.one-ring", 60, 1)

– done
a:add_room(r)


Is this even worth doing? What's cool is it reminds me a bit of LPC style
loading.

EDIT: added exits, and resets to show a better example.
25 Nov, 2009, David Haley wrote in the 52nd comment:
Votes: 0
I dunno, just in that form the main advantage is that you don't have to write any parsing code. However, it is also more difficult to write area saving code. I kind of like keeping data and code separate, so I might serialize the area to Lua tables, and then do something like: a = area_from_table(t) (or similar).
To be honest, I start confusing myself when I think too much about fancy serialization and what should and shouldn't be serialized across game sessions, globally uniquely, during a single session, etc.
In the end of the day, you should save and load areas in a way that lets you do your interesting work quickly. The most important thing is to make sure that whatever you do, it is relatively easy to extend later on as your requirements change.
25 Nov, 2009, Runter wrote in the 53rd comment:
Votes: 0
DavidHaley said:
In the end of the day, you should save and load areas in a way that lets you do your interesting work quickly.


That.

After spending quite a bit of time in this muck I myself came to that conclusion.
26 Nov, 2009, JohnnyStarr wrote in the 54th comment:
Votes: 0
Are Lua table methods limited to get / set types when they are written in C?
For example, you can write "get_name" or "set_name", and this will push the value to the C object.
However, are there alternatives? Such as using an assignment operator?

mob:name = "Orc"
mob:level = 25
26 Nov, 2009, JohnnyStarr wrote in the 55th comment:
Votes: 0
@David,
How exactly do you pass a Userdata as the first argument in a Lua function from C?
I've been playing around with things, but I get this error:
Lua Error: ../lua/player.lua:5: attempt to index local 'ch' (a userdata value)


This is the code I'm trying out:
/* constructs new lua_State* for character */
void load_char_lua( CHAR_DATA *ch )
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaL_register(L, "mud", mudlib);
luaL_newmetatable(L, "lunacy.ch");
luaL_register(L, NULL, charlib);

if (luaL_loadfile(L, "../lua/player.lua") || lua_pcall(L, 0, 0, 0))
errorL ("LUA-ERROR [%s: %s]\r\n", __FUNCTION__, lua_tostring(L, -1));
if (L)
ch->L = L;
else
bug ("load_char_lua: bad lua state", 0);
}

// in call_lua…

lua_getglobal(L, func); /* stack: function … */
CHAR_DATA *a = (CHAR_DATA*)lua_newuserdata(L, sizeof(CHAR_DATA*));
luaL_getmetatable(L, "lunacy.ch");
lua_setmetatable(L, -3);
lua_pushstring(L, argument); /* stack: function(actor, argument) … */
err = lua_pcall(L, 2, 1, 0);
26 Nov, 2009, kiasyn wrote in the 56th comment:
Votes: 0
From Drazon:
void LuaEngine::pushUD( Instance *ud, const char *meta )
{
if ( ud )
{
Instance **ptr = (Instance **)lua_newuserdata( L, sizeof(Instance**) );
*ptr = ud;
luaL_getmetatable( L, meta );
lua_setmetatable( L, -2 );

/* Lua provides a registry, a pre-defined table that can be used by any C code to store whatever Lua value it needs to store.
This table is always located at pseudo-index LUA_REGISTRYINDEX. Any C library can store data into this table,
but it should take care to choose keys different from those used by other libraries, to avoid collisions.
Typically, you should use as key a string containing your library name or a light userdata with the address of a C object in your code.
The integer keys in the registry are used by the reference mechanism, implemented by the auxiliary library, and therefore should not be used for other purposes.
*/
}
else
lua_pushnil( L );
}
void LCharacter::push( lua_State *L, Character *ch )
{
luaEngine.pushUD( ch, CHARACTER_META );
}
26 Nov, 2009, David Haley wrote in the 57th comment:
Votes: 0
JohnnyStarr said:
Are Lua table methods limited to get / set types when they are written in C?
For example, you can write "get_name" or "set_name", and this will push the value to the C object.
However, are there alternatives? Such as using an assignment operator?

mob:name = "Orc"
mob:level = 25

You could do the above, yes, although you would have to use ., not :. You would add a hook to the metatable for the "newindex" event, which detects assignment to a field that does not exist. Then you would look up the field that is trying to be assigned, and do something appropriate with it.

Quote
How exactly do you pass a Userdata as the first argument in a Lua function from C?

I'd have to see your Lua code as well. But judging from the error message, it looks like you're not actually setting the metatable. And indeed, this line:
lua_setmetatable(L, -3);

should maybe be -2, because -1 is the metatable, -2 the udatum, and -3 the function.
26 Nov, 2009, JohnnyStarr wrote in the 58th comment:
Votes: 0
I wrote a quick stack dump routine and it gave me this output:
Lua Stack Dump:
(null)
(null)
(null)
(null)
Hey you guys!
Lua Error: ../lua/player.lua:5: attempt to index local 'ch' (a userdata value)


So, I thought I would save some time and show you the entire file:

#include <stdio.h>
#include <stdarg.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include "merc.h"

/* global Lua state declaration, used for server calls to Lua */
lua_State *global_L;

/* errorL() a general error message handler */
void errorL (char *fmt, …)
{
CHAR_DATA *ch;
char buf[MAX_STRING_LENGTH];
va_list args;
va_start (args, fmt);
vsprintf (buf, fmt, args);
va_end (args);

for (ch = char_list; ch != NULL; ch = ch->next)
if (!IS_NPC(ch))
send_to_char(buf, ch);
}

/* stack_dump() another testing function to view contents of stack */
static void stack_dump( lua_State *L, CHAR_DATA *ch )
{
send_to_char("Lua Stack Dump:\r\n", ch);

int i;
int top = lua_gettop(L);
for ( i = 1; i <= top; i++ ) {
int t = lua_type(L, i);
switch(t) {
case LUA_TSTRING:
sendf(ch, "%s", lua_tostring(L, i));
break;

case LUA_TBOOLEAN:
sendf(ch, lua_toboolean(L, i) ? "true" : "false");
break;

case LUA_TNUMBER:
sendf(ch, "%g", lua_tostring(L, i));
break;

case LUA_TUSERDATA:
sendf(ch, "%s", lua_tostring(L, i));
break;

default:
sendf(ch, "%s", lua_tostring(L, i));
break;
}
send_to_char("\r\n", ch);
}
}






/*
* Mudlib functions: ML
* Used in the global lua_State as a call to the system: "mud.function()"
*/

/* sends a message to every player, used for testing */
static int ML_send(lua_State *L)
{
CHAR_DATA *ch;
for (ch = char_list; ch != NULL; ch = ch->next)
if (!IS_NPC(ch))
sendf(ch, "%s\r\n", luaL_checkstring(L, 1));

log_string(luaL_checkstring(L, 1));

return 0;
}

/* End of mudlib ——————————————————*/


/*
* charlib functions CHL
*/

static int CHL_send( lua_State *L )
{
CHAR_DATA *ch = (CHAR_DATA *)lua_touserdata(L, 1);
send_to_char(luaL_checkstring(L, 2), ch);
return 1;
}

/* registry for mudlib */
static const struct luaL_reg mudlib [] = {
{"send", ML_send},
{NULL, NULL}
};

/* registry for charlib */
static const struct luaL_reg charlib [] = {
{"send", CHL_send},
{NULL, NULL}
};


/* constructs global lua_State* */
void load_global_lua(void)
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaL_register(L, "mud", mudlib);
if (luaL_loadfile(L, "../lua/mud.lua") || lua_pcall(L, 0, 0, 0))
errorL ("LUA-ERROR [%s: %s]\r\n", __FUNCTION__, lua_tostring(L, -1));
/* global Lua state */
global_L = L;
}

int set_env( lua_State *L )
{
lua_newtable(L);
lua_replace (L, LUA_ENVIRONINDEX);
return 0;
}

/* constructs new lua_State* for character */
void load_char_lua( CHAR_DATA *ch )
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);

luaL_register(L, "mud", mudlib);
luaL_newmetatable(L, "lunacy.ch");
luaL_register(L, NULL, charlib);

if (luaL_loadfile(L, "../lua/player.lua") || lua_pcall(L, 0, 0, 0))
errorL ("LUA-ERROR [%s: %s]\r\n", __FUNCTION__, lua_tostring(L, -1));

if (L)
ch->L = L;
else
bug ("load_char_lua: bad lua state", 0);

}

/* main lua interface. Hooks into lua and returns a char* string for output */
char* call_lua(lua_State *L, CHAR_DATA *actor, int call_type, char *func, char *argument)
{
int err = 0;
char *output = NULL;

switch (call_type) {
/* one text argument */
case LCALL_TEXT:
lua_getglobal(L, func); /* stack: function … */

CHAR_DATA **a = (CHAR_DATA**)lua_newuserdata(L, sizeof(CHAR_DATA**));
*a = actor;
luaL_getmetatable(L, "lunacy.ch");
lua_setmetatable(L, -2);

lua_pushstring(L, argument); /* stack: function(argument) … */

if (actor)
stack_dump(L, actor);

err = lua_pcall(L, 2, 1, 0);

if (lua_isstring(L, -1))
output = str_dup( lua_tostring(L, -1));
else
output = NULL;
break;

default:
bug("call_lua: bad call_type", 0);
}

if (err) {
errorL ("Lua Error: %s", lua_tostring(L, -1));
return NULL;
}

return (output);
}
26 Nov, 2009, David Haley wrote in the 59th comment:
Votes: 0
Your stack dump isn't quite right; note this part of the documentation to lua_tolstring (which is what lua_tostring does):

Quote
The Lua value must be a string or a number; otherwise, the function returns NULL.


So, you might be traversing the user datum and the function, and printing out (null) because you shouldn't be using lua_tostring on them.

So you should fix that and look at the stack again, and also show what this line (and surrounding) is up to: ../lua/player.lua:5
26 Nov, 2009, JohnnyStarr wrote in the 60th comment:
Votes: 0
Ok, I got everything to work, thanks for the tip.
Now there's one last thing that's weird.
I am sending a pointer to a pointer, is this the right way to do this?
CHAR_DATA **a = (CHAR_DATA**)lua_newuserdata(L, sizeof(CHAR_DATA**));
*a = actor;

// Now in CHL_getname()
static int CHL_getname( lua_State *L )
{
CHAR_DATA **ch = (CHAR_DATA **)lua_touserdata(L, 1);
lua_pushstring(L, ch->name);
return 1;
}


This gives me this error:

gcc -c -O -g -Wall  lua_interface.c
lua_interface.c: In function `CHL_getname':
lua_interface.c:123: error: request for member `name' in something not a structure or union


Which is odd because CHL_send works find:
static int CHL_send( lua_State *L )
{
CHAR_DATA **ch = (CHAR_DATA **)lua_touserdata(L, 1);
if (*ch)
sendf(*ch, "%s\r\n", luaL_checkstring(L, 2));
else
errorL("CHL_send: error");
return 0;
}
40.0/125