Register forum user name 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 ➜ MUDs ➜ MUD Design Concepts ➜ Questions about starting a new MUD codebase from scratch using Lua...

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

It is now over 60 days since the last post. This thread is closed.     Refresh page


Pages: 1 2  3  

Posted by Sofakng   (6 posts)  Bio
Date Thu 09 Aug 2007 01:31 PM (UTC)
Message
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...)
Top

Posted by David Haley   USA  (3,881 posts)  Bio
Date Reply #1 on Thu 09 Aug 2007 09:26 PM (UTC)
Message
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)

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

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

Posted by David Haley   USA  (3,881 posts)  Bio
Date Reply #2 on Thu 09 Aug 2007 09:26 PM (UTC)
Message
(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...

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

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

Posted by Nick Gammon   Australia  (23,101 posts)  Bio   Forum Administrator
Date Reply #3 on Thu 09 Aug 2007 09:30 PM (UTC)
Message
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


- Nick Gammon

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

Posted by Nick Gammon   Australia  (23,101 posts)  Bio   Forum Administrator
Date Reply #4 on Thu 09 Aug 2007 09:32 PM (UTC)
Message
Quote:

(part 2)


David, I have removed the post-length restriction on your forum account.

- Nick Gammon

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

Posted by Nick Gammon   Australia  (23,101 posts)  Bio   Forum Administrator
Date Reply #5 on Thu 09 Aug 2007 09:33 PM (UTC)
Message
Quote:

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


He mentioned Delphi.

- Nick Gammon

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

Posted by Nick Gammon   Australia  (23,101 posts)  Bio   Forum Administrator
Date Reply #6 on Thu 09 Aug 2007 09:55 PM (UTC)
Message
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.

- Nick Gammon

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

Posted by Sofakng   (6 posts)  Bio
Date Reply #7 on Thu 09 Aug 2007 10:19 PM (UTC)
Message
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)
Top

Posted by Sofakng   (6 posts)  Bio
Date Reply #8 on Thu 09 Aug 2007 10:23 PM (UTC)
Message
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)
Top

Posted by Nick Gammon   Australia  (23,101 posts)  Bio   Forum Administrator
Date Reply #9 on Thu 09 Aug 2007 11:06 PM (UTC)

Amended on Thu 09 Aug 2007 11:08 PM (UTC) by Nick Gammon

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

- Nick Gammon

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

Posted by David Haley   USA  (3,881 posts)  Bio
Date Reply #10 on Fri 10 Aug 2007 12:39 AM (UTC)
Message
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. :)

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

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

Posted by Sofakng   (6 posts)  Bio
Date Reply #11 on Fri 10 Aug 2007 01:28 AM (UTC)
Message
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...
Top

Posted by Nick Gammon   Australia  (23,101 posts)  Bio   Forum Administrator
Date Reply #12 on Fri 10 Aug 2007 02:17 AM (UTC)
Message
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)

- Nick Gammon

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

Posted by Nick Gammon   Australia  (23,101 posts)  Bio   Forum Administrator
Date Reply #13 on Fri 10 Aug 2007 02:28 AM (UTC)
Message
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)


- Nick Gammon

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

Posted by Sofakng   (6 posts)  Bio
Date Reply #14 on Fri 10 Aug 2007 02:36 AM (UTC)
Message
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

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.


97,489 views.

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

It is now over 60 days since the last post. This thread is closed.     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.