Game Programming and Development Tools

MUD programming – HanClinto

HanClinto

Administrator

Posts: 1828
From: Indiana
Registered: 10-11-2004
Hey all!

There's been talk lately that there hasn't been enough "hardcore" programming discussion on here.

There's also been a number of people requesting help with MUD programming.

So here's a thread to talk about both of those topics.

I recently decided to help RifleFire work on a Christian MUD he's been wanting to do for a long time, and so I've been working on a MUD engine for him. There are a lot of good MUD codebases out there already on SourceForge, and I'm learning from them, but I decided to try making one from scratch myself just for the learning experience.

I decided to write it in C#, and hopefully get it to run on a Linux server using Mono or DotGNU.

I've never done a MUD before, and so I'm having a lot of fun trying new things, because I don't know what I'm not supposed to try.

So basically I'm building a really generic framework. We're talking so generic, that the engine doesn't even have verbs built into it -- you have to define the verbs in the database and assign scripts to them.

For instance, you want to have the command "look" in your game's vocabulary. Great! So you just attach a script to the verb "look", and a simple version of the script would look something like:

Player.Message( Target["DESCRIPTION"] );

So this basically says, message the player with the description of the target.

If you wanted to make the "look" script aware of a player being blind, you could have a slightly more complex version of the "look" script:

if (Player["IS_BLIND"] != true)
Player.Message( Target["DESCRIPTION"] );
else
Player.Message( "You can't see anything, you are blinded!" );

All of these scripts are located in the database, and are compiled in dynamically at runtime using Reflection. This means that if you wanted add that blindness feature, you would do it while the game was up and running, and you could compile the script in, and it would take immediate effect! You wouldn't have to recompile the server, you wouldn't even have to boot the players off and interrupt their game playing! This makes bug-fixes less annoying for players, because you don't have to take the server down for every little tweak and change.

This all sounds well and good, but the trouble is implementing all of this. It's simple when I lay it out here, but it's becoming more complicated as I've started defining the database and whatnot. I'm even doing some cool stuff as far as having dynamic attributes and whatnot, so that you can define new fields for objects really easily.

I'll probably post more about this, but this is enough for now.

As far as coding all of this, so far I've got the database structure for logins and objects (working on scripts next), I've got my program talking to the database, and I've got my program able to compile scripts into assemblies using Reflection. Nothing too complicated, but just fitting it all together and trying to make it simple and elegant.

Hope some of you found that interesting! If there are many other people interested in learning how to do stuff like this, I'm willing to make the code open and work on it with other people!

In Christ,
clint

[This message has been edited by HanClinto (edited May 16, 2005).]

Jari

Member

Posts: 1471
From: Helsinki, Finland
Registered: 03-11-2005
Yeah that sounds fun and interesting. But I have couple questions though, mind if I ask them? Ok.

1)
If the Player object's Message function takes only one string argument, how can the client know how to deal with the string? Maybe you we're thinking something like if server sends a message it also sends the messages type as well?
Player.Message( Target["DESCRIPTION"] );
where Target["DESCRIPTION"] would actually prododuce data packet that client recognizes as description packet?

2)
How is it possible that you don't have boot the players if the server is updated? If If you add a new message handler in server shouldn't the clients also be updated? Does the server send the new script files to clients and they execute them so that they can respond to the new server message?
And what about updating existing features/code?

Thats all.

------------------
Psalms 127:1 Unless the LORD builds the house, they labor in vain who build it; unless the LORD keeps the city, the watchman stays awake in vain.

kiwee

Member

Posts: 578
From: oxfordshire, england
Registered: 04-17-2004
since i can't code, is there a MUD maker on the faithful interweb? or am i just putting my hopes up too high, also, can you code MUD's for websites?

------------------
I Am God's Kid!!

HanClinto

Administrator

Posts: 1828
From: Indiana
Registered: 10-11-2004
Hey Jari! Thanks for the thoughtful reply!

quote:
Originally posted by Jari:
Yeah that sounds fun and interesting. But I have couple questions though, mind if I ask them? Ok.


quote:
Originally posted by Jari:
1)
If the Player object's Message function takes only one string argument, how can the client know how to deal with the string? Maybe you we're thinking something like if server sends a message it also sends the messages type as well?
Player.Message( Target["DESCRIPTION"] );
where Target["DESCRIPTION"] would actually prododuce data packet that client recognizes as description packet?


Ah, nope. I forgot that MUDs can display text in color. I'm thinking this MUD would just use a simple Telnet client, and having color totally slipped my mind. I was just assuming it would send simple black-and-white text to the user. So the transaction on the user's screen would look something like:
quote:
> look flask
The rising bubbles have a mesmerizing effect on your senses, and almost call to you "drink me… drink meee…"

That's all I was picturing. You're right though, when I add color support, I'll add another parameter for the message type.

quote:
Originally posted by Jari:
2)
How is it possible that you don't have boot the players if the server is updated? If If you add a new message handler in server shouldn't the clients also be updated? Does the server send the new script files to clients and they execute them so that they can respond to the new server message?
And what about updating existing features/code?

All of the scripts are compiled at runtime. For example, here's a sample piece of code that, in C#, compiles a string of code which is more C#, and then you can call that compiled piece of code from within the code that compiled it. Boy that was a mouthful. Anyway, here's a function from my code:


public static Assembly CompileCode(string code)
{
Microsoft.CSharp.CSharpCodeProvider provider =
new Microsoft.CSharp.CSharpCodeProvider();
System.CodeDom.Compiler.ICodeCompiler compiler =
provider.CreateCompiler();
System.CodeDom.Compiler.CompilerParameters param =
new System.CodeDom.Compiler.CompilerParameters();
System.CodeDom.Compiler.CompilerResults results;

param.GenerateInMemory = true;
param.GenerateExecutable = false;

results = compiler.CompileAssemblyFromSource(param, code);

if (results.Errors.HasErrors)
{
string errText = "";
foreach (System.CodeDom.Compiler.CompilerError err
in results.Errors)
{
errText += ("Line [" + err.Line.ToString() + "] :"
+ err.ErrorText + "\r\n");
}
System.Windows.Forms.MessageBox.Show(errText,
results.Errors.Count.ToString() + " errors");
return null;
}

return (results.CompiledAssembly);
}

So then, you can use that function like so:

string code = MyDB.GetScript("LOOK_SCRIPT");
Assembly myAsm = MyScripting.CompileCode(code);

And create a .NET assembly from the string of code stored in the database under the tag "LOOK_SCRIPT". If there are errors, it will notify you, and fail to create the new assembly, and continue to use the old one until you resolve the errors in the new one.

Because this all happens at runtime, you wouldn't even have to kick players off, because you're just hot-swapping these assemblies in at runtime. Before you update the LOOK_SCRIPT, it uses the old one, and then when you change it, it updates it, and from there on out, it just uses the new one. It's all compiled on the fly, and linked in.

It's really cool, and it's one of the things that I've been learning how to do since I've been sick at home for most of the past week. I've been learning that, and database access.

Speaking of database access, here's a SQL select that I wrote yesterday to select the inventory of a player. It's pretty complicated, and I'm really happy that it works.

SELECT Attributes.ObjectID, Attributes.Name, Attributes.Value
FROM Attributes
WHERE (((Attributes.ObjectID) In (SELECT Attributes.ObjectID
FROM Attributes
WHERE (((Attributes.Value)=[PlayerID])
AND (Attributes.Name = "CONTAINER"))))
AND ((Attributes.Name)="LONGNAME"));

And Kiwi, I'm not sure about there being a MUD maker on the faithful interweb. I plan on letting other people use this MUD engine when I'm done with it. And the cool thing about this engine is that for people to use it, they have to be able to code, but only a *little bit*. I'll have taken care of all of the diffucult network and database coding, all that the MUD creators would have to make is the gameplay code (which is the fun stuff). Like deciding how alignment and combat and other such things work.

Code MUDs for websites? You mean like a MUD that you can access through a web browser? I don't know how to do that yet, no. I imagine this system could be adapted for such a purpose -- all you would probably need is just a javascript telnet client that could connect to the normal MUD server via telnet.

In Christ,
clint

[This message has been edited by HanClinto (edited May 16, 2005).]

Jari

Member

Posts: 1471
From: Helsinki, Finland
Registered: 03-11-2005
Ok, I'm glad to see you have it figured out.

I wish you well with the project.

------------------
Unless the LORD builds the house, they labor in vain who build it; unless the LORD keeps the city, the watchman stays awake in vain. - Psalms 127:1


And the work of righteousness shall be peace; and the effect of righteousness quietness and assurance for ever. - Isa 32:17

HanClinto

Administrator

Posts: 1828
From: Indiana
Registered: 10-11-2004
heh, hope I didn't put you to sleep with that last post. I re-read it, and it's all extremely confusing. I understand what I'm talking about, but I'm not sure that anyone else who doesn't have an solid grasp of .NET and reflection would be able to understand what I'm talking about.

Basically C# lets you compile code on the fly, and then lets you access that code. Built-in compiled scripts. I'm storing all of my game logic in dynamic scripts, so that it can be changed whenever I want.

quote:
Originally posted by Jari:
I wish you well with the project.

Thanks! I pray it's useful to Christian mudders. It should really allow people to create MUDs that don't follow the "norm". Entirely new systems and concepts can be built directly into the game logic, all while maintaining the original core engine.

I'll keep everyone posted as far as my progress goes. Today and yesterday I've been working on object inheritance, so that you can have a generic "bottle" object with a bunch of descriptions and attributes associated with it (such as the ability to fill the bottle), and then you can have different types of bottles that inherit everything from the original, plus whatever you want to change/add.

I'm worried that this system will be overly complex, and not really useful to anyone except myself. I guess it means I'll just have to write really good tutorials, neh?

--clint

------------------
http://www.includingjudas.com/christiangame.html

Jari

Member

Posts: 1471
From: Helsinki, Finland
Registered: 03-11-2005
quote:
Originally posted by HanClinto:

I'll keep everyone posted as far as my progress goes. Today and yesterday I've been working on object inheritance, so that you can have a generic "bottle" object with a bunch of descriptions and attributes associated with it (such as the ability to fill the bottle), and then you can have different types of bottles that inherit everything from the original, plus whatever you want to change/add.

I'm worried that this system will be overly complex, and not really useful to anyone except myself. I guess it means I'll just have to write really good tutorials, neh?


Nah, I think it's going to be fine if you keep the object hierarchy and methods for scripting simple. Did you plan that the objects derived from the bottle's base class would all be in script files?


------------------
Unless the LORD builds the house, they labor in vain who build it; unless the LORD keeps the city, the watchman stays awake in vain. - Psalms 127:1


And the work of righteousness shall be peace; and the effect of righteousness quietness and assurance for ever. - Isa 32:17

HeardTheWord

Member

Posts: 224
From: Des Moines, IA
Registered: 08-16-2004
Sounds like a pretty flexible engine!

I'm just curious if you have some of the server code done. I started writing a managed dll in C++ that can be used for networking purposes. Right now it is extremely basic because the person I wrote it for just wanted a peer to peer chat program. So right now it is a bit feature deprived.

In case you are wondering... I am using WinSock's asynchronous sockets. I have also created some event functions that are triggered when data is received, a client connects, the server accepts, and other basic socket messages. I plan to add multiple clients that are easy to keep track of and send messages to. Also broadcasting is a feature I would like to add as well.

Let me know if this could be of use to you and I will continue to add features to it.

HanClinto

Administrator

Posts: 1828
From: Indiana
Registered: 10-11-2004
Jari: Thanks for the encouragement!

The objects derived from the base bottle class are defined in the database. In the database are stored the object's attributes and its actions (scripts). So not *just* in scripts, but also in the database.

I'm rethinking how all of the object inheritance works, and man, it's a pain for it to be all nested. I'm thinking I might not deal with it for now, and just let everything be copied different places or something -- it's crazy to try and do multiple levels of inheritance in SQL.


HeardTheWord:
Thanks for the compliment!

No, I haven't taken time to do any of the networking yet, so it would be extremely interesting to me to have someone help me out with it. I need to make this support Telnet, and ideally it would support a few different MUD client standards, as well as just straight Telnet color to "spice things up".

I know that's a little beyond the scope that you were talking about, but I'm interested!

You said you're doing this in managed C++? I'm doing all of the other stuff in C#, but if we can compile it all into a .NET assembly, I suppose it should all work together.

Winsock eh? Hrm. I'm trying to use System.Net.Sockets, so that it can be Mono compatible and be able to be run on Linux. However, they're very similar in structure.

Anyway, that's where I stand. If you're interested in getting involved, that would be fantastic, but if this is outside of your scope, I understand. Ideally, I'm hoping to get other developers in on this project who are looking to get a project done and under their belt -- I think the project could be usable in a matter of months (maybe less), and that's one thing I'm looking forward to -- just having a completed and running Christian game on my resume to help drive me forward into bigger projects.

BTW, the URL for RifleFire's discussion group about the MUD is at http://games.groups.yahoo.com/group/thewaymud/ .

In Christ,
clint

------------------
http://www.includingjudas.com/christiangame.html

HeardTheWord

Member

Posts: 224
From: Des Moines, IA
Registered: 08-16-2004
I am interested in helping you out. I know the dll will work in C# but only in Windows. If it needs to work in Linux it may take some time to convert. I will keep you posted on what I get accomplished.

As for telnet color, I believe all it takes is the correct Ascii code. I will see if that can be easily implemented as well as some of the other telnet features.

HanClinto

Administrator

Posts: 1828
From: Indiana
Registered: 10-11-2004
Hey HeardTheWord!

Okay, great. I'd be grateful for whatever help I could get. If I had my choice, I would want the whole engine to be coded in C# just for consistency's sake, but either way is great!

Glad to see you joined the forums over on Yahoo -- we'll keep talking about this.

In Christ,
clint

------------------
http://www.includingjudas.com/christiangame.html

HanClinto

Administrator

Posts: 1828
From: Indiana
Registered: 10-11-2004
Sample of progress:

Okay! Well it's now loading scripts from the database, compiling them on the fly, and having the scripts interact with the database and the player.

It's late, and I need to go to bed, but I wanted to post this so that people could dink around with it.

Sorry, but the database is still in Microsoft Access. Unless you've got Access, you won't be able to edit this run of the database. Later, I'll put it into a better format like MySQL, but this was the easiest for me to do at the moment.

So to give a rundown of the scripts, and the way they work, here's a current "test" script that's in the database:

Player.Message("This is a test script! Hello world!");

Nothing special. Basically it just says, "Tell the player, 'This is a test script! Hello world!'", nothing too fancy.

A more complex script is the "inventory" script. That looks like this currently:

Player.Message("You are carrying:"); 
foreach (MyGameObj invenItem in Player.Contents())
{
Player.Message(" " + invenItem["LONGNAME"]);
}

So basically all that's saying is, "for each item in the player's contents, display the LONGNAME of that item".

I realize it's a little confusing, but I really think people can learn it after a little while. Regardless, most of the hard scripting like this will be done by me, and all people will have to do to create MUDs is add in objects and rooms and the room descriptions.

Once you've wrapped your teeth around the inventory script and want to bite into something bigger, here's the "look" script:

Player.Message("You see: " + Player.GetTarget(TargetStr)["DESCRIPTION"]);
if (Player.GetTarget(TargetStr).Contents().Length > 0)
{
Player.Message("In it, you see:");
foreach (MyGameObj item in Player.GetTarget(TargetStr).Contents())
{
Player.Message(" " + item["LONGNAME"]);
}
}

Part of that code should look familiar, as it's basically the inventory script, except instead of looking at the player's contents, it looks at the contents of the target (what you're pointing at).

It's pretty dumb if you type in a target that doesn't exist, it doesn't give you an error message or anything. So these scripts aren't final, they're just a quick example I threw together of what you can do with this engine scheme.

So if you want to check it out, you can download the engine and the small sample database here!
http://www.includingjudas.com/clint/mymud.zip

Please check it out, and try playing with the database if you have Microsoft Access.

In Christ,
clint

------------------
http://www.includingjudas.com/christiangame.html

CoolJ

Member

Posts: 354
From: ny
Registered: 07-11-2004
I didn't know you could do stuff like that with C#! Store code in a database then compile it on the fly. That's really cool!

How do you use the script code once you get it compiled? I noticed you get a reference to it, but how is it executed when someone types the command in? Are you storing it's reference in an array with all the other compiled scripts?

HanClinto

Administrator

Posts: 1828
From: Indiana
Registered: 10-11-2004
There is a table called "Verbs". It has entries that look like this:

VerbID ScriptID VerbWord
1 1 get
2 1 pickup
3 1 obtain
4 1 yoink
5 2 look
6 2 gaze
7 2 peer
8 3 inventory
9 3 inven
10 3 carrying
11 4 test

So let's say you wanted to add a new command called "ls" as a joke to the Linux ppl who play the game. So you add a new verb to the Verbs table:


VerbID ScriptID VerbWord
12 5 ls

Then now you've just linked the verb "ls" to script number 5 in the Scripts table. Over in the Scripts table, you can define a script with ID 5, and with the name of whatever you want. For simplicity sake, you can also name it "ls". The entry would look something like:

ScriptID: 5
Name: ls
ParamList: MyPlayer Player, string TargetStr
Script: Player.Message("Silly, this isn't Linux!");
ReturnType: void

And if you wanted to make "dir" do the same thing as "ls", you could then just add another verb with the name "dir" and have it also point to the "ls" script.

I hope that makes sense!

I think I'll be switching this all over to MySQL so that more people can edit the database and play with creating scripts. This was just the first proof of concept demo, and I'm really pleased that it works as well as it does.

In Christ,
clint

------------------
http://www.includingjudas.com/christiangame.html

HanClinto

Administrator

Posts: 1828
From: Indiana
Registered: 10-11-2004
I just realized that you might have been referring to how I actually store the script inside the engine, and call it. That's done like so:
public object Execute(MyGameObj gameObj, string targetText)
{
object[] scriptParam = new object[2];
scriptParam[0] = gameObj;
scriptParam[1] = targetText;

System.Reflection.MethodInfo meth = ScriptMethod;

object retObject = null;

if (meth != null)
retObject = ScriptMethod.Invoke(asmInstance, scriptParam);

return retObject;
}

And ScriptMethod is defined right here:

private System.Reflection.MethodInfo ScriptMethod
{
get
{
if (!IsBuilt())
Build();

if (asm != null)
{
asmInstance = asm.CreateInstance("MyMUD.CustomScripts.script_" + Name);

if (asmInstance != null)
{
Type scriptType = asmInstance.GetType();
System.Reflection.MethodInfo scriptMethod = scriptType.GetMethod(Name);

return scriptMethod;
}
else
{
Console.WriteLine("Error in retrieving the assembly instance for the type: '" + "MyMUD.CustomScripts.script_" + Name + "'");
Type[] myTypes = asm.GetTypes();

foreach (Type t in myTypes)
Console.WriteLine(t.ToString());

return null;
}
}
else
{
Console.WriteLine("Error in retrieving the script method.");
return null;
}
}
}

I realize it's not commented and is probably pretty confusing, but that's the heart and soul of being able to execute the assembly. I can post code on how to get the compiled assembly if anyone's interested, but there's how, once you have a compiled assembly (in my code the instance is named "asm"), then you can get the method and execute it like so.

I'm actually storing all of the compiled assemblies in a hash table, with the key of the hashtable being the name of the script. When someone wants to execute a script, my main script manager looks to see if that script is loaded, if it isn't, it goes out to the database, grabs it, compiles it, and then uses that and executes it. If the script has already been loaded, it just uses the existing instance that was already saved in the hashtable and goes from there. I *think* that's probably what you were asking.

My next step is to have it check to see if the script changed in the database, so that if you change the script, it will reload it if possible. The other option is to create a "build" verb, so that you can rebuild scripts from within and get your error messages right there in the console. If the new version doesn't compile, the new assembly shouldn't replace the old one that does work. Ideally, it will continue to use the old assembly until the new one is built, at which point it will use the new one and forget the old one.

In Christ,
clint

[This message has been edited by HanClinto (edited May 20, 2005).]

CoolJ

Member

Posts: 354
From: ny
Registered: 07-11-2004
That's brilliant! Thanks for explaining it. I'll have to look at the C# api to understand it completely.

Just a thought, but I guess it could be possible to allow admins to add elements to the game from the MUD command prompt. Something like:
'add command'
'create monster'
'add monster to room'
'add direction'
'add building'
'add object'
'add npc'

That would be cool, an admin could be walking around the world and go, hmmm..this room needs something..I think I'll add a tree!

HanClinto

Administrator

Posts: 1828
From: Indiana
Registered: 10-11-2004
quote:
Originally posted by coolj:
That would be cool, an admin could be walking around the world and go, hmmm..this room needs something..I think I'll add a tree!

Yup! That's totally the idea! Walking around and editing is one thing, but something else I'd like to try is making a web interface for *creating* the worlds. It's kindof daunting to have this huge image of a world, and have to create it one little room at a time. I'm thinking of perhaps being able to use a web interface with PHP and such (or maybe just a seperate program just for editing the world). But yeah, admins will have access to scripts to be able to do just about anything in the world, including scripts (so they can change the way things like combat work from within the game).

I'm just chilling out at a coffeeshop tonight, and I'll be working on it. Looking forward to getting more stuff done on it!

In Christ,
clint

------------------
http://www.includingjudas.com/christiangame.html

oneinch
Junior Member

Posts: 1
From:
Registered: 07-31-2007
There is a Christian MUD out there called Ark of the Covenant. Found at http://www.aotcmud.org

the server

aotcmud.org:4200

HanClinto

Administrator

Posts: 1828
From: Indiana
Registered: 10-11-2004
Hey oneinch -- welcome to CCN!

I've played a bit of AoTC -- it seems like a neat MUD. It's got a lot of leftovers from its ROM codebase, but on the whole it seems like a neat community of players.

Welcome to our forums! Do you play/develop many MUDs?

In Christ,
clint

samw3

Member

Posts: 542
From: Toccoa, GA, USA
Registered: 08-15-2006
Clint,
This is a very interesting approach. I amazed to see that .NET includes a compiler as a part of the reflection system.

If you are looking for telnet terminal control sequences (color, cursor positioning, etc.) Here is a quick synopsis.

Have you thought about how the server will scale? It seems like all these dynamic hits and compiles will flat-line your server pretty quick.

What actually does results.CompiledAssembly return? An object, anonymous function? If so, maybe create a action cache that will only recompile if the script has changed. Maybe write an associated md5 when posting the script to the db.

Anyways, just some ideas for you.

Keep up the good work.

God Bless!

------------------
Sam Washburn

HanClinto

Administrator

Posts: 1828
From: Indiana
Registered: 10-11-2004
Hey, Sam!

quote:
Originally posted by samw3:
This is a very interesting approach. I amazed to see that .NET includes a compiler as a part of the reflection system.


Yeah, isn't it cool?
I found out about this from a project we were working on at work, where we do a similar thing to provide dynamic code generation for user-customizable forms.


quote:
Originally posted by samw3:
Have you thought about how the server will scale? It seems like all these dynamic hits and compiles will flat-line your server pretty quick.


Perhaps this will be answered by my response to the next paragraph.

quote:
Originally posted by samw3:
What actually does results.CompiledAssembly return? An object, anonymous function? If so, maybe create a action cache that will only recompile if the script has changed. Maybe write an associated md5 when posting the script to the db.


This was all originally done in C# 1.1, before anonymous methods came about. results.CompiledAssembly returns a pointer to an assembly. DLLs and EXEs are kinds of assemblies -- basically what I'm doing is dynamically compiling code into a DLL stored in memory, which I then link into. I cache those assemblies in memory, and so long as the program is running, I only recompile those assemblies if the scripts for them change. If the script breaks and doesn't compile, I still have the compiled binary in memory that I can load up until you work out the compile errors in the script. So the performance hits on the scripts are negligible -- aside from the reflection needed to get the method, it's just as fast as calling normal compiled C# code.

Thanks for the feedback!

BTW, the code has been heavily modified since the original 2005 post -- RifleFire and I put it up on SourceForge, so if you're curious, you're welcome to peruse all of the code in the SVN tree -- for instance, here's miteScript.cs -- one of the files that handles some of the nitty gritty stuff regarding run-time code comilation/linking.

In Christ,
clint

samw3

Member

Posts: 542
From: Toccoa, GA, USA
Registered: 08-15-2006
I didn't even realize this thread was resurrected. I thought this was something you were active on. I'm not active in C# as of late, but I'll tuck that info away for a rainy day.

Strangely enough, I think your first post's sentence about not enough "hardcore" programming discussion still stands as of late. Still, reading through this stale thread was kind of refreshing.

hmm.

God Bless!

------------------
Sam Washburn