Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.
 Entire forum ➜ SMAUG ➜ Lua ➜ How to add Lua scripting to SMAUG

How to add Lua scripting to SMAUG

Posting of new messages is disabled at present.

Refresh page


Pages: 1 2  3  4  5  6  

Posted by Nick Gammon   Australia  (23,122 posts)  Bio   Forum Administrator
Date Tue 03 Jul 2007 12:48 AM (UTC)

Amended on Tue 03 Jul 2007 01:19 AM (UTC) by Nick Gammon

Message
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.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,122 posts)  Bio   Forum Administrator
Date Reply #1 on Tue 03 Jul 2007 01:00 AM (UTC)

Amended on Tue 03 Jul 2007 01:04 AM (UTC) by Nick Gammon

Message
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.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,122 posts)  Bio   Forum Administrator
Date Reply #2 on Tue 03 Jul 2007 01:11 AM (UTC)

Amended on Tue 03 Jul 2007 01:19 AM (UTC) by Nick Gammon

Message
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

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,122 posts)  Bio   Forum Administrator
Date Reply #3 on Tue 03 Jul 2007 01:17 AM (UTC)
Message
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


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by David Haley   USA  (3,881 posts)  Bio
Date Reply #4 on Tue 03 Jul 2007 01:29 AM (UTC)
Message
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...

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
Top

Posted by Nick Gammon   Australia  (23,122 posts)  Bio   Forum Administrator
Date Reply #5 on Tue 03 Jul 2007 02:27 AM (UTC)
Message
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.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Samson   USA  (683 posts)  Bio
Date Reply #6 on Tue 03 Jul 2007 02:22 PM (UTC)
Message
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.
Top

Posted by David Haley   USA  (3,881 posts)  Bio
Date Reply #7 on Tue 03 Jul 2007 07:54 PM (UTC)
Message
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.

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
Top

Posted by Nick Gammon   Australia  (23,122 posts)  Bio   Forum Administrator
Date Reply #8 on Tue 03 Jul 2007 09:21 PM (UTC)
Message
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.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by David Haley   USA  (3,881 posts)  Bio
Date Reply #9 on Tue 03 Jul 2007 10:11 PM (UTC)
Message
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.

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
Top

Posted by Nick Gammon   Australia  (23,122 posts)  Bio   Forum Administrator
Date Reply #10 on Wed 04 Jul 2007 01:06 AM (UTC)
Message
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.



- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by David Haley   USA  (3,881 posts)  Bio
Date Reply #11 on Wed 04 Jul 2007 01:32 AM (UTC)
Message
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.

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
Top

Posted by Nick Gammon   Australia  (23,122 posts)  Bio   Forum Administrator
Date Reply #12 on Wed 04 Jul 2007 01:57 AM (UTC)
Message
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 ...

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,122 posts)  Bio   Forum Administrator
Date Reply #13 on Wed 04 Jul 2007 02:12 AM (UTC)
Message
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.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by David Haley   USA  (3,881 posts)  Bio
Date Reply #14 on Wed 04 Jul 2007 02:26 AM (UTC)
Message
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.

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


246,645 views.

This is page 1, subject is 6 pages long: 1 2  3  4  5  6  [Next page]

Posting of new messages is disabled at present.

Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.