How to add Lua scripting to SMAUG

Posted by Nick Gammon on Tue 03 Jul 2007 12:48 AM — 82 posts, 344,152 views.

Australia Forum Administrator #0
I have been experimenting with adding Lua support to the SmaugFUSS server, with some good results.

Why use Lua?

  • Even for experienced C coders, Lua is easier to work with than C. For one thing, string management is much simpler. In C, virtually any operation using strings is tedious. You can't simply say something like 'if name == "Nick"' -- you need to use 'strcmp' to compare them. Then to make a new string you need to allocate memory for it, use 'strcpy' to move the data in, and remember to free it up later on.
  • Memory management in general is the bane of programmers working on a large, complex system like a MUD server. You need to allocate memory to hold strings, lists, and other structures, and these need to be deallocated when they are no longer needed (eg. when the player logs off) or you get a memory leak. Lua handles all this much more simply by having a garbage collection system that reclaims unused data.
  • Lua is very stable - it is virtually impossible to crash the Lua interpreter. The worst that will happen is that a syntax or runtime error is raised, which is handled cleanly and logged or otherwise reported.
  • The approach I have used is to make a Lua script "space" for every connected character. Thus they are all independent - a problem in one will not affect other players. This lets you develop and test changes (as an immortal) without affecting current players.
  • Although Lua will be slower than straight C, it will be faster than existing SMAUG "mud programs". This is because Lua is compiled first into pseudo-code, and then the pseudo-code is run at execution time. The SMAUG "mud programs" are continually interpreted, which is a much slower approach.
  • You can add extra variables to players (eg. a list of current quests, or a list of shops s/he has visited recently), by saving the Lua data into a separate "state file". This means you can add new features without having to make a single change to existing player files. For example, in my testing, I have a file "Nick" which is the current player file (this is unchanged from the standard format), and also a "Nick.lua" file which contains any extra variables used by the Lua scripts. This allows you to add heaps of extra features without having to recompile or change the way player files are read in, stored, or saved.
  • You can make changes "on the fly" - since changing Lua scripts doesn't require a reboot of the MUD, you can make a change, and to test it simply log out your own character and log back in. This makes it reprocess the script file, and you can be testing your change a moment later. No more annoying your player base by constantly rebooting (and crashing, if you make a mistake).


How is Lua incorporated?

There are a few main steps to take. These are all easily reversed if you choose not to go ahead with using Lua.

  • We need to store the Lua "script state" somewhere. I added this line to the bottom of the char_data structure:

    
        lua_State *L;  /* for Lua scripting - NJG  */
    

  • This state needs to be set to NULL initially so we know whether it is in use for a particular character, and thus needs to be freed up when they leave. In the load_char_obj function I set it to NULL:

    
       ch->L = NULL;  /* no Lua state yet */
    

  • The Lua state needs to be initialized as the player connects. I have done this in the nanny routine in two places - one for new characters, and one for existing ones.

    
      open_lua (ch);  /* fire up Lua state */
    


    This initializes the Lua script engine, and also adds into the script space additional routines for interacting with SMAUG (eg. get the player details, inventory, room details, mob, or object details).

    It then loads the Lua script file. I have made a new "lua" directory, and added a file "startup.lua" into that directory. The Lua script in that file will be read in and compiled, thus providing whatever functionality you require. You can use "dofile" or "require" directives in Lua to pull in additional files (eg. SQL database, or split various things into separate files for neatness).

  • When the character structure is being freed, the Lua state needs to be closed as well, thus freeing up all the memory it has used. Inside the free_char function I added this line:

    
       close_lua (ch);  /* close down Lua state */
    


    This is a small function that checks if ch->L is not NULL, and if so, calls lua_close to close it.
  • The next thing we need to do, to make this do anything useful, is to place "hooks" inside SMAUG in various places, to call into the Lua script at appropriate points.

    For example, if a new character is being created, we do this:

    
      call_lua (ch, "new_player", ch->name);
    


    This calls a function called new_player in the Lua script file. For example you might write this (in the Lua part):

    
    function new_player (name)
      mud.send_to_char ("Welcome to my MUD, " .. name .. "\n")
    end -- function new_player
    


    For existing characters, a different function named "reconnected" is called:

    
    function reconnected (name)
       dofile (get_file_name ())  -- load player state
    end -- reconnected
    


    This simple piece of code loads the "state" file (eg. Nick.lua) - the name is generated based on the player name by the function get_file_name.

    Another useful "hook" point is added into the function save_char_obj. This lets us know when the character is being saved, so we can save our Lua state as well:

    
      call_lua (ch, "saving", NULL);
    


    In my case I used the "serialize" file distributed with MUSHclient, which lets you convert a Lua table into a string, suitable for reading in with "dofile".

    
    require "serialize"
    
    function saving ()
    local charinfo = mud.character_info ()
    local fname = get_file_name ()
       local f = assert (io.open (fname, "w"))
       f:write (os.date ("-- Saved at: %c\n\n"))
       f:write (string.format ("-- Extra save file for %s\n\n", charinfo.name))
       
       f:write ((serialize.save ("current_quests")), "\n")    -- save quests info
       f:write ((serialize.save ("completed_quests")), "\n")
       f:close ()
    end -- saving
    


    This example saves the contents of the two tables "current_quests" and "completed_quests". Any other stuff you wanted to save would simply be added there.

    So far I have added a few other hook points:

    • entered_room --> the player has entered a room - we might be interested here, if visiting a particular room was part of a quest
    • looking --> the player has typed "look" - this might be a good place to add extra information (eg. 'mob xyz has a quest for you')
    • got_object --> the player has received an object - this might be important if the quest was to find things
    • killed_mob --> the player has killed a mob - this might be important if the quest is to kill things
    • char_update --> periodic character update (about once a minute) - useful for general housekeeping.


    You could obviously add more hooks - things that spring to mind are joining groups, dying, starting a fight, ending a fight.

    If you don't want a particular hook to do anything, then simply put an "empty" function into the Lua file.
  • The final part of getting all this to work is to make some useful functions that can be called from Lua, that get information about the current player or MUD state, or affect it.

    The purpose of these functions is to let you make decisions (eg. is the player in a certain room with a certain mob?), and then act upon them.

    So far, I have implemented these functions:

    • system_info --> find out stuff like the player directory, area directory etc.
    • character_info --> find out details about the character (eg. name, hp, etc.). If no argument is given the details are for the current character, otherwise you can specify another character by name.
    • mob_info --> find out details about a certain mob, given its vnum
    • room_info --> find out about the current room (if no vnum specified), or any room
    • object_info --> find out details about an object, given its vnum
    • inventory --> return a table of the player's inventory (included nested items, like contents of bags). If no argument is given the inventory is for the current character, otherwise you can specify another character by name.
    • equipped --> return a table of the items the player has equipped. If no argument is given the table is for the current character, otherwise you can specify another character by name.
    • object_name --> returns an object's short and long description
    • send_to_char --> sends a message to the player
    • players_in_room --> returns a table listing all players in the room (by name)
    • players_in_game --> returns a table listing all players in the game (by name)
    • mobs_in_room --> returns a table listing all mobs in the room (by vnum)
    • interpret --> interprets a command from the player (eg. mud.interpret ("sigh"))
    • gain_exp --> gives experience to the player (eg. for quest reward)
    • oinvoke --> invokes an object and gives it to the player (eg. for quest reward)


    Also the following ones which are intended to be used as "if" tests:

    • mobinworld --> returns the number of mobs of that vnum in the world, or false if none
    • mobinarea --> returns the number of mobs of that vnum in the current area, or false if none
    • mobinroom --> returns the number of mobs of that vnum in the current room, or false if none
    • carryingvnum --> returns true if the player is carrying an item of that vnum, false otherwise
    • wearingvnum --> returns true if the player is equipped with an item of that vnum, false otherwise
    • wearing --> returns the vnum of the item the player is wearing on the specified location (eg. finger), false if nothing


    These are all in the "mud" table, so you would actually use something like:

    
    t = mud.inventory ("nick")  -- get Nick's inventory
    


    This is hardly complete, but I am releasing this for comment at this stage. The existing code should make it obvious how to add more, and I would be open to suggestions for improving the "official" version.


You might be wondering how the Lua functions know which the current character is - that is, who has called the function, so that things like 'send_to_char' can work properly. The answer is that as part of setting up the script space, the current character pointer (CHAR_DATA type) is added to the Lua "environment" table. This is a table that is part of the script space, but hidden from Lua scripts. The first thing that most functions do is pull that pointer out of the environment table, so we know who the current character is.




So what can you do with it?

To test the general idea out, I wrote a simple "quest" system in Lua. As "quest" is already a keyword in SMAUG, I called it "task". The first thing was to add a "task" verb to SMAUG, and the appropriate handler. This is all the C code that was required:


void do_task( CHAR_DATA * ch, char *argument )
  {
  call_lua (ch, "task", argument); 
  }


Plus, adding this to mud.h:


DECLARE_DO_FUN( do_task );


As you can see, the C code is absolutely minimal, and can hardly go wrong. The entire rest of the task system is handled in the Lua part, with the argument (eg. 'task list') being passed down to the Lua code.

A simple preliminary implementation in Lua might be:


function task (arg)

  if not mud.mobinroom (10338) then
    send ("There are no tasks available here")
    return
  end -- if
  
  if arg == "" then
    send ("Type: 'task list' to show available tasks")
    return
  end -- if
  
  if arg == "list" then
    
    -- list tasks here
    
    return
  end  -- if listing
    
end -- function


This checks that you are at the task-giver (mob vnum 10338), and if so instructs you to type "task list" to see available tasks.

You can gradually build up the functionality of the task system, simply logging out and back again after making changes to the Lua file. This avoids distrupting other players while you are testing, as they do not have to experience a reboot (or crash) while you are testing.

If you make a syntax error, then a message appears in your client window, like this:


Error loading Lua startup file:
 ../lua/startup.lua:170: '=' expected near 'blah'


If you have a runtime error, you also see an error message:


Error executing Lua function 'task':
 ../lua/startup.lua:170: attempt to perform arithmetic on a nil value
stack traceback:
        ../lua/startup.lua:170: in function <../lua/startup.lua:168>


In my test version I kept two tables - current_quests and completed_quests. These are initially defined as empty tables in the startup.lua file:


current_quests = {}
completed_quests = {}


However as you start doing quests these tables fill up, and are serialized as described earlier as part of the loading/saving routines, so that it "remembers" how far you are through your tasks.

For example, this is what my Nick.lua file looks like:


-- Saved at: Tue 03 Jul 2007 10:28:37 EST

-- Extra save file for Nick

current_quests = {}
  current_quests.killnaga = {}
    current_quests.killnaga.kills_required = 5
    current_quests.killnaga.kills_done = 2
    current_quests.killnaga.name = "Kill those naga"
    current_quests.killnaga.kill_mob = 10306
completed_quests = {}


Based on this, if I type "task list", I see this:


1. Kill those naga (current) - killed 2 of 5 naga


Off I go and kill a naga (mob vnum 10306), and the killed_mob function detects that killing this mob is part of a quest, and generates this message:


Quest Kill those naga: killed 3 of 5


After killing a couple more, I can go and complete the quest. The Lua code reads like this:


  send ("Quest complete!")
  send ("You gain 20 xp!")
  mud.gain_exp (20)
  mud.oinvoke (21021, 1)
  fsend ("You receive %s", mud.object_name (21021))


My reward is 20 xp, plus a copy of object 21021. This is what I see in my client:


Quest complete!
You gain 20 xp!
You receive a loaf of bread


The whole quest system could be written much better than I have done it - this was really to illustrate the general idea.

One of the nice things about using Lua is that you could have a development and production version of your task system, and load the appropriate one based on something (like player name. For example:


function entered_game (name)
  if name == "Nick" then
    dofile ("../lua/quest_system_development.lua")
  else
    dofile ("../lua/quest_system.lua")
  end -- if
end -- entered_game


Thus, Nick gets the new version, while other players use the tried-and-tested existing version. When then new version is ready, you just rename the files.
Amended on Tue 03 Jul 2007 01:19 AM by Nick Gammon
Australia Forum Administrator #1
Files needed to try this stuff out are here:



I emphasize again that adding this code does not change in any way existing player or area files, so it is completely backwards-compatible.
Amended on Tue 03 Jul 2007 01:04 AM by Nick Gammon
Australia Forum Administrator #2
Below is a minimal startup.lua file - this shows the hooks for the various functions that are called from SMAUG, and demonstrates loading and saving the player state file:


os.setlocale ("", "time")

-- player-specific data - this will be saved to state file

current_quests = {}
completed_quests = {}


-- helper functions

-- send to player - with newline
function send (...)
  mud.send_to_char (table.concat {...} .. "\n\r")
end -- send

-- formatted send to player - with newline
function fsend (s, ...)
  mud.send_to_char (string.format (s, ...) .. "\n\r")
end -- send

-- send to player - without newline
function send_nocr (...)
  mud.send_to_char (table.concat {...})
end -- send_nocr

require "tprint"
require "serialize"

systeminfo = mud.system_info ()  -- get file names, directories

-- work out file name of player state file
function get_file_name ()
local charinfo = mud.character_info ()
  return systeminfo.PLAYER_DIR .. 
        string.lower (string.sub (charinfo.name, 1, 1)) .. 
        "/" ..
        charinfo.name ..
        ".lua"
end -- get_file_name

-- player has entered game 
function entered_game (name)
end -- entered_game

-- player has entered room 'vnum'
function entered_room (vnum)
end -- entered_room

-- player has killed mob 'vnum'
function killed_mob (vnum)
end -- killed_mob

-- player has received object 'vnum'
function got_object (vnum)
end -- got_object

-- player is looking around
function looking (arg)
end -- looking

-- player has entered game 
function reconnected (name)
   dofile (get_file_name ())  -- load player state
end -- reconnected

-- new player has joined game
function new_player (name)
end -- new_player

-- periodic update (each minute)
function char_update ()
end -- char_update

-- player is being saved
function saving ()
local charinfo = mud.character_info ()
local fname = get_file_name ()

   local f = assert (io.open (fname, "w"))
   f:write (os.date ("-- Saved at: %c\n\n"))
   f:write (string.format ("-- Extra save file for %s\n\n", charinfo.name))
   
   f:write ((serialize.save ("current_quests")), "\n")
   f:write ((serialize.save ("completed_quests")), "\n")
   f:close ()

   fsend ("*** Saved into file %s", get_file_name ())
end -- saving
Amended on Tue 03 Jul 2007 01:19 AM by Nick Gammon
Australia Forum Administrator #3
To see the sort of stuff made available to Lua functions, you could add some debugging into things like the 'entered_game' function:


function entered_game (name)
  if name == "Nick" then
    send ("Character info ...")
    tprint (mud.character_info ())
  end -- if Nick
end -- entered_game


I log out and log back to see this:


Character info ...
"logon"=1183425291
"deaf"=0
"damplus"=0
"numattacks"=0
"no_immune"=0
"mod_int"=1
"mod_lck"=0
"resistant"=0
"trust"=0
"perm_dex"=11
"perm_int"=17
"move"=110
"hitroll"=14
"wait"=0
"pc":
  "release_date"=0
  "bio"=""
  "fprompt"=""
  "filename"="Nick"
  "clan_name"=""
  "authed_by"=""
  "r_range_lo"=0
  "bamfin"=""
  "outcast_time"=0
  "o_range_lo"=0
  "m_range_lo"=0
  "flags"=32768
  "title"=" the Spell Student"
  "homepage"=""
  "o_range_hi"=0
  "prompt"=""
  "m_range_hi"=0
  "pkills"=0
  "mkills"=21
  "deity_name"=""
  "council_name"=""
  "auth_state"=0
  "pagerlen"=24
  "favor"=0
  "quest_number"=0
  "pdeaths"=0
  "charmies"=0
  "bamfout"=""
  "min_snoop"=0
  "quest_curr"=0
  "bestowments"=""
  "quest_accum"=0
  "wizinvis"=195
  "illegal_pk"=0
  "r_range_hi"=0
  "restore_time"=0
  "mdeaths"=0
  "rank"=""
"style"=2
"sex"=1
"mental_state"=0
"mod_cha"=0
"susceptible"=0
"no_susceptible"=0
"no_resistant"=0
"Class"=0
"long_descr"=""
"immune"=0
"gold"=0
"perm_str"=13
"mobthac0"=0
"saving_poison_death"=0
"speaks"=-1
"mod_str"=0
"speaking"=1
"perm_cha"=12
"substate"=0
"perm_lck"=13
"description"=""
"saving_wand"=0
"hitplus"=0
"weight"=149
"max_move"=110
"barenumdie"=1
"saving_para_petri"=0
"damroll"=7
"carry_weight"=52
"name"="Nick"
"position"=12
"timer"=0
"mod_dex"=0
"played"=36772
"carry_number"=4
"exp"=2100
"mana"=106
"saving_spell_staff"=-6
"baresizedie"=4
"race"=0
"mod_con"=1
"practice"=2
"mod_wis"=3
"saving_breath"=0
"hit"=1000
"armor"=75
"level"=195
"wimpy"=0
"height"=68
"num_fighting"=0
"max_hit"=1000
"short_descr"=""
"save_time"=0
"defposition"=0
"xflags"=0
"perm_wis"=13
"max_mana"=106
"emotional_state"=0
"alignment"=0
"perm_con"=13

USA #4
Heh... we should talk. :-) I'm going to send you an email -- I owe you a few anyhow and need to stop procrastinating on that. (My sincere apologies for that.)

Here are some things I've written at the SmaugFUSS forum about Lua & SMAUG:
http://www.fussproject.org/index.php?a=topic&t=1197

And then, asking a question based on our BabbleMUD discussions:
http://www.fussproject.org/index.php?a=topic&t=1199


At some point I will go update the BabbleMUD wiki with some of the thoughts I've had. I think I've decided to stick with text games, for now at least...
Australia Forum Administrator #5
It's funny that you are adding Lua to another SMAUG derivative. :)

Here I go, re-inventing the wheel again. ;)

Anyway, I hope that my posting above helps people add Lua to stock SMAUG - my view is that the easier it is to add improvements to a MUD, the more likely they are to be done.

Judging by what I read on your page, your quest system is a lot more sophisticated than what I did. Still, it shows what can be done - the power of Lua is very great, and it is an excellent tool for these situations.
USA #6
Dumb question here since I'm not all that familiar with Lua and what it requires, but is this implementation example enough to replace the existing mudprog system?

Doing so has been one of the hot issues being debated ( which met with huge resistance ) for doing with Smaug at some point.

It sort of looks to me like the call_lua hooks you have are intended to be used as prog triggers, similar to how one might use mprog_speech_trigger and such.
USA #7
You would need a little more, like making the characters first-class handles in script. Looking at Nick's Lua, it would appear that the character is kept as a global of sorts to the script environment, because a call like mud.get_character_info() is enough to know what character is being referred to.

I think to make this into a rich scripting environment, you would need to do ch.get_character_info(). That shouldn't be too hard given Nick's set-up; I think it should suffice to make ch into a userdatum with a metatable pointing to the same functions in the 'mud' table, and instead of using the global ch you would use the userdatum supplied in the indexing call to the metatable.

The reason you need this is to have handles to the various characters involved, if anything just the actor and the victim.
Australia Forum Administrator #8
Samson - it is intended as a supplement, and possibly it could eventually replace the existing system. As I said before, you don't need to change existing area or player files, and all the existing stuff continues to work the way it did.

Indeed, various events are triggered off by putting hooks in similar places to where the existing mud progs are, depending on the exact nature of what you are trying to do. For example, since posting the code above, I have been looking at what existing mud progs do a bit more, and added a "speech" hook. It goes like this:


void mprog_speech_trigger( char *txt, CHAR_DATA * actor )
{
   call_lua (actor, "speech", txt);

/// --> existing code here


Thus, when an actor says something, it gets routed to the "speech" function in Lua. Of course, this would happen a fair bit, so you would probably do a quick test about whether the actor is in a room where speech needs to be tested (ie. a simple table lookup), and then after that you could do a string.match to see if they said anything you need to match on.

In order to avoid resistance to change, I would initially use this system to add extra functionality, and demonstrate that it works and is easy to use.

I am gradually adding extra script functions to do things you would commonly want inside a mud prog (eg. find the class name, race name, current level). Also more stuff to modify the player state (eg. give gold, change rooms, force other characters, invoke new mobs).

So far I have only put the Lua code onto players and not mobs. Adding it to mobs would be pretty trivial, however I am not convinced it is required. A typical MUD might only have 50 players (on at one time), but hundreds of mobs.

Thus, checking players for triggers would be faster than for each mob.

After all, you only need to turn the logic around a bit. Rather than testing if a player enters the room with a mob (from the mob's point of view), you test if, once a player enters a room, if a particular mob is there (from the player's point of view).

Quote:

I think to make this into a rich scripting environment, you would need to do ch.get_character_info().


I am reluctant to have userdata for each player, because players (and mobs) tend to be ephemeral. The only one we can really rely upon is the current player - the one the Lua script is attached to. S/he must exist, or the script space wouldn't exist.

If you had userdata, the scripter would store it. Then, a minute later, when they go to use it (ch.get_character_info()), and that player has disconnected, then the userdata could be an invalid pointer.

The way it is currently written, you can already do this:


player_info = mud.get_character_info ("david")


This lets you get the info about any currently-connected player. It returns nil if that player doesn't exist, otherwise a table of their info.

Similarly most of the other functions (eg. getting inventory) use a similar method.

I am having a little trouble with mobs, as they aren't named uniquely, but at present you can do this:


player_info = mud.get_character_info (2450)


This will get information about the first mob with vnum 2450, *in the current room*. Effectively it is like a player lookup, but checks if it is an NPC and has the correct vnum, in the room list.

I think this is adequate for situations where you are in a room with a healer, and you want the healer to help the player. For example:


if <some reason> then
  mud.force (10394, "cast 'armor' " .. mud.char_name())
end -- if


This would get mob 10394 (assuming it is in the room with you) to cast a spell on the current player.




Looking at the existing mud progs in New Darkhaven (for example) it is obvious the existing system could be simplified.

For example, this appears about a dozen times:


> act_prog p is starved~
mpoload 20
give mushroom $n
mpforce $n eat mushroom
mpechoat $n Eating food will help your hunger and improve your mental state.
~


This sort of stuff is crying out to made into a single function, to be called from various places. For, example, the hook for act_prog, could check for "is starved" and then do a table lookup to see if the player is in one of 20 rooms where you are prepared to help it, and if so, go ahead and feed it.
USA #9
Quote:
I am reluctant to have userdata for each player, because players (and mobs) tend to be ephemeral. The only one we can really rely upon is the current player - the one the Lua script is attached to. S/he must exist, or the script space wouldn't exist.

If you had userdata, the scripter would store it. Then, a minute later, when they go to use it (ch.get_character_info()), and that player has disconnected, then the userdata could be an invalid pointer.

Well, that would give you a Lua nil pointer error, which would be handled gracefully. It's not too different from getting a nil back from the get_char_info() function.

I think not treating characters as full objects defeats a lot of the purpose of using Lua in the first place. How would I do something like this: for every fighter in the room, add +5 to their hit points. If you had to add hooks into C to do all of that, it would be recreating part of what's annoying about mudprog to begin with. Of course, it'd be nicer due to all the ways Lua is nicer than mudprog, but it seems to be still quite short of where this all should be leading.



Also, the scheme assumes that you have one Lua state per player, which might work for players (except on the particularly big MUDs) but wouldn't work for Lua states on rooms, mobs, objects, and so forth.

How would your system handle things like an entry prog on a mob? If the mob had its Lua state (not really feasible, I think, when you have thousands and thousands of mob instances as Darkstone does), then the functions you have would get the mob's state. You would need to be able to reference at minimum the player (or other mob) who just wandered in, to get their name and so forth.
Australia Forum Administrator #10
Quote:

Well, that would give you a Lua nil pointer error, which would be handled gracefully. It's not too different from getting a nil back from the get_char_info() function.


The userdatum might be invalid, but you wouldn't know it, that is my point. Say you have a userdatum which is really a pointer to a character structure for David (say, 0x12345678).

Now it is valid the moment we get it, but if we store it, and later try to use it, like: ch.gain_exp (100), but meanwhile David has logged out, and that piece of memory now points to something else.

There is no real way, given a pointer, to say "is that pointer valid?". It might point to what looks like valid memory, but if that memory has been freed it might be slowly being corrupted.

That is why I wanted to make the action routines take something, like a name or vnum, that can be validated *at that moment* to still be valid.

Quote:

I think not treating characters as full objects defeats a lot of the purpose of using Lua in the first place. How would I do something like this: for every fighter in the room, add +5 to their hit points.


You could use the function "players_in_room" I described earlier. This gives a table of the (names of the) players in your room (or in some room). Now you iterate that table, and given the player names, call a function to add 5 hp to each one.

I am trying to make things safe. Without putting all the data structures into Lua, the Lua script basically has to just get a copy of things (like player state). To make changes, we have to go back to something that can be verified, like a vnum or character name.

Quote:

How would your system handle things like an entry prog on a mob?


As I read the code, an entry prog is actually triggered when a player enters a room. There is already a hook for that in the existing system.

If you need a test for when a particular mob enters a room the player is in, then another hook for the player (eg. "something_entered_room") could handle it.

As a general rule, you are interested in things happening from the player's perspective, not the mob's perspective, so putting the program on the player seems the logical place to have it.


USA #11
Quote:
There is no real way, given a pointer, to say "is that pointer valid?". It might point to what looks like valid memory, but if that memory has been freed it might be slowly being corrupted.

Well, your ID system solves this problem quite nicely. :-)

A userdatum doesn't have to be a pointer; it could be the character ID.

I've used your ID system on Darkstone for quite some time and to great success; it's made a lot of things simpler like dealing with follows/masters logging out and the like.

Anyhow, if you did:
print(userdatum.name)
the metatable for userdatum would access the userdatum's value -- in this case, a character ID -- and find out, whoops, that character doesn't exist anymore. So it would issue a Lua error and everything is happy again.

Quote:
This gives a table of the (names of the) players in your room (or in some room).

Well, then if you wanted to do the same thing with mobs, you'd have to do things like:

- figure out the vnum of the mobs you want to iterate over
- figure out how many of that vnum there are
- loop over the number, getting mob-with-vnum-<vnum>-and-number-<number>

Seems like a lot of effort to me. You could spend time in C making special iterators and the like, but again I think using an id-based safe system as described above obviates the issues of dealing with pointers.

Of course, yes, you are completely correct about the pointers. If Lua were to have handles to characters, it would have to be wrapped in the safety of the ID system or something like it.

Quote:
As a general rule, you are interested in things happening from the player's perspective, not the mob's perspective, so putting the program on the player seems the logical place to have it.

Unfortunately I don't think that is always true. I have several real-world cases where I care about mobs doing things to each other as they wander around, and can easily concoct many more non-contrived examples.

Besides, having to load, for every player, every single mob's entry scripts seems like a lot of effort to me. If anything from a memory perspective: if you have 1,000 mobs with entry progs (that's not such an unreasonable amount, from empirical experience on Darkstone), and you have 50 players (again not unreasonable) then you need to store 50,000 functions, one in each player state. Compare that to storing 1,000 functions, one per mob. Even if you used lazy loading, and loaded mob progs into a player as they were needed, you'd have an awful lot of duplication of programs.
Australia Forum Administrator #12
Ah yes, the ID system. :)

That would certainly solve a lot of problems - my problem is I was trying to illustrate how to add Lua to stock SMAUG, which is written in C and not C++.

It is incredibly tedious in the current system to distinguish multiple mobs, of the same vnum, in the same room.

Quote:

Besides, having to load, for every player, every single mob's entry scripts seems like a lot of effort to me


You have a good point here, and this is partly why I am releasing this code for comment. Possibly there should be a single script space, and not one per player (or mob). If there was, then maybe as each mob (or player) is instantiated, it gets added to the script space, along with a unique ID, along the lines of what you described.

I will have to think about this a bit more ...
Australia Forum Administrator #13
A disadvantage of a single script space is losing the very ability I was pleased about, that you could keep retesting new code "on the fly" because each player had their own script space.
USA #14
Quote:
That would certainly solve a lot of problems - my problem is I was trying to illustrate how to add Lua to stock SMAUG, which is written in C and not C++.

Ah -- yes, that would be problematic. Given that constraint I share your aversion to using userdata. A fair bit more work would have to be put in to make the userdata safe.



As for the single script space:
What prevents you from testing new code? You can replace functions on the fly, so if you want to rewrite and re-test the function, you could just replace it.

I do this with whole files. It's true that I can't do require again because Lua is "my friend" and notes that it's already required (but I am going to write a re-require function that clears the package.loaded field for that file). So I just loadfile instead. It's been working fine for me.

I also don't think a script space per mob is all that realistic. One per player would work for almost all MUDs. But needing thousands of states, or tens of thousands for the MUDs with lots of mobs, just isn't really workable IMHO. It might be better to try to reproduce the goals of having multiple states, instead of just going for so much states directly.
Australia Forum Administrator #15
Well I did say *not* one per mob. :-)

My current system seems to work OK for most situations I have seen in stock SMAUG mud progs.

I have been adding in "give", "act" and "bribe" hooks.

In terms of adding functionality (like a quest system) it is probably fine as it is. If you are trying to do fine-grained stuff like handling thousands of mobs doing very specific things, then it might need tweaking for efficiency.

I'll try a quest system and see how I go.

USA #16
Part of my plan is to not do just what SMAUG mudprogs do. Part of the whole problem is that mudprog is very limited, and so, as a result, what you can do with them is very limited.

I'd like mudprogs that can do things as complicated as you could do in C(++).

Consider something like the following, written in skrypt, the home-brew language I wrote several years ago (so take it with the appropriate grain(s) of salt). It is a script attached to a room. The functions are events, trigger hooks if you will.


initRoom ()
{
	// Create a repeating timer of 5000 ms.
	eventTimerCreate(me, "timerHeal", 5000, -1);
}

enter(mobile ch)
{
	/*echoToRoom(me, "Hey, " + name(ch) + "! The people already in this room are:");
	forEach (mobile person, getRoomPeopleList(me) )
	{
		if ( person != ch )
			echoToRoom(me, name(person));
	}*/
	echoToRoom(me, "Enjoy your stay.");
}

timerHeal ()
{
	forEach ( mobile person, getRoomPeopleList(me) )
	{
		if ( getPosition(person) == "sleeping" )
		{
			affectHP(person, 4);
			//echoToRoom(me, "Healing " + name(person) + " for 4 HP.");
		}
		else if ( getPosition(person) == "resting" )
		{
			affectHP(person, 1);
			//echoToRoom(me, "Healing " + name(person) + " for 1 HP.");
		}
		//echoToRoom(me, name(person) + " is in this room, and is " + getPosition(person) + ".");
	}
}


timerKickOut()
{
	// Kick everybody out of the room
	forEach ( mobile person, getRoomPeopleList(me) )
	{
		transfer( person, "" );
	}
}


It should be pretty obvious what the syntax means.

This is impossible in mudprog. But this is just the very beginning of what I'd like to do.

Would your system be able to handle this? I'm not saying it can't, just that I would only be satisfied if it did. :-P

About the quest system:
What stumps me on the quest system is not that I don't know how to code it, but that I'm unsure what exactly I want to code. I think there's a little more work to do with designing the quest system. I mentioned some of those in a recent email; I'm still thinking about a hybrid system of some kind.

I'm starting to think of it in two concepts: quests vs. tasks. A quest is one that has steps and all that stuff, in which players move through steps via script events. A task is one in which the kinds of tasks are pre-defined, and would be things like "kill so-many monsters", "bring this so-many of this item to that npc", etc.

But anyhow -- one thing at a time. :-)
USA #17
Nick,

First off, thanks a heap for providing this. I think a lot of interest is out there, but the initial work that you've done here to get Smaug and Lua communicating is the bulk of what kept people from taking a deeper look at this. It's a huge effort and I for one am very appreciative that you've done all this for the community at large.

I did want to chime in with one thing I'm running up against though:


  Compiling o/lua_bits.o....
lua_bits.c:14: warning: ISO C90 does not support `long long'
lua_bits.c:15: warning: ISO C90 does not support `long long'
lua_bits.c: In function `bit_tonumber':
lua_bits.c:65: warning: pointer targets in initialization differ in signedness
lua_bits.c:76: warning: integer constant is too large for "long" type
make[1]: *** [o/lua_bits.o] Error 1


Any ideas on how to get around this one? There's apparently a compiler flag to avoid the long long warning, but the other warnings of course persist.

If you have any suggestions, I'd definitely appreciate it. Other than that, I've not had any issues so far.

Again, thanks a million for sharing this!
Amended on Mon 09 Jul 2007 03:33 AM by Gatewaysysop2
Australia Forum Administrator #18
I think they are all related, and it is strange you are getting them. I compiled using gcc under Linux.

Anyway, the bits file is not really required yet - I didn't use it for the initial stuff, and so far have only used it in one place to "and" a room flag.

I would remove it from the makefile for now, and remove the reference to it in the lua_scripting.c file.
Australia Forum Administrator #19
Quote:

Again, thanks a million for sharing this!


My pleasure, and you will be pleased to know the new task system is coming along nicely. One thing that is good is that SMAUG simply doesn't crash when I am playing with it. Sure, I get the odd script error, but that is neatly reported and logged. You just fix the script, log out and back again, and you are off again.
USA #20
Nick, have you decided to stick with the system of one state per player? How are you dealing with scripts that need to run on rooms or mobs independently of the player being there? Or are you considering that separately?

(Here's another example: on my game, some passages open or close depending on whether it's night-time or day-time. That needs to happen whether or not a player is there at the instant of time change.)

One advantage, though, is that it should be very easy to move a lot of your work to a system that has a global Lua state. That's another really nifty feature about Lua. :)
Australia Forum Administrator #21
So far I have one state per player.

As I continue to work on the task system, that seems adequate. The overhead of having 50 states (for 50 players) is a lot less than 10,000 states (for 10,000 mobs).

You could probably add an additional state to the overall game for stuff like periodically opening gates. This could be called from a heartbeat function (eg. every minute).
USA #22
Where are you storing the database of available tasks? Is that a separate Lua file somewhere that every player state loads in?

I'm still fairly uneasy about using one state per player; although it does give certain advantages (like you don't need handles to characters to send things to them) it seems like it makes a lot of other things more complex.

I'll have to play around with this and see what happens...
Australia Forum Administrator #23
Yes, every player gets it. It is handy for debugging, although I agree there is a potential for memory usage to creep up if you have lots of tasks, and lots of players on at once.

One technique could be to "require" subsets of all tasks, based on player level. For example, a level 40 player probably doesn't need in memory all the newbie tasks.

There is a potential problem with that if players hang onto level 1 tasks, when they are still level 50. I think in practice they won't because, they will want to free up the task list. However I think you could warn them that tasks which are well under their level will be discarded. I don't have an exact mechanism for showing that right now, but something like "recommended level" could be used to grade tasks.
USA #24
Hmm.

Getting this now:

make -s -j2 smaug
/usr/lib/gcc/i686-pc-cygwin/3.4.4/../../../../i686-pc-cygwin/bin/ld: cannot find
-llua


Lua gave no errors during installation. What is going on here?
USA #25
Some distros install Lua as lua51 or lua50, depending on the version. You can check /usr/lib/ in Cygwin to see what it is.
Australia Forum Administrator #26
I have:


/usr/local/lib/liblua.a


However you could make the lua library local I imagine, if you tweak the Makefile.
USA #27
Hrmmm.

It appears you have to use "make install INSTALL_TOP=/usr" for this to work properly, at least on my install of Cygwin. Goofy, but it works now.

Thanks for pointing me in the right direction!

Any insight on that 'long long' issue I brought up above?

USA #28
You can use -Wno-long-long to remove that warning.

The Lua install probably went into /usr/local/lib, which means that you need to add the -L/usr/local/lib flag to the makefile linker flags. But going into /usr itself is ok, too.

I think that Cygwin itself has a package for Lua, which you might want to use, too. (It probably doesn't make any difference, though.)
USA #29
I tried that flag and that did quiet the warning, but it still gives me the other error I pasted from the output. :-/

USA #30

const unsigned char * text = luaL_checkstring (L, 1);


becomes


const unsigned char * text = (const unsigned char*) luaL_checkstring (L, 1);


and


maxvalue = 4503599627370496 / base; // 2^52


becomes


maxvalue = ((Integer) 4503599627370496) / ((Integer) base); // 2^52


That should do it...
USA #31
The maxvalue line is still generating the same error as before. Any other ideas on how to get around this?
USA #32
Err... well you could try dividing it by 64 (using an external calculator), and then adding the result together 64 times. (Adjust 64 by whatever factor is needed to bring the result to a value that fits inside an int.)

Then you'd divide that by 'base'.

Not a pretty solution, but . . .
Australia Forum Administrator #33
Don't get too bogged down with this function. I am not even using bit_tonumber at this stage. Just comment out the entire function, and remove the line:


 {"tonumber", bit_tonumber},  // new by Nick


... which is near the end of the file.

USA #34
At this point, that works for me. :)

Looks like Lua is in, up and running at this point. I'm gonna have to get those Lua texts I saw on Amazon and start pouring through at at work when I'm supposed to be programming in SAS (which is far, far too easy by comparison to C!).

Kudos again for all your help guys!
Amended on Tue 10 Jul 2007 03:10 AM by Gatewaysysop2
Australia Forum Administrator #35
Excellent!

This is what I had to do to get it to compile under Cygin:

  • Install Lua 5.1 and make it like this:

    
    make "CC=gcc -mno-cygwin" mingw
    

  • Copy various Lua .h files around so they could be found:

    
    cp ~/lua-5.1.1/src/lua.h /usr/include
    cp ~/lua-5.1.1/src/luaconf.h /usr/include
    cp ~/lua-5.1.1/src/lualib.h /usr/include
    cp ~/lua-5.1.1/src/lauxlib.h /usr/include
    

  • Edit the Makefile that comes with SMAUG Fuss to uncomment the line about Cygwin:

    
    #Uncomment to compile in Cygwin
    CYGWIN = -DCYGWIN
    

  • Copy the Lua DLL into the src directory:

    
     cp ~/lua-5.1.1/src/lua51.dll ~/smaugfuss/src
    

  • Amend the Makefile to link against the Lua DLL:

    
    L_FLAGS = $(PROF) $(SOLARIS_LINK) -lz $(NEED_DL) lua51.dll -lm
    

  • Edit the lua_bits.c file to remove the bit_tonumber function.
  • Edit the lua_bits.c file to comment out or remove this line:

    
      {"tonumber", bit_tonumber},  // new by Nick
    


Then it should compile and run OK.

Amended on Tue 10 Jul 2007 03:25 AM by Nick Gammon
USA #36
Well, I've tried to add the task, whereis and the reset modules to my SMAUG 1.4a source. I've fixed most of the errors that popped up that were mostly 'caused by tweaks/changes that had been made to the source. However, there's a set of errors that keep coming up and I'm not quite sure what should be done to fix them.

lua_scripting.c: In function `L_getchar':
lua_scripting.c:123: `LUA_ENVIRONINDEX' undeclared (first use in this function)
lua_scripting.c:123: (Each undeclared identifier is reported only once
lua_scripting.c:123: for each function it appears in.)
lua_scripting.c: In function `L_system_info':
lua_scripting.c:365: warning: implicit declaration of function `lua_setfield'
lua_scripting.c: In function `L_mob_info':
lua_scripting.c:1016: warning: passing arg 2 of `lua_pushstring' from incompatible pointer type
lua_scripting.c: In function `build_nested_object':
lua_scripting.c:1645: warning: implicit declaration of function `lua_objlen'
lua_scripting.c:1650: warning: implicit declaration of function `lua_getfield'
lua_scripting.c: In function `RegisterLuaRoutines':
lua_scripting.c:2444: `LUA_ENVIRONINDEX' undeclared (first use in this function)
lua_scripting.c:2450: warning: implicit declaration of function `luaL_register'
lua_scripting.c: In function `open_lua':
lua_scripting.c:2475: warning: implicit declaration of function `luaL_newstate'
lua_scripting.c:2475: warning: initialization makes pointer from integer without a cast
lua_scripting.c:2484: warning: implicit declaration of function `luaL_openlibs'
lua_scripting.c: In function `open_mud_lua':
lua_scripting.c:2677: warning: assignment makes pointer from integer without a cast


I searched through the smaugfuss files to find where/if LUA_ENVIRONINDEX was defined and I can't seem to find it anywhere. Also, there are a lot of implicit declarations of lua functions that I'm not sure how to fix either.

Any suggestions?
Australia Forum Administrator #37
They will be in the lua include files mentioned above. Make sure you have installed Lua, and that the include files are in your normal include place (/usr/include).

If you can't modify that, copy the Lua includes to your src directory, and change the includes to use local files. eg.

Change:


#include <lua.h>

to:

#include "lua.h"


I think those includes are just in mud.h.
USA #38
My server admins installed Lua on their system, so these files should be available. Would the version of Lua installed make a difference to which header file should be included?
USA #39
Ok, I copied the header files into the src directory and still comes up with the same errors. I'm thinking there's a version conflict. They've got Lua 5.0.2 installed. What version do these changes require?
Australia Forum Administrator #40
My Lua code used some of the features of Lua 5.1, from memory.

You should be able to type "lua" at the command prompt to see if Lua is available. Also check that the include files are where you expect them. Perhaps they installed Lua without copying the include files.

There is absolutely nothing to stop you doing a local install. To quote from the INSTALL file that comes with Lua:


  If you want to install Lua locally, then do "make local". This will
  create directories bin, include, lib, man, and install Lua there as
  follows:

    bin:        lua luac
    include:    lua.h luaconf.h lualib.h lauxlib.h lua.hpp
    lib:        liblua.a
    man/man1:   lua.1 luac.1


That gives you the include files, and the liblua.a that you link against in the Makefile.
Australia Forum Administrator #41
Quote:

They've got Lua 5.0.2 installed. What version do these changes require?


You replied while I was typing. :)

Lua 5.1 would be good, just use a local version as I described above. The size is so small it won't matter if you aren't using the one they installed.
USA #42
This is beginning to become a pain in the neck. I know the payoff will be worth it once I get this all right.

After, finally, successfully installing Lua 5.1.2 as local, I go to compile the MUD code. I get so many errors, well actually warnings, that it scrolls through about 3 or 4 pages worth.

Small section of the errors/warnings:
lua_scripting.c: In function `L_mob_info':
lua_scripting.c:1016: warning: passing arg 2 of `lua_pushstring' from incompatible pointer type
lua_scripting.c: In function `RegisterLuaRoutines':
lua_scripting.c:2450: warning: implicit declaration of function `luaL_register'
lua_scripting.c: In function `open_lua':
lua_scripting.c:2475: warning: implicit declaration of function `luaL_newstate'
lua_scripting.c:2475: warning: initialization makes pointer from integer without a cast
lua_scripting.c:2484: warning: implicit declaration of function `luaL_openlibs'
lua_scripting.c: In function `open_mud_lua':
lua_scripting.c:2677: warning: assignment makes pointer from integer without a cast
gcc -c  -O -g3 -Wall -Wuninitialized    -DSMAUG  -DTIMEFORMAT   lua_bits.c
lua_bits.c: In function `luaopen_bits':
lua_bits.c:138: warning: implicit declaration of function `luaL_register'
gcc -c  -O -g3 -Wall -Wuninitialized    -DSMAUG  -DTIMEFORMAT   mt19937ar.c
gcc -c  -O -g3 -Wall -Wuninitialized    -DSMAUG  -DTIMEFORMAT   lua_tables.c
distcc[21152] ERROR: compile ***** on localhost failed
lua_tables.c:79: `AT_BLACK_BLINK' undeclared here (not in a function)
lua_tables.c:79: initializer element is not constant
lua_tables.c:79: (near initialization for `at_colours_table[34].val')
lua_tables.c:79: initializer element is not constant
lua_tables.c:79: (near initialization for `at_colours_table[34]')
lua_tables.c:79: initializer element is not constant
lua_tables.c:79: (near initialization for `at_colours_table[35]')

This goes on for many lines giving warnings for the same things through line 161 of lua_tables.c. This is a lot more than I was getting before.

What in the world have I done wrong? Suggestions for fixing this mess?

If this has something to do with linking against the liblua.a file in the Makefile, then I'm not sure I know what to do with that...
Australia Forum Administrator #43
All this stuff:

warning: implicit declaration of function `luaL_register'

Looks like it didn't find the lua .h files. What were the first few errors? They are usually the ones to fix.

Make sure the files:


lua.h
luaconf.h
lualib.h
lauxlib.h


... are in your src directory, and that you changed mud.h to use the quotes rather than the angled brackets, like I mentioned further up. There may be a file included from one of them (luaconfig.h or something) - just make sure you got all of the include files.
USA #44
Further fiddling...

After making the includes use local headers it cleared up some of the warnings. lua_scripting.c still leaves me with this:
lua_scripting.c: In function `L_mob_info':
lua_scripting.c:1016: warning: passing arg 2 of `lua_pushstring' from incompatible pointer type

All the same errors/warnings remain for lua_tables.c though.
USA #45
I've copied all these into the src directory: lauxlib.h, luaconf.h, lua.h and lualib.h which were noted as being needed for development.

As for the first errors, those were actually the first errors. Everything else compiled with no warnings or errors.

These are the errors that first appear
lua_scripting.c: In function `L_mob_info':
lua_scripting.c:1016: warning: passing arg 2 of `lua_pushstring' from incompatible pointer type
gcc -c  -O -g3 -Wall -Wuninitialized    -DSMAUG  -DTIMEFORMAT   lua_bits.c
gcc -c  -O -g3 -Wall -Wuninitialized    -DSMAUG  -DTIMEFORMAT   mt19937ar.c
gcc -c  -O -g3 -Wall -Wuninitialized    -DSMAUG  -DTIMEFORMAT   lua_tables.c
distcc[30706] ERROR: compile /tmp/darwin/lua_tables.tmp.nymph.mudmagic.com.30698.i on localhost failed
lua_tables.c:79: `AT_BLACK_BLINK' undeclared here (not in a function)
lua_tables.c:79: initializer element is not constant
lua_tables.c:79: (near initialization for `at_colours_table[34].val')
lua_tables.c:79: initializer element is not constant
lua_tables.c:79: (near initialization for `at_colours_table[34]')
lua_tables.c:79: initializer element is not constant
lua_tables.c:79: (near initialization for `at_colours_table[35]')
lua_tables.c:80: `AT_BLOOD_BLINK' undeclared here (not in a function)
and continue on until finally ending with:
lua_tables.c:159: initializer element is not constant
lua_tables.c:159: (near initialization for `object_types_table[64]')
lua_tables.c:161: initializer element is not constant
lua_tables.c:161: (near initialization for `object_types_table[65]')
make[1]: *** [lua_tables.o] Error 1


Ideas?
Amended on Sat 28 Jul 2007 12:57 AM by Darwin
USA #46
Eh, warnings caused by differences in item and color tables. Got those fixed. Now I'm getting errors from the .o files.
lua_scripting.o(.text+0x5a): In function `optboolean':
lua_scripting.c:84: undefined reference to `luaL_checknumber'
lua_scripting.o(.text+0x8e): In function `check_vnum':
lua_scripting.c:90: undefined reference to `luaL_checknumber'
lua_scripting.o(.text+0xc5):lua_scripting.c:92: undefined reference to `luaL_error'
lua_scripting.o(.text+0xee): In function `make_char_ud':
lua_scripting.c:101: undefined reference to `luaL_error'
lua_scripting.o(.text+0x10e):lua_scripting.c:104: undefined reference to `lua_getfield'


I've no idea how to fix this.

edit: Just for clarification, these are not the only errors I get. I get similar errors from the other lua_ .o files. There's many more from the lua_scripting.o but these are few enough to list here:
lua_bits.o(.text+0xe): In function `bit_bnot':
lua_bits.c:47: undefined reference to `luaL_checknumber'
lua_bits.o(.text+0x6a): In function `bit_band':
lua_bits.c:48: undefined reference to `luaL_checknumber'
lua_bits.o(.text+0xa6):lua_bits.c:48: undefined reference to `luaL_checknumber'
lua_bits.o(.text+0x11d): In function `bit_bor':
lua_bits.c:49: undefined reference to `luaL_checknumber'
lua_bits.o(.text+0x159):lua_bits.c:49: more undefined references to `luaL_checknumber' follow
lua_bits.o(.text+0x48d): In function `bit_tonumber':
lua_bits.c:62: undefined reference to `luaL_optinteger'
lua_bits.o(.text+0x49f):lua_bits.c:65: undefined reference to `luaL_checklstring'
lua_bits.o(.text+0x4cf):lua_bits.c:73: undefined reference to `luaL_argerror'
lua_bits.o(.text+0x55d):lua_bits.c:97: undefined reference to `luaL_error'
lua_bits.o(.text+0x5c3):lua_bits.c:107: undefined reference to `luaL_error'
lua_bits.o(.text+0x645): In function `luaopen_bits':
lua_bits.c:138: undefined reference to `luaL_register'
lua_tables.o(.text+0x11): In function `MakeFlagsTable':
lua_tables.c:48: undefined reference to `lua_createtable'
lua_tables.o(.text+0x5d):lua_tables.c:54: undefined reference to `lua_setfield'
collect2: ld returned 1 exit status
distcc[15165] ERROR: compile (null) on localhost failed
make[1]: *** [smaug] Error 1
Amended on Sat 28 Jul 2007 02:06 AM by Darwin
Australia Forum Administrator #47
Are you compiling this under OS/X? If so, I might try that myself and see what happens.
USA #48
I think it was mostly a linking problem. I added the liblua.a file to the L_FLAGS and it removed most of the errors as it was compiled. However, I'm still recieving these errors:

gcc -c  -O -g3 -Wall -Wuninitialized    -DSMAUG  -DTIMEFORMAT   lua_scripting.c
lua_scripting.c: In function `L_mob_info':
lua_scripting.c:1016: warning: passing arg 2 of `lua_pushstring' from incompatible pointer type

And these related to the .o files:
lua/lib/liblua.a(loadlib.o)(.text+0x19): In function `ll_load':
: undefined reference to `dlopen'
lua/lib/liblua.a(loadlib.o)(.text+0x31): In function `ll_load':
: undefined reference to `dlerror'
lua/lib/liblua.a(loadlib.o)(.text+0x56): In function `ll_sym':
: undefined reference to `dlsym'
lua/lib/liblua.a(loadlib.o)(.text+0x6e): In function `ll_sym':
: undefined reference to `dlerror'
lua/lib/liblua.a(loadlib.o)(.text+0x5): In function `ll_unloadlib':
: undefined reference to `dlclose'
collect2: ld returned 1 exit status
distcc[1517] ERROR: compile (null) on localhost failed
make[1]: *** [smaug] Error 1


I'm compiling under a version of Linux. So far, this is the least amount, and the complete list, of errors that I receive while trying to compile.
Australia Forum Administrator #49
I think I got those messages about `ll_load' until I had -ldl in my Makefile. In other words, in Makefile:


#Comment it out if you get errors about ldl not being found.
NEED_DL = -ldl


Note the line is not commented out.

As for this:

lua_scripting.c:1016: warning: passing arg 2 of `lua_pushstring' from incompatible pointer type



Can you show me what line 1016 in lua_scripting.c is? I have changed my copy, and it is not at the same place.
USA #50
Awesome. Adding
#Comment it out if you get errors about ldl not being found.
NEED_DL = -ldl
to the Makefile fixed that problem. It finally compiled with only that one warning.

As for what that warning is:
lua_scripting.c:1016:
  MOB_STR_ITEM (spec_fun);

Changed from
  MOB_STR_ITEM (spec_funname);
due to that being what it's called in my source.

Related stuffs that might be nice to know:
in mud.h
typedef bool	SPEC_FUN	args( ( CHAR_DATA *ch ) );

struct	mob_index_data
{
    MOB_INDEX_DATA *	next;
    MOB_INDEX_DATA *	next_sort;
    SPEC_FUN *		spec_fun;
...
};
Australia Forum Administrator #51
Great. You could always omit lines like that. I just means it won't put "spec_fun" into the table returned by mob_info. Probably no big deal.
USA #52
Good and bad news.
Good news: It compiles just fine. No errors, no warnings.

Bad news: MUD crashes as soon as I log in.
Australia Forum Administrator #53
Try doing a "make clean" and then "make", just in case.

If that doesn't help, time to use gdb. Some of your "tweaks" may have changed the sense of things.

Change to the area directory, then:

gdb ../src/smaug

Then type "run" and after it starts up, log in. When it crashes type "bt" into the gdb window, and post the results.
USA #54
I have a habit of always doing make clean before actually compiling.

I did as you suggested and when it crashed, bt gave this:
Program received signal SIGSEGV, Segmentation fault.
luaG_typeerror (L=0xa37df60, o=0xa3e4e1c, op=0x41cda4 "call") at ldebug.c:517
517     ldebug.c: No such file or directory.
        in ldebug.c
(gdb) bt
#0  luaG_typeerror (L=0xa37df60, o=0xa3e4e1c, op=0x41cda4 "call") at ldebug.c:517
#1  0x004116fc in tryfuncTM (L=0xa37df60, func=0xa3e4e1c) at ldo.c:211
#2  0x004117aa in luaD_precall (L=0xa37df60, func=0xa3e4e1c) at ldo.c:225
#3  0x00411abf in luaD_call (L=0xa37df60, func=0xa3e4e1c, nResults=0) at ldo.c:311
#4  0x081709bd in lua_call ()
#5  0x0817461e in luaL_openlibs ()
#6  0x0816e7db in open_lua (ch=0xa37df60) at lua_scripting.c:2484
#7  0x080a6b0c in nanny (d=0xa379fc0, argument=0xbfffbb70 "******") at comm.c:1827
#8  0x080a5030 in game_loop () at comm.c:723
#9  0x080a47df in main (argc=2, argv=0xbfffc390) at comm.c:318
Amended on Sat 28 Jul 2007 04:16 AM by Darwin
Australia Forum Administrator #55
Strange it should crash when opening Lua. I am wondering if, with all the mucking around with .h files and everything, you have got the Lua 5.0 and Lua 5.1 intermixed somehow.

Make sure that the .h files are all copied from the Lua 5.1 distribution, and that the liblua.a file is also from when you built it.
USA #56
I did that. The link to the liblua.a file uses an absolute path to where I installed the 5.1.2 version. I also went into the include directory and copied all the required .h files into my src directory. The only other thing that I can think of is that the server installed version is messing with the version I installed.

Thoughts?
Australia Forum Administrator #57
I can't see what is wrong if you did as you described. Perhaps change to the lua source directory (which is /home/nick/lua-5.1.1/src in my case) and try typing:


./lua


That should at least fire up your local copy of Lua. See if you get this message:


Lua 5.1.1  Copyright (C) 1994-2006 Lua.org, PUC-Rio
>


That at least confirms Lua is operational. I can't help much more than that, unless you want to send me a message through the forum mail, giving me a login and password for your shell account so I can try various things for myself. I will understand if you don't want to. If you do, I suggest changing the password temporarily to something new, let me try to fix it for an hour, and change it back. Better also give me the IP address of the server.
USA #58
$ ./lua
Lua 5.1.2 Copyright (C) 1994-2007 Lua.org, PUC-Rio

I'll send you the info via email in a moment.
Australia Forum Administrator #59
OK we worked it out. In case anyone is wondering, this is how a line in the Makefile looked:


L_FLAGS = $(OPT_FLAG) $(PROF) $(SOLARIS_LINK) $(NEED_DL) $(NEED_CRYPT) ~/lua/lib/liblua.a -llua -lm


However that was effectively linking against both versions of Lua. I simply removed the Lua 5.0 version, like this:


L_FLAGS = $(OPT_FLAG) $(PROF) $(SOLARIS_LINK) $(NEED_DL) $(NEED_CRYPT) ~/lua/lib/liblua.a -lm


Then it worked OK.
Amended on Sat 28 Jul 2007 05:35 AM by Nick Gammon
USA #60
Thanks for all your help.

Now on to adding the tasks.
USA #61
Well, so much for that. After setting up the resets, editing the tasks and setting locations for whereis, I find that the lua call for the reset crashes the mud. Comment that out since it's not really needed. Recompile, start it up log in, the task and whereis commands display no output

The mud registers that the commands are being used but it seems as though the call_lua function isn't returning anything or processing any data given to it.

I've been at this all day so it's probably something minor that I overlooked. I'll try again tomorrow.
USA #62
I added some debugging stuff in the call_lua function to confirm that the function was being called. It is indeed being called and with the right arguments. It appears that the find_lua_function err.. function isn't finding the uh.. function.
This is the output I get from the debugging I added:
task

Inside call_lua function.
        ch = Darwin, fname = task, argument = 
Did not find lua function: task

whereis

Inside call_lua function.
        ch = Darwin, fname = whereis, argument = 
Did not find lua function: whereis

The call_lua function gets called in the other events and also prints out the debugging info as well:
Inside call_lua function.
        ch = Darwin, fname = looking, argument = (null)
Inside call_lua function.
        ch = Darwin, fname = entered_game, argument = Darwin

So I know those, at least, are working.

Ideas on what broke this?
Australia Forum Administrator #63
Look at the MUD output log (ie. for bug messages). It won't find those functions if there was an error loading the Lua startup script. For example, if I deliberately introduce an error into it, and connect, I see stuff like this:


Sun Jul 29 07:35:12 2007 :: Admin (10.0.0.4) has connected.
Sun Jul 29 07:35:12 2007 :: [*****] BUG: Error loading Lua startup file ../lua/startup.lua:
 ../lua/startup.lua:2: '=' expected near 'afd'
Sun Jul 29 07:35:12 2007 :: [*****] BUG: Warning: Lua script function 'reconnected' does not exist


The startup.lua file implements those commands by "requiring" (loading in) extra files, like this:


-- install other stuff here (like task system)

task    = require ("tasks").task  -- module - provides 'task' command handler
whereis = require ("whereis").whereis -- module - provides 'whereis' command handler


If that isn't it, take a look at the start of find_lua_function:


static lua_State * find_lua_function (CHAR_DATA * ch, const char * fname)
  {
  lua_State *L = ch->L;
  
  if (!L || !fname)
    return NULL;  /* can't do it */


The first thing it checks is that you have a Lua state set up (ie. not a NULL pointer). If the state wasn't initialized on a reconnect that would be it. It was working yesterday.

Have you done a hotboot? We found you needed to set up the Lua state after doing that. You need a call to open the Lua state when the hotboot finishes. This is the code I had there:


        char_to_room( d->character, d->character->in_room );
         act( AT_MAGIC, "A puff of ethereal smoke dissipates around you!", d->character, NULL, NULL, TO_CHAR );
         act( AT_MAGIC, "$n appears in a puff of ethereal smoke!", d->character, NULL, NULL, TO_ROOM );
         d->connected = CON_PLAYING;
         open_lua (d->character);  /* fire up Lua state */
         call_lua (d->character, "reconnected", d->character->name);

USA #64
Ah, as usual, it was my tweaking that lead to my own downfall. I mistakenly left out a double quote from a task function I added. After fixing that, it all works as it should.

Thanks a ton. This will certainly help to add a lot of variety to the game.
USA #65
Thanks, Nick. This is really nice having this scripting available for added functionality to the mud code. I'm actually having fun adding tasks and stuff. The flexibility of this code is really nice. There's so many possibilities with it.
Australia Forum Administrator #66
Glad it is working for you.

There is a lot of scope here for MUDs to make quests (tasks) that are fun, and also help advance players along (eg. to find new towns).

For higher level players, you can give them quests which reward them with gold, experience, and equipment for doing the quests.

Another interesting add-on would be a reputation system, but I might leave that for a while. :)
USA #67
I've been tinkering with the finish_task function trying to make it use the mud.msg_game function to send a mud-wide [INFO] line informing everyone of a completed task. This is the same kind of line that serves the same function for the other various quests we have. I've tried this with no success:
mud.msg_game (string.format("&Y[INFO] %s has completed task: %s", mud.char_name, task.name))

Basically, I want the line to be in yellow and formatted like:
[INFO] <player> has completed task: <task name>

I'm hoping that is the right function to use. However, it just sends out a script error message and exits the finish_task function prematurely before marking the task complete.

The task module is great and my players are loving it. They also think the whereis command is "awesome" too. :)
Australia Forum Administrator #68
It helps to copy and paste the error message.
Australia Forum Administrator #69
Probably the problem is your use of mud.char_name.

That is a function, see:

http://www.gammon.com.au/forum/?id=8015

That means, to call it you need to supply arguments, even if it is an empty argument string. Try this:


mud.msg_game (string.format("&Y[INFO] %s has completed task: %s", mud.char_name (), task.name))

USA #70
The error message, I think, was something generic about a script error and the premature exiting of the function was due to where I had placed that line. In either case, your suggestion worked and I moved the line to below setting the task complete so even if it did error again, the task would still be marked as completed. The players are loving these things and wanting more. Great job on such a simple and flexible questing system. :)
USA #71
Ok, my server has lua 5.1 on it. I put my mud on and tried to compile. It compiles all the way to the very end and them I get a ton of undefined references. I'm not linking correctly or something, I don't know.

here's my makefile:

CC      = g++
#PROF    = -p

#Uncomment to compile in Cygwin
#CYGWIN = -DCYGWIN

#Uncomment the line below if you are getting undefined references to dlsym, dlopen, and dlclose.
#Comment it out if you get errors about ldl not being found.
NEED_DL = -ldl

#Some systems need this for dynamic linking to work.
EXPORT_SYMBOLS = -export-dynamic

# Uncomment the two lines below if compiling on a Solaris box
#SOLARIS_FLAG = -Dsun -DSYSV
#SOLARIS_LINK = -lnsl -lsocket

#IMC2 - Comment out to disable IMC2 support
IMC = 1

W_FLAGS = -Wall -Werror -Wshadow -Wformat-security -Wpointer-arith -Wcast-align -Wredundant-decls -Wconversion

C_FLAGS = -g2 $(W_FLAGS) $(SOLARIS_FLAG) $(PROF) $(EXPORT_SYMBOLS)
L_FLAGS = $(PROF) $(SOLARIS_LINK) -lz $(NEED_DL) ~/old/lua/lib/liblua.a -llua -lm
#D_FLAGS : For the DNS Slave process. No need in linking all the extra libs for this.
D_FLAGS = -g2 -O $(PROF) $(SOLARIS_LINK)

C_FILES = act_comm.c act_info.c act_move.c act_obj.c act_wiz.c ban.c board.c boards.c \
          build.c chess.c clans.c color.c comm.c comments.c const.c db.c deity.c \
          dns.c fight.c handler.c hashstr.c hint.c hotboot.c imm_host.c interp.c \
          liquids.c magic.c makeobjs.c mapout.c mapper.c mccp.c \
          misc.c mpxset.c mud_comm.c mud_prog.c news.c planes.c player.c polymorph.c \
          renumber.c reset.c save.c services.c sha256.c shops.c skills.c special.c tables.c \
          track.c update.c variables.c lua_scripting.c lua_bits.c mt19937ar.c lua_tables.c \
          mem.c


ifdef IMC
   C_FILES := imc.c $(C_FILES)
   C_FLAGS := $(C_FLAGS) -DIMC -DIMCSMAUG
endif

O_FILES := $(patsubst %.c,o/%.o,$(C_FILES))

H_FILES = $(wildcard *.h)

all:
        $(MAKE) -s smaug
        $(MAKE) -s dns

# pull in dependency info for *existing* .o files
-include dependencies.d

ifdef CYGWIN
smaug: $(O_FILES)
        rm -f smaug.exe
        dlltool --export-all --output-def smaug.def $(O_FILES)
        dlltool --dllname smaug.exe --output-exp smaug.exp --def smaug.def
        $(CC) -o smaug.exe $(O_FILES) smaug.exp $(L_FLAGS)
        @echo "Generating dependency file ...";
        @$(CC) -MM $(C_FLAGS) $(C_FILES) > dependencies.d
        @perl -pi -e 's.^([a-z]).o/$$1.g' dependencies.d
        @echo "Done compiling mud.";
        chmod g+w smaug.exe
        chmod a+x smaug
        chmod g+w $(O_FILES)

clean:
        @rm -f o/*.o smaug dependencies.d resolver resolver.o *~
endif

dns: resolver.o
        rm -f resolver
        $(CC) $(D_FLAGS) -o resolver resolver.o
        @echo "Done compiling DNS resolver.";
        chmod g+w resolver
        chmod a+x resolver
        chmod g+w resolver.o

indent:
        indent -ts3 -nut -nsaf -nsai -nsaw -npcs -npsl -ncs -nbc -bls -prs -bap -cbi0 -cli3 -bli0 -l125 -lp -i3 -cdb -c1 -cd1 -sc -pmt $(C_FILES)
        indent -ts3 -nut -nsaf -nsai -nsaw -npcs -npsl -ncs -nbc -bls -prs -bap -cbi0 -cli3 -bli0 -l125 -lp -i3 -cdb -c1 -cd1 -sc -pmt $(H_FILES)

indentclean:
        rm *.c~ *.h~

o/%.o: %.c
        echo "  Compiling $@....";
        $(CC) -c $(C_FLAGS) $< -o $@

.c.o: mud.h
        $(CC) -c $(C_FLAGS) $<
                                                        


I've tried different types of L_FLAGS
This:

L_FLAGS = $(PROF) $(SOLARIS_LINK) -lz $(NEED_DL) ~/old/lua/lib/liblua.a -llua -lm


This:

L_FLAGS = $(PROF) $(SOLARIS_LINK) -lz $(NEED_DL) ~/old/lua/lib/liblua.a -lm


This:

L_FLAGS = $(PROF) $(SOLARIS_LINK) -lz $(NEED_DL) -llua -lm


This:

L_FLAGS = $(PROF) $(SOLARIS_LINK) -lz $(NEED_DL) -lm


and it still errors at the very end.

USA #72
What kind of errors are we talking about here?
USA #73
I installed it locally and it worked first time.

For future reference, to make locally, you have to do this:

make linux

then

make local

Then point to your liblua.a file in your Makefile:

L_FLAGS = $(PROF) $(SOLARIS_LINK) -lz $(NEED_DL) ~/your/folder/lib/liblua.a -lm
Amended on Fri 17 Aug 2007 08:57 PM by Orik
#74
Okay, I am trying to add lua to my FotE based mud. I got all of the regular errors out and get to the end. I think there's a problem linking the lua headers but don't know why. Here are the errors...


lua_scripting.c:732: undefined reference to `lua_isnumber(lua_State*, int)'
lua_scripting.c:739: undefined reference to `lua_createtable(lua_State*, int, int)'
lua_scripting.c:743: undefined reference to `lua_pushstring(lua_State*, char const*)'
lua_scripting.c:743: undefined reference to `lua_setfield(lua_State*, int, char const*)'


Thats not all of them, but a bunch that look like that. I have the -llua in the L_FLAGS of my makefile like I read to do. Also, I downloaded FUSS with lua support added by Nick, and it compiled error free. So can anyone tell me what I am doing wrong. Apparently the lua is working globally for it to work with FUSS w/lua.

Thanks in Advance,
KeB
Australia Forum Administrator #75
Is FotE done in C++ (rather than C?).

If so, that would explain the missing references. You will need to put extern "C" around your .h files. I am not at my usual PC right now, but it will look something like this:


extern "C"  {
#include "lua.h"
// other Lua includes
}

#76
You were right on the money. Btw regular FotE isn't compiled with C++ but a while back I started using the C++ compiler instead of the C one. Thanks for the help, I looked over the code probably 30 times before posting and couldn't figure it out for the life of me. Never would have thought that the problem was with me using the C++ compiler.

Thanks Again,
KeB
#77
6 Dragons mud is now using Lua Code....

THANKS NICK!!!

http://6dragons.org
Australia Forum Administrator #78
Glad you like it!
USA #79
ok. I've been having a problem with the undefined references as well. Here is the errors.


make -s smaug
/***/baklua/lib/liblua.a(ldebug.o)(.text+0x92f): In function `luaG_runerror':
: undefined reference to `__stack_chk_fail'
/***/baklua/lib/liblua.a(ldo.o)(.text+0x51d): In function `luaD_callhook':
: undefined reference to `__stack_chk_fail'
/***/baklua/lib/liblua.a(ldump.o)(.text+0x3f0): In function `luaU_dump':
: undefined reference to `__stack_chk_fail'
/***/baklua/lib/liblua.a(lobject.o)(.text+0x68c): In function `luaO_pushvfstring':
: undefined reference to `__stack_chk_fail'
/***/baklua/lib/liblua.a(lundump.o)(.text+0x74d): In function `luaU_undump':
: undefined reference to `__stack_chk_fail'
/***/baklua/lib/liblua.a(lvm.o)(.text+0x82a): more undefined references to `__stack_chk_fail' follow


I have lua local on my server. do I need to change something in ldo.c, ldump.c and so on? or is it something in my actual smaug src files?


******************EDIT*******************
Turns out it was a lua issue so I just reinstalled lua locally and it worked.
Amended on Sat 19 Jul 2008 07:44 PM by Orik
#80
Thread rezzing here, but I wanted to ask if you had a version of SmaugFuss 1.9 with Lua already integrated?
Australia Forum Administrator #81
I don't think so, this thread is about 5 years old and the source is probably 2 PCs back, plus I wouldn't be sure what other changes I made.

But if you download the linked files and do a bit of fiddling around it should be possible to get it working.