Quote:
We want the core functionality of the game to be in C++, mainly socket handling. Almost all other game logic would be handled in Ruby scripts.
I was planning something similar, although I think "core functionality" depends on your definition. If "almost all" logic is in Ruby, I would say your core functionality is in Ruby.
I have been experimenting with a Lua MUD (I know this isn't Ruby, but the idea is similar). I obtained a DLL from the Lua downloads site that provides all the TCP/IP functionality I need. In fact, to load that, all I had to do is this:
print "Loading sockets library..."
-- load luasocket namespace
socket = require("socket")
print ("Socket library version:", socket._VERSION)
From then on, I was able to poll for incoming connections, see if there was text arriving, or send outgoing text, all in Lua, using the socket library.
After that, for reasons I described earlier, all the data storage was in Lua tables, so the need to convert to/from C++ didn't arise.
I also used the coroutines functionality in Lua to make processing new players much easier. Rather than have the rather convoluted "nanny" routine that SMAUG uses, with all its states, and having to remember what the player typed last time through, it can be done by making the whole connection process into a coroutine, and then yielding when more input from the player is required. See below for how I did that:
function NewConnection (line, client)
local name
Send (client, messages.welcome)
-- get name
while true do
Send (client, "Your name? (Enter 'new' to create a new character) ...", true)
name = coroutine.yield ()
local ok, err = validname (name)
if ok then
name = capitalize (name)
if name == "New" then
break
end -- special name
if yes_no (client, "You are called " .. name .. ", right? ...") then
break
end -- they agreed to that name
else
Send (client, err)
end -- good name entered
end -- while getting name
local char -- the character s/he is
if name == "New" then
char = NewCharacter (client)
else
char = ExistingCharacter (client, name)
end
if not char then
Send (client, "Press <enter> to restart the connection process ...")
return
end
print (char.name, "has entered the game.")
Send (client, "Welcome to " .. config.mudname)
char.connected_time = os.time ()
char.client = client -- point our character to the controlling client
client.char = char -- tell the client which character it is
char.playing = true -- they are on now
client.handler = CommandHandler
end -- NewConnection
Notice how I simply ask their name and yield until I get a response? The low-level TCP socket handler simply breaks input into lines, and when a line has arrived resumes the current coroutine with that line. This could be the "new player" handler, or simply the command processor.
We can do a similar thing to get a yes/no response from the player (as called from above):
-- get a yes/no reponse from the client
function yes_no (client, query)
local reply
while true do
Send (client, query, true)
reply = trim (coroutine.yield ())
if reply == "yes" or reply == "y" then
return true
elseif reply == "no" or reply == "n" then
return false
end -- if good reply
Send (client, "Please reply 'yes' or 'no'.")
end -- while loop
end -- yes_no
This sends the query, and then yields until it gets a reply. Then if the reply is not yes/y/no/n then it resends the query and loops. Nice and easy to see how it is working.
I'm not sure if Ruby has coroutines, and if not maybe consider Lua instead. A quick search on the web seems to indicate that Ruby does have coroutines, and probably an interface for TCP/IP as well, so that is probably all you really need.
Quote:
I am unsure exactly how to implement things ...
Welcome to the club! I am also thinking deeply about the best way of implementing (as is Ksilyan). A couple of things you might want to think about are:
- How are you going to store data? One big file? Lots of small files?
Here is one example of a problem area. I was initially planning to keep each player's data in its own file (like SMAUG does), but what happens if you want to (say) send mail to another player? Without his file loaded, you don't even know if he exists, or maybe does not accept mail from you, or is in the wrong faction, or something.
- How are you planning to handle the difference between prototype objects (eg. the design for a short sword, or a level 5 naga), and the instances of them (eg. 100 players have the sword, there are 10 naga in a room)?
- What, if any, event model do you want? For example, if I hit a naga with my sword. Who notifies other players that "Nick hits naga with sword"? The enactor (Nick)? The room? The naga? The player who is in the room with Nick? Maybe the act of hitting something raises an event, that other things in the room detect.
Good luck with the project, I hope you have lots of time on your hands. ;) |