Questions about starting a new MUD codebase from scratch using Lua...

Posted by Sofakng on Thu 09 Aug 2007 01:31 PM — 37 posts, 141,731 views.

#0
I've read many of the excellent articles (posts) on this site about how to integrate Lua in SMAUG as well as other information about using Lua with MUDs in general, but I still have a few questions.

My main problem is that I'm still not sure how to structure the MUD to use Lua.

The articles by Nick Gammon indicated that when he integrated Lua with SMAUG he created one lua_state for each connected character, plus one "global" lua_state for other MUD functions.

Is this really the best way to go?

What if you only use one lua_state but use coroutines for every single action and yield/resume as needed?

I've never really used Lua before (nor have I ever integrated it), but here is what I'm thinking:

Every time a script needs to be executed, I simply push it onto the main Lua stack and execute it using lua_resume(). If user-input is needed, the coroutine would yield and resume after input is acquired. Other events could work the same way or I can use triggers.

Example: A sword has been equipped and the OnExecute Lua script sets a timer to damage the player in 60 seconds unless he removes the sword. This can be done via a timer system (scheduler?) or using a yield/resume system. I'm not sure which is better...

Does this make sense? When I yield a coroutine and wait for something to happen, I assume I need to store the coroutine id (the number it is in the stack?) so I can resume the correct one.

Any other help would be greatly appriciated :)

(Also, as I mentioned, I'm not using SMAUG or any other codebase. I'm designing my own using Delphi...)
USA #1
Quote:
The articles by Nick Gammon indicated that when he integrated Lua with SMAUG he created one lua_state for each connected character, plus one "global" lua_state for other MUD functions.

Is this really the best way to go?

Well, this is something we've been talking about. :) It makes it a lot easier to integrate with stock SMAUG without changing too much. The main reason for that is that you don't treat characters as first-class objects in Lua. This is important because you then never actually store those character pointers: that could lead to Lua storing pointers to invalid characters (if they, say, disconnect).

I agree with Nick that if you are trying to minimally introduce Lua to SMAUG, having one state per player is a good approach: it's simple, it's fast, and it's safe.

But you do need a global state that is external to the players. You need it for things like resets. Basically you need it for any kind of scripting that needs to occur independently of a single player. If you want mud programs to trigger when one mob walks into the room with another, you need them to be at the global level.

Furthermore, having one state per player could be impractical if you have too many players (although I haven't really done the memory analysis to figure out the overhead of that). Not sure about this one.

I tend to not care as much about changing SMAUG, even drastically. So I'm perfectly happy to rip out what I don't like if it suits my purposes. (Nick's goal, again, was to minimally introduce Lua to SMAUG: my goal is a little different.)

So I have just one state, and I treat characters as first class objects. To do that, I create binding structures for Lua: basically I push userdata with metatables that hook into C-land.

To avoid the problem of storing bad pointers, which could cause a crash if accessed from Lua, I use the ID system that I think Nick posted here a while ago. The basic idea is that you store not pointers but unique numeric identifiers, and whenever you want to dereference the pointer, you go through a central manager that verifies the identifier still "points" to a valid character. If it doesn't, you can raise an error condition and deal with it however appropriate.

It's nice because in C++ you can do something like:

if (ch->getFollowing() != NULL) { /* whatever */ }

and that will return NULL both if it really was NULL, or if it pointed to a bad character (in which case it would have printed a log message or something like that).

So from Lua, it's perfect, because you can raise a Lua error that can be dealt with cleanly, instead of crashing in the C side of things.

For a little bit more detail, to represent a character, I have a Lua actor class (table+metatable) that stores its own data and so forth, and then has a field for the userdatum that is used to talk to the C functions. So when you do something like:

actor:sendText("hello, how are you")

the sendText method grabs the actor's userdatum and forwards the sendText call to that userdatum, which then goes back into the C code to deal with the network calls.

So the actor object really is a Lua class, with methods and all that, and you can call the user datum's bound methods fairly easily and it all just gets forwarded to C.


Of course, all this requires a fair bit of messing around with the code base... so whether or not this is appropriate for you depends on how much you're willing to mess with SMAUG. Since you're not even starting from SMAUG, you pretty much have free reign which gives you lots of flexibility.


(end of part 1)
USA #2
(part 2)


But the command handling is the more interesting part, in my opinion, because it lets you do something that would have been quite hard to do before.

I reworked the command handling a bit so that as soon as a character receives a line of input, instead of going straight to the C interpret function, it goes to a Lua dispatcher.

The Lua dispatcher sees what state the character is in: either it starts a fresh coroutine to handle the command, or it resumes one that previously started.

That command function can exit in several ways... one, it can just return, at which point the dispatcher notes that the character is once again in a "normal" state ready to handle a new command. But the command can also yield for more input, at which point the dispatcher notes that and marks the actor as waiting for more input. (Then the next time input comes in, it gets sent to the resumed coroutine.)

This lets you do cool stuff like:

darkstone.makeCommand {
    name = "luatest",

    code = function(actor, ...)
        actor:sendText("Say something: ")

        local response = darkstone.waitForInput()

        actor:sendText("You just said: ", response, "\n")

    end,

}


waitForInput is just a helper function to yield the coroutine with the right yield-code so that the dispatcher knows what to do. (You can also wait on a timer, for instance.)

Finally, if the dispatcher fails to find a Lua version of the command, it just sends it back to C(++) -- again this is because this is all an integration project.



One question to think about is to what extent you want to use Lua in the first place. Do you want the bulk of your code to be in C (or Delphi in your case) and then use Lua as a scripting assistant, like how mudprogs are used in SMAUG? Or do you want the bulk of your code to be in Lua, with Delphi providing basic infrastructure like networking? Since you're really starting from scratch, this decision is particularly important for you. You might decide to write *everything* in Lua, actually.

That's where Nick and I think we want to head: we are both working from SMAUG, but from now on, development will be Lua-based, new features will be added only to the Lua side of things, and over time a fair amount of the C stuff will be moved to Lua. The main reason I see to not put everything into Lua is that there is just so much stuff on the C side already and it would take quite a while to move it all. There is more interesting work to be done on the Lua side, so there isn't that much point (for now) in spending all the effort it would take to move everything. Still, were I to start over, I think almost everything would be in Lua. I'm not even sure I'd use C++ at all...
Australia Forum Administrator #3
Quote:

The articles by Nick Gammon indicated that when he integrated Lua with SMAUG he created one lua_state for each connected character, plus one "global" lua_state for other MUD functions.

Is this really the best way to go?


Not necessarily, in this particular case I was trying to add Lua scripting to an existing codebase with minimal effort and need to change.

If I was doing an entire MUD from scratch I would probably have one Lua state, for one thing, that is less overhead than many states.

Quote:

I've read many of the excellent articles (posts) on this site ...


Thanks for the compliments. I hope you have read this one in particular, because it discusses a lot of the design concepts I would want to be considering before writing any MUD from scratch, in Lua or not:

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

Quote:

What if you only use one lua_state but use coroutines for every single action and yield/resume as needed?


What I did when I did a MUD engine (only partly completed) in Lua, was start up a Lua function for handling user input in the following way:

  • If there existed, for this player, an existing (and running) coroutine, I would resume it, passing in the input as an argument.

    This handled the case of asking a question (eg. "What is your name?"). After asking the question you yield the coroutine, and thus the answer is supplied as the return from the yield.

    eg.

    
    Send (player, "What is your name?")
    name = coroutine.yield ()
    


    Thus, resuming the coroutine in this way lets you handle answers simply. However eventually you run out of questions. :)
  • If there is no running coroutine for this player, then I simply call a "command handler", starting it "from the top", passing down the line. eg.

    
    function CommandHandler (line, client)
    
      -- warning - this will disregard lines which do not start with alpha stuff
      
      local s, e, command = string.find (line, "^[ ]*(%a+)")
      
      -- blank line will result in nothing found
      if not s then
        return
      end -- no command
      
      local args = string.sub (line, e + 1)
      
      f = commands [command]  -- find command handler
      
      if f then
        f.handler (client, trim (args)) -- send command and args to handler
      else
        Send (client, config.unknown_command)
      end -- command not known
    
    end -- CommandHandler
    
    


    In fact, this is my actual code from the Lua MUD.

    I had a Lua table of known commands, so each possible command was the key of the table, and the argument was a table which had the function to handle it, and the command help.

    As you can see, the function above extracts out the first word (the command itself), looks that up, and then calls the command, passing it the remainder of the line (eg. the command might be "say" and the argument "hello").


Looking around some more, this is how I did the resuming stuff:


  if not client.thread or 
     coroutine.status (client.thread) ~= "suspended" then
    client.thread = coroutine.create (client.handler)
  end

  -- let worker thread do something with input line
  local ok, err = coroutine.resume (client.thread, line, client)


You can see from this that we always have a coroutine thread (and if we don't, we create one). Then, regardless, we resume it. The first thing you do for a newly created coroutine is start at the start (ie. the function start), otherwise you continue where you left off.

The field "client.handler" is initially your "handle logon" function (eg. "what is your name"), and then changes to the general command handler.

Quote:

A sword has been equipped and the OnExecute Lua script sets a timer to damage the player in 60 seconds unless he removes the sword. This can be done via a timer system (scheduler?) or using a yield/resume system. I'm not sure which is better...


In this case I would simply call another function if the time elapsed. Periodic stuff like this (eg. damage in a fight) is really independent of commands the player may be in the middle of typing.

Quote:

When I yield a coroutine and wait for something to happen, I assume I need to store the coroutine id (the number it is in the stack?) so I can resume the correct one.


Yes, but you need to be careful what you store. It becomes tricky if you make a Lua/Delphi combination, because you can't really store Lua internal variables (like coroutines).

However what you could do easily enough is have a table (in the Lua state), keyed by something unique (eg. player name).

Then when you go to handle a command for the player you do something like this:

  • Look up the player coroutine in the player-name table
  • If it exists, and is suspended (similar to the Lua code above), resume it
  • Otherwise create a new one, and store it in the table, then resume it

Australia Forum Administrator #4
Quote:

(part 2)


David, I have removed the post-length restriction on your forum account.
Australia Forum Administrator #5
Quote:

I'm not even sure I'd use C++ at all...


He mentioned Delphi.
Australia Forum Administrator #6
Quote:

I'm not even sure I'd use C++ at all...


However this raises a very good point. C++ / Delphi, both have the same problem. Namely, moving data structures to/from Lua space.

A while ago when I was writing a MUD server from scratch, using C++, I tried to make things flexible. The reason being, that you don't know at the early design stages all the things you might need (eg. you might decide to add Luck later on, or a list of recipes for things they can make).

So, having a hard-coded list of player (or mob) attributes was inflexible, and requires a recompile every time you change one, to say nothing of the tedium of adapting your load/save routines to handle the extra data.

I eventually came up with a sort of "table of attributes" idea (using STL in C++), however after seeing Lua I thought that using a Lua table would be even better.

Say for each player / mob / room (etc.) you simply store them as a Lua table. Then, each attribute can be a table element. And since Lua variables are untyped, you can easily add anything (eg. a number, a string, a sub-table).

A room might have a description, short description, room type, table of exits, table of players in the room, table of objects in the room, and so on. In fact tables can hold functions so you could make the room hold a function that is called when someone enters it.

With all this flexibility it seemed hardly necessary to use C++ (or Dephi) at all, and put most of the code in Lua. There are already Lua libraries that handle TCP/IP, so you simply use those to handle the actual player comms.
#7
Thanks for the replies!

I think I'm beginning to understand more and more about how this should work...

Do you even need to use a host language (eg. Delphi or C++)? I was planning on using Delphi for the core data structures ((n)pcs, rooms, items, etc) but the more I think about it everything could simply be stored/created in Lua. That pretty much reduces Delphi to simply providing socket services to Lua.

It seems like the reason to use Delphi (or C++) is that you have a strongly-typed language providing true OOP. It is much easier to serialize objects (and the entire state of the MUD) and I think the general structure of objects is much better with Delphi/C++.

On the other hand, using Lua is very nice because everything can be re-compiled on-the-fly and you don't have to worry about memory management, pointers, or any of that fun stuff.

As it stands right now, my current plan is to use Delphi for socket services but provide nearly everything else in Lua.

What kind of data structures do you use?

I was thinking about having template objects that are used to create the real objects for the game.

For example, I might have a BasicRoom object (eg. a table with methods) and then I might inherit (using a metatable and the __index method) from that to create an UnderwaterRoom. The same basic structure can be used for monsters, items, etc.

I was also thinking about some type of event system so that you can do stuff like:

-- Find the player called "Bob"
local p = Rooms.CurrentRoom.TargetPlayer("Bob");

-- Find the item called "DeadlySword" on the current player
local w = CurrentPlayer.Items.FindItem("DeadlySword");

-- Add an event to the above player to be executed when he attacks (DeadlySword.HurtPlayer is a function)
p.AddEvent(EVT_ONATTACK, DeadlySword.HurtPlayer);

I haven't really though the whole system through yet, but it seems to involve using template objects for certain things and concrete objects for others.

A potential problem might occur in the following scenario:

1) Player A's sword, "DeadlySword" adds an OnAttack event to Player B.
2) Player B starts his attack.
3) Player A logs off.
4) Player B has an event pointing to the logged-off player A's sword. (eg. invalid pointer?)

How does this sound? I guess I would need to add Serialize() methods to each template object so they can be saved to disc... (I remember seeing Lua code for this posted in one of your articles, Nick)
#8
Also, here is what my template object script would look like:

@BasicRoom.lua

io.write("Loading BasicRoom template...")

BasicRoom = {}
BasicRoom_mt = { __index = BasicRoom }

BasicRoom.Name = "BasicRoom"
BasicRoom.Title = "This is a very basic room"
-- BasicRoom.Attributes = "..." -- not thought out yet

function BasicRoom:AddPlayer(p)
table.insert(self, p)
end

function BasicRoom:IsEmpty()
if (table.getn(self) == 0) then
return true
else
return false
end
end

-- Default events
function BasicRoom:OnEnter(p)
mud.Room.Broadcast(p.Name .. " has entered the room.");
end

io.write("Finished loading BasicRoom template!");

This BasicRoom would never be used except to create new objects from. (sort-of like a class, but with default methods/properties)
Australia Forum Administrator #9
Quote:

That pretty much reduces Delphi to simply providing socket services to Lua.


There is already one:

http://luaforge.net/projects/luasocket/

I used that in my Lua MUD.

Quote:

It is much easier to serialize objects (and the entire state of the MUD) and I think the general structure of objects is much better with Delphi/C++.


I serialized an entire player very easily using Lua (the serialize function is described elsewhere on this forum):

function SaveCharacter (name)
  name = capitalize (name)

  local char = chars [name]
  local dir = config.player_dir .. string.sub (name, 1, 1)

  -- open file to serialize into
  local f, err = io.open (dir .. "/" .. name, "w")
  -- too bad if we can't
  assert (f, err)

  -- don't want to serialize client table for various reasons
  local client = char.client
  char.client = nil

  -- save all player data
  ok, err = f:write (serialize (name, char))

  -- put it back
  char.client = client

  -- done with file
  f:close ()

  -- now check for errors
  assert (ok, err)

end -- SaveCharacter


Loading it back was even shorter:


function LoadCharacter (name)
  name = capitalize (name)

  local char = chars [name]
  local dir = config.player_dir .. string.sub (name, 1, 1)

  -- load the file, get a function to execute
  local f, err = loadfile (dir .. "/" .. name)
  -- oops, if not found
  assert (f, err)
  -- want to load this character into the chars table
  setfenv (f, chars)
  -- load it
  f ()
end -- LoadCharacter


You could use the same approach for other things, although arguably you don't serialize rooms on the fly.

Quote:

For example, I might have a BasicRoom object (eg. a table with methods) and then I might inherit (using a metatable and the __index method) from that to create an UnderwaterRoom. The same basic structure can be used for monsters, items, etc.


To be honest, this was where I started getting bogged down, although I agree in principle with what you are suggesting.

The primary point of contention was to distinguish between a prototype object (that is, one the MUD builder designs), such as a sword, and an instance of the object (there might be 100 such swords in people's inventories).

Some aspects might be immutable, and could belong to the prototype (eg. its description), whereas some other aspects would change per instance (eg. the wear and tear on it). Also, you might bless or otherwise add enhancements (sharpen, perhaps) to an individual instance.

Having got that far, and you could do a fair bit with inheritance, you need to have a way of referencing the instance. For example, if I have 5 swords in my inventory, all based on the same prototype, I can't use the prototype "vnum" or whatever you call it, because they will all have that. So, I need to have a unique ID per item, so I can wield (or give away, or drop) a particular one.

This raises other issues - for example, do rooms have instances? Or is there only one of a particular room? It might be sensible to say "only one", however some MMORPGs actually have instanced dungeons, where the same room actually occurs multiple times.

Quote:

A potential problem might occur in the following scenario:

1) Player A's sword, "DeadlySword" adds an OnAttack event to Player B.
2) Player B starts his attack.
3) Player A logs off.
4) Player B has an event pointing to the logged-off player A's sword. (eg. invalid pointer?)


There are very good questions. Again, I would be cautious about closely linking the player (that is, the comms connection) with his/her avatar. If you start a fight, you shouldn't be able to escape by simply disconnecting. It would be logical for your avatar to keep existing, and keep fighting (or at least, taking damage), even if the connection is dropped.

You could argue that each avatar continues to exist in the world, even if its "controlling player" is logged off. For example, the avatar could be asleep, or in another dimension or something, but it still exists. This makes sense in a way, because if the avatar is in a group (eg. a guild) then that avatar's membership persists even if the player disconnects.

There are a whole heap of potential problems if you don't design carefully. Here is an example:


Nick creates an avatar called Gandalf, and joins a guild.
Nick gets bored with the game, logs off and deletes his character, making the name Gandalf available.
David creates an avatar called Gandalf now.


Is Gandalf still in the guild? If not, why not? Does the "delete avatar" routine have to know every possible place an avatar might be referenced (eg. guild lists)? Or, do we actually assign numeric IDs to avatars that are never re-used? If we decide to do this, then at an early stage we need to reference players by their ID and not by their name, which might change or be reused.

Quote:


function BasicRoom:AddPlayer(p)
table.insert(self, p)
end


In principle this looks good, but you need to consider, in this case, removing him from his previous room.

Again, are we really talking players here or avatars? Or how about mobs?
Amended on Thu 09 Aug 2007 11:08 PM by Nick Gammon
USA #10
Quote:
David, I have removed the post-length restriction on your forum account.

Thanks. :) I almost lost my whole post when I was told it was too long -- fortunately the browser kept it in memory so I still had it when I pressed 'back'. That was a mildly terrifying moment. :)

Quote:
I eventually came up with a sort of "table of attributes" idea (using STL in C++), however after seeing Lua I thought that using a Lua table would be even better.

I agree with Nick: not much point using STL maps if you have Lua lying around.

Quote:
Do you even need to use a host language (eg. Delphi or C++)?

Not necessarily, really. LuaSocket will provide the networking you need to get a MUD going. Lua already has file handling routines. One thing you might want that is difficult in Lua (so I understand) is threading; maybe you want a network thread that takes care of all that stuff while another thread takes care of all the game logic stuff.

Quote:
It seems like the reason to use Delphi (or C++) is that you have a strongly-typed language providing true OOP. It is much easier to serialize objects (and the entire state of the MUD) and I think the general structure of objects is much better with Delphi/C++.

Actually I disagree with that a little: Lua is extremely easy to serialize with a very small library and then just a single call. To serialize a C++ object you have to write an awful lot of handling code. Don't know about Delphi, though.

But yes, it is very nice to have a strongly-typed language from time to time. I wonder if anything will come out of the attempts to use token filters in Lua to add typing information to e.g. function prototypes. That would be kind of nifty IMO.

Quote:
and you don't have to worry about memory management, pointers, or any of that fun stuff.

Well, you don't have to worry about memory management, but you do still have to worry about resource management. Like keeping sockets open and whatnot. And if a player is following another, but then that guy logs off, you'll want the follower to notice that at some point and not get confused.

Quote:
I guess I would need to add Serialize() methods to each template object so they can be saved to disc..

I wouldn't worry too much about that; you can just leave serialize as an external function and it'd be fine.



Nick's discussion of prototypes is important:
Quote:
The primary point of contention was to distinguish between a prototype object [...] and an instance of the object

Frankly I'm not sure what the right answer is here either. I'm not sure it's best to think of it in terms of inheritance, per se, at least not code inheritance. I mean, the objects themselves do inherit in some sense from their prototypes. It's something I don't really have clear thoughts on yet. :)

Quote:
This raises other issues - for example, do rooms have instances? Or is there only one of a particular room? It might be sensible to say "only one", however some MMORPGs actually have instanced dungeons, where the same room actually occurs multiple times.

On this question I have clearer thoughts. It's important for several reasons to treat rooms as instances and not singleton objects; instanced dungeons are one example, and vehicle rooms are another. If you have lots of carriages going about the world, and getting into a carriage brings you to a "room" of sorts, then for all intents and purposes those rooms are just instances of a single prototype, and it's easier to just implement them that way.

Besides, treating rooms as instances lets you do fun stuff like put rooms in your pocket. That sounds crazy but here is a concrete example: you pack a tent with you, and then you unpack it, set it up, and presto you have a portable room...


Quote:
Again, I would be cautious about closely linking the player (that is, the comms connection) with his/her avatar.

I agree. I try to implement characters as basically independent from the connection that happens to be controlling them. For all intents and purposes, PCs and NPCs are the same thing to me except that one set has connections controlling them and another will have an AI behind them.

That makes a lot of things simpler: you don't have to care if an actor is a PC or NPC except in a few extremely specific cases having to do with providing input.





In the end of the day, if you're going to design using Lua only and not a host language integration, you will run into the same issues you'd have just using a host language. The issues are more or less the same, except perhaps that Lua makes it really easy to use coroutines and store arbitrary data into flexible tables. It's the question of integrating a host language like C++ with Lua that confuses me a bit more, especially when dealing with an entire existing system.

I hope all this was helpful, I'm just rambling a bit. :)
#11
Thanks again David and Nick!

I was planning on using "templates" for just about everything, including rooms. This would allow multiple copies of the same room and everything.

Multiple copies of the same object (eg. arrows) shouldn't be a problem because I would have 5 instances of the "arrow" object. (or perhaps I could add a quantity for this particular item, but you get the idea)

As you seperating templates from instance objects, I was thinking about a system like this:

function BasicRoom:New()
local o = {};
setmetatable(o, BasicRoom_mt);
return o;
end

Whenever I wanted to create a new basic room, I would simply do something like this:

local NewbieRoom = BasicRoom.New();
local OtherNewbieRoom = BasicRoom.New();

That would give me two unique instances of the BasicRoom object.

Of course, I would define more rooms that inherited from BasicRoom, but you get the idea.

Also, don't forget that "BasicRoom" (the template) would never be used directly. It would always be instantiated via BasicRoom.New() or something like that.

I'd also need a RoomManager object that maintained all instantiated rooms (so all "activate" rooms could be saved to disk).

I've taken a look at LuaSockets and it looks nice but it uses blocking sockets with an optional timeout value. I've only used sockets a few times, but why doesn't it support the select() function which would prevent ALL blocking and not require a timeout. Requiring even a slight timeout that would cause the entire MUD to pause would be terrible. (unless I'm missing a feature in LuaSocket?)

I'll need to think about how to gracefully remove objects but I think it could handled by keeping track of everything that has a handle to a given object or item. Then, when something (or someone) is removed you recursively notify every dependant object of it's removal...
Australia Forum Administrator #12
Quote:

I mean, the objects themselves do inherit in some sense from their prototypes.


The other problem I wrestled with a bit is that, if you instance every object, then you may have a heck of a lot of them. For example, if you have 1,000 mobs and they each have 3 items in their inventory you can loot, that is 3,000 objects straight away.

Another example might be some simple objects (for example, quest items like a rat's ear, or gold bars). Now such things probably don't need individual information kept about them - you either have one in your inventory or not, so have dozens of instances of them seems a waste of space.

Other things, like things you know (eg. recipes, skills) need to be stored, and you need to own them (eg. if you have a skill you have a "copy" of it in your player attributes), however they don't actually change.

I tentatively devised a plan where you would have two sorts of objects:

  • Prototype objects - which have immutable stuff in them, like description, type (eg. sword), and other basic attributes. These are individually designed by the MUD designers.
  • Instances of objects - these are used where an object needs to have extra stuff added (eg. wear-and-tear, blessings, etc.).


One simple scheme would be to have prototypes have a positive ID, and instances have a negative ID.

For example, if I have 20 rat's ears, then I might simply have:

20 x (Object ID = 1234)

But if I buy a sword (eg. Object ID = 2244), it needs to be instanced, because it can change. So, we convert it to an instanced object (eg. Object ID = -1234324) and then the instanced object goes into my inventory. It could still "point" to the parent object for the main attributes.

You could do a similar thing with skills, for example if I have skill level 100 with axes, I might have in my "skill inventory":

100 x (Skill ID = 4455)
Australia Forum Administrator #13
Quote:

I've taken a look at LuaSockets and it looks nice but it uses blocking sockets with an optional timeout value.


Not at all. I used select and that works perfectly.



-- initialization code:

  -- create a TCP socket and bind it to the local host
  server = assert (socket.bind ("*", config.port))
  server:settimeout (0)  -- do not block waiting for client connections


-- getting player input:

  -- use select to see if input from clients
  local readable, _, err = socket.select(t, nil, 1)

#14
Ahhhh... now I understand what you're saying. That sounds like a really good idea!

I think you could also design it so only objects that are needed are loaded. Everything else stays on disk until it is needed.

For example, a room might be saved that contains 50 monsters, each containing 10 items. However, if nobody is in the room then there is no reason to keep it loaded in-memory. (However, if the NPC need to run AI or move from room-to-room then this could break the load-on-demand idea).

What do you assign IDs to every object?

How would assign a unique number to every copy of a prototype?

If you have one single object called "BasicRoom", then if you set BasicRoom.ID it will show the same every regardless of where it's stored. (I think you know what I mean)

...or are you proposing a wrapper structure like this: (?)

Room = {}
Room.Object = BasicRoom; // Room.Object now points to BasicRoom
Room.ID = 1001; // Unique identifier

Australia Forum Administrator #15
Quote:

What do you assign IDs to every object?

How would assign a unique number to every copy of a prototype?


It was about at this point where my design bogged down - plus I was running out of energy for what seemed a very large project.

Assigning IDs to every object is certainly a possibility, another one would be to simply make them without ID, but simply exists as something that is contained by their parent. For example, a sword has to exist somewhere (in a room, in your inventory) so it could just be part of that inventory list.

However you have a problem then, if you have 5 swords in your inventory, how do you unambiguously refer to one of them?
Australia Forum Administrator #16
To help anyone who is planning to try something similar, this is the code I found for my Lua-based MUD. It is laughably incomplete, but it fires up, runs, accepts connections, lets you create new characters, and implements a couple of commands. If nothing else, it demonstrates how you can use luasocket to implement non-blocking IO.

The Lua files are in this directory:

http://www.gammon.com.au/files/muds/luamud/

They are small enough that I didn't bother to zip them.

I also had a 'players' directory, in which I placed subdirectories A, B, C ... Z, for saving player files.

You can download luasocket from:

http://luaforge.net/frs/download.php/1589/luasocket-2.0.1.tar.gz

Untar it, and then edit the "config" file and comment out the lines referring to Lua 5.0, and uncomment the lines referring to Lua 5.1, then do "make all" and "make install" (as root), then it should work for you.

To run the MUD, just type: lua main.lua

You should see:


$ lua main.lua
LuaMUD version  1
Initializing ...
Loading sockets library...
Socket library version: LuaSocket 2.0.1
Binding server port ...
MUD ready, on port      5000
Running ...


Then if you connect to port 5000 with your client, you should see something like this:


Welcome to Nick's Lua MUD!

Your name? (Enter 'new' to create a new character) ...
new
What would you like to be called? ...
Nick
You want to be called Nick, right? ...
yes
Please choose a password for Nick ...
xxxxx
Please re-enter your password to confirm it ...
xxxxx
Please choose all sort of other things ...
Welcome to LuaMUD
look
Huh? Try typing 'help'.
help
quit........... Logs off from the MUD
help........... Get help on commands
say............ Say something to people in the same room as you


Meanwhile in the server window you see:


Connection from 10.0.0.3        port    1618
Nick    has entered the game.


The file logon.lua in particular shows how you use coroutines to ask questions and yield while awaiting a reply.
Amended on Fri 10 Aug 2007 05:44 AM by Nick Gammon
Australia Forum Administrator #17
I think I had a more elaborate Lua MUD somewhere, but can't remember where I put it. :(
#18
Thanks for this! I'll take a look at it...
USA #19
About the ID assigning:

I'm not sure I would assign IDs to object instances. Not only are there several complexities regarding how to differentiate prototype from instance IDs, but also because in the vast majority of cases you don't need them to be uniquely identified beyond their "pointer value" in the code. Players don't talk to objects by their IDs but by keywords, etc. And if, for some reason, you needed to give them a unique ID, it could just be the pointer value or something like that (or the ID from Nick's ID system, if the object is implemented on the host language, or if the ID system is reimplemented in Lua).

And, if you do need to reference an object precisely, most of the time it will be in the context of a single session, like I want *that* object from the storekeeper, or I want to drop *that* object from my inventory.

Besides this is really an interface problem anyhow, because if you have several *identical* objects it doesn't matter which one you drop, and if they are different somehow you should be able to communicate that difference to the interface and not rely purely on ambiguous keywords.



Now, very occasionally, you might actually *want* to give an instance a particular ID. For example, a script in one room might want to control two instances of an object in another room, and for some reason it might need to do that separately. (For instance, it could be moving each instance on its own pattern.) This might seem contrived but hey, it happens sometimes. :)

In this case, I would consider being able to just assign IDs to the object instances. That is, a builder comes along and says "henceforth this instance shall be known as XYZ". The system could complain at you if that is already taken, and otherwise would then assign the ID to the object.



Another thing to note is that IDs don't have to be numeric; you could use strings (I think it's better, actually). That way you could explicitly state whether you want a prototype or an instance by just adding that to the ID. For instance, your sword could be weapon.longsword.17, but then the instances of that would be weapon.longsword.17:1, weapon.longsword.17:163, etc. (as in: 17, instance 1 etc)

Actually that's another thing Nick and I had talked about: whether prototypes should inherit from each other. Does the steel longsword inherit from the longsword, which in turn inherits from the sword, which in turn inherits from the weapon? Then when you code things up, do you add code to the weapon itself, or does the code exist somewhere outside the weapon? Like where does the 'wield' action sit -- in the weapon? Outside? If it lives outside, does it need to "ask" the object if it is wieldable? Does that property exist in every prototype? Or in proto-prototypes?

Sometimes I wonder if there is use to all that; I mean, I think there is a little bit to some extent; for instance all weapons really do share some behavior. But then I start thinking that you get into very, very big design questions, like whether all of your code is truly object based, or if you have these procedures lying around that take care of code. The wielding stuff is a good example of this. Does the notion of 'wielding' exist outside the notion of weapons?

Well anyhow -- that's neither here nor there...



There's another MUD out there that uses Lua; it's called MusicMUD: http://www.evilmagic.org/musicmud/
It seems to be using C++ as a host language. I haven't looked at it in ages so I don't remember to what extent it uses one over the other. But this one is designed with SMAUG in mind so it might give you other ideas about how to set this stuff up.
Australia Forum Administrator #20
Quote:

I'm not sure I would assign IDs to object instances. Not only are there several complexities regarding how to differentiate prototype from instance IDs, but also because in the vast majority of cases you don't need them to be uniquely identified beyond their "pointer value" in the code.


I would like to be able to agree with you, because IDs are fiddly. However I keep thinking of cases where, without them, you get into trouble. Without an ID, the only way of disambiguating one item from another is by its internal table reference (value), assuming we store objects (rooms, mobs etc.) as tables with their values being items inside that table.

Now, clearly table references are going to be unambiguous at a given moment, because otherwise they would refer to the same table.

Let's take for an example, there are five mobs in the room, all with the same vnum, but different (ie. 5 instances of the mob). Now the players have decided to target a particular mob, to kill it first. Better to have one dead mob and 4 left alive, than 5 slightly dead mobs. ;)

So, we need to know which mob each one is attacking, and like I say, we probably need to store its table reference. Other methods (like the "4th mob in the room") are likely to change if more mobs enter or leave.

Now say a player puts a curse on a mob, that lasts for quite a while (like, 15 minutes). So we make a timer, that says every minute or so, the mob of that address gets a downgrade to its health.

However the mob gets killed quite quickly, its corpse looted, and is removed from the world. The server then creates a new mob somewhere, and the Lua interpreter happens to re-use that table address (as it is now free). However we still have a timer for that curse, tied to that memory address, so our new mob starts getting damaged for no obvious reason.

You could probably construct a similar example for objects (eg. a sharpened sword, where the sharpening lasts for an hour, but which is subsequently destroyed).




While I was writing all that, a thought occurred to me ... maybe this wouldn't happen after all. This is because, in my example of the curse, the timer with the curse in it amounts to a reference to the table, and thus it can't be garbage collected, and thus the address can't be reused.

You still potentially have the problem of a curse affecting something that is dead, but perhaps when the mob is released from the world, as it were, its table is marked as "dead object", so that any other references to it see that it is dead, and do nothing (things like timers).
Amended on Fri 10 Aug 2007 09:39 PM by Nick Gammon
USA #21
Yes, I think that if you had the curse example, you could do it in several ways: either (1) your reference is a hard reference, and so the table wouldn't get garbage collected. Or (2) it's a weak reference, and so when the object does get collected, you lose that reference and the key is removed from the table.

Another option would be to make the curse a property of the mob, and so when the mob is destroyed all of its properties go away at the same time.



I think your first example problem is a little harder to solve (the one where players want to coordinate on which mob to attack). Using the "pointer value" as a unique ID works but it could be slightly clumsy. But at least it works... :-)
Australia Forum Administrator #22
It's not too bad - assuming the players can specify which mob somehow (custom client perhaps?).

Given that the room list contains a list of mobs, each one will be a table, presumably. Thus the table "reference" is simply the thing you put on your "attack list".

The big problem with references is that they are hard to serialize. Perhaps you simply don't serialize things like the mobs you are currently targetting.

I can still imagine other problems, say you bless your sword for an hour, so that it is stronger or something. Then after 30 minutes you log out for two hours. Now players may expect that the remaining 30 minutes of blessing should still be present, so as part of saving the player you need to save the "30 minutes of blessing remaining" as part of the sword's table.
USA #23
Quote:
It's not too bad - assuming the players can specify which mob somehow (custom client perhaps?).

Well, you could just have a special "look" mode where you see things annotated by their unique id (in this case table reference). Not perfect but doesn't require a custom client, which would be an unfortunate selling point for Lua MUDs. :-)

Quote:
The big problem with references is that they are hard to serialize. Perhaps you simply don't serialize things like the mobs you are currently targetting.

Well, serialization code already handles this, right, because it can handle things like cycles. If you were to serialize the entire game state, *exactly* as is, everything would still work. (Which I think is really cool -- it makes copyovers a lot nicer.)

And if you're not serializing the exact state but instead saving the character to disk, presumably you wouldn't be saving any references to stuff outside the player. So in particular you wouldn't be saving targeting data.

Quote:
I can still imagine other problems, say you bless your sword for an hour, so that it is stronger or something. Then after 30 minutes you log out for two hours. Now players may expect that the remaining 30 minutes of blessing should still be present, so as part of saving the player you need to save the "30 minutes of blessing remaining" as part of the sword's table.

Hmm, yes. I guess the solution to this depends on how you modeled it in the first place. If you model the timer as a property of the sword object, it'll get saved naturally. But if you modeled it as an external property, it won't get saved unless you do extra work. (I think that the first solution is more natural, though, right?)
#24
A lot of these design decisions are the same ones facing lpmud mudlib implementers. So it might be faster to just pick a mudlib and port it from lpc to lua.

(Though I think most (all?) lpmuds have access-control built in to the driver (C/C++ code) so you can hire untrusted builders.)
Australia Forum Administrator #25
This raises an interesting point, and one that probably has to be designed in from the start. If you are planning to add in untrusted areas (from untrusted builders) then presumably you want to isolate them from your mainstream data.

This is probably a good idea anyway, because it might force you to develop an "events" model that makes it easier to handle things anyway.

You can do this in Lua fairly easily by using setfenv to set an environment for some code (eg. a function). This environment can hide the "main" data (eg. a list of all players, or all rooms), and force you to get them via function calls.

For example, given a certain player p, if a MUD script wants to add 5 to his HP, one way is to simply access the data structure directly:


p.hp = p.hp + 5


However this lets you (presumably) access other things as well, and possibly corrupt the entire structure. Instead, you might give the build a function, like this:


ChangePlayerAttribute ("nick", "hp", 5)


Thus, you "expose" to the scripters (builders) the ChangePlayerAttribute function but not the underlying data. Now this function can check stuff, like does "nick" exist, is this builder allowed to modify the "hp" field, and is 5 a reasonable value to modify it by.

The advantage of such a model is that you can then automatically generate an "event" from the function call. For example, notify each avatar in the room that Nick's HP has gone up by 5. A side-effect of this might be that a mob is annoyed to see this happen, and attack the person who caused it to happen (for example, if a cleric heals someone they get aggro from a mob).

Now this really requires that you deal in IDs and not direct table structures. If the builders have the actual structure (table) it is hard to stop them doing whatever they want. But if they only have the ID, then the low-level routines convert that into a lookup to find the structure.
Australia Forum Administrator #26
I want to point out again some questions raised in this thread:

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

There are a heap of decisions to be made before you write a line of code, otherwise it might all be wasted. Some that have arisen so far (in this thread and elsewhere) are:

  • Language (C++, Delphi, Lua)
  • Do you support Unicode? If to, are you going to use UTF-8? If so, you need to allow for things like testing the length of names against Unicode code lengths, not straight byte counts.
  • Are you going to use untrusted builders? If so, you need to have some way of isolating the damage they can do (eg. sandbox environment, access control).
  • Are you going to save everything at once (like PennMUSH does), or individual things (like player files, as SMAUG does)?
  • What are your plans for instancing things (objects, rooms, mobs)? Do you refer to them by some sort of ID system, or by reference?
  • Are you going to support multiple languages (eg. French, German, Japanese, Korean)? If so, multiple ones in the same session, or just allow for the MUD to be translated? This can ramp up the complexity considerably if you do.
  • Are you going to use a custom client? If so, have you set aside time to write it?


That is about all for this list, there are lots of other questions in the other thread.
#27
Hey all, sorry for necro'ing the thread...

I just wanted to toss in a couple of reasons supporting giving object instances their own ID.

I know many people treat instances as simple clones of their prototypes, but I expect every instance to be modifiable by events in the game. Equipment gets damaged, monsters gain experience by killing players, etc. Ideally, the state of everything gets saved and restored. Not having unique ID's makes it impossible to do this UNLESS you rely on state dumps exclusively.

I'm also a database fanatic, and ideally want to store every bit of data in an RDBMS. As such, I tend to think of relationships between game objects in terms of how I'd build a schema for them.

There's nothing that says you have to have a SINGLE unique ID for every object in the game. You can have unique ID's for every prototype, and then within each prototype, you issue unique ID's for every instance.

So, perhaps you have a sword (ID: 37), and this particular sword has a big chip out of the edge (23/40 hp, -1 damage), so that sword happens to be instance 12. You go to the store and sell it, buying another one because some silly admin decided it was cheaper to buy a new one than to repair the old one. Now, you have a sword (37), but this one is instance 20.

You have have 5 swords in your inventory, and the code that shows you the inventory can stack them if it wants (by ID), but it can also distinguish them by instance ID. If I say drop 2.sword, it could just sort the list of swords by instance ID and drop the 2nd one in the list.

Since ID's are permanent, the order will always remain the same. If I pick it back up again, it will become 2.sword again.

Using 32-bit integers, you can have 2 billion kinds of objects, and 2 billion instances of each. If you really needed more than that (perhaps every coin is a real object -- shudder), you could decide if you want to recycle unused id's, or duplicate an object ID to get another 2 billion instances of it -- dealing with the inconsistencies.

USA #28
Why would you need to give every object its own unique, persistent ID? Most of the time, you either don't need an ID to begin with, and when you do, you need one "just now". In these cases, the pointer of the object should suffice. Only in relatively infrequent cases would you need to always be able to uniquely identify something.

The scheme of having prototype.instance id is pretty workable (I'd still just use a string, probably) but I still wonder at why you would need to. I guess that if it's just two integers, it is almost "free" to give every object an id, but still...

But it's not actually free in terms of memory: only having IDs when you actually need them will make it easier to cheaply index your objects by id. If you have prototype.instance, then your index needs to contain every instance (or you need two levels of indexing), even the ones you really don't care about.
Australia Forum Administrator #29
Quote:

Why would you need to give every object its own unique, persistent ID? Most of the time, you either don't need an ID to begin with, and when you do, you need one "just now". In these cases, the pointer of the object should suffice.


Well I believe Lasher has just implemented unique IDs for all objects on Aardwolf, as part of our attempts to make the inventory plugin work smoothly. We need to know, especially with cached object details, that we are still referring to the same object we had 5 minutes ago. A pointer might not cut it, as conceivably if the object was destroyed, and another allocated using the same memory - not altogether unlikely - then we could be referring to the wrong object.

To allow for growth he is using 64 bit integers, which we calculated should last well beyond our graves, even if objects are created at quite a fast rate (like 1000 per second).
USA #30
You are of course correct -- I'm not sure why I said pointers earlier, since I store everything as IDs anyhow using your ID management table...

Nonetheless I still don't see why you would often need a persistent ID, i.e. one that lasts across reboots. Even in the case of the inventory plugin you mentioned, you only need it for one session.
Australia Forum Administrator #31
The MUD might reboot, but the client still have inventory items cached in its memory. It would throw things out if they now referred to different things. This might especially happen on a hot boot, but I'm not sure.
USA #32
Hmm, yes, a hot boot could cause trouble, I think, because the client doesn't really know it happened. But for a full reboot, shouldn't the client dump its cached data when it gets disconnected?
#33
I guess it depends on how you handle reboots. Since I intend to store everything (prototypes AND instances) in SQL, there's no reason not to restore everything on reboot except player objects (and even those if it's a hot boot).

You need some kind of unique identifier to store things in SQL anyways, and integers are the easiest to index and use for junction tables.

USA #34
Yes, I suppose that if you've already got everything in an SQL table, everything is already indexed for you so you might as well take advantage of it. I'm still not convinced it's worthwhile if you need to build the indexing yourself, though.
#35
Yeah, if you're not using a database for storage, or you're just not saving instances, you probably don't need persistent ID's because just knowing that you have a "red cloth bag", and that it has 3 "sweet onions" and a "note from Farmer Ted" is enough to recreate them from prototypes.

I think ID's are useful for things which have internal state, because it provides a concise way to tie state information to usage within the game. If my sword gets damaged and I sell it to a vendor, you *could* have the vendor destroy it and only sell new swords... but you could also have him re-sell your used sword at a discount.

If, for example, that sword happened to have been a murder weapon, you'd only need to store the ID as pending case data. If your ID goes away at reboot time, you have to store the player/NPC who has it and all the stats about it too.
USA #36
Oh, just to be clear, I think there are indeed several cases where a persistent ID is useful. What I don't think is necessary is giving everything an ID if you don't have to.