I’ve written what is essentially an online help desk system for the MUD to keep track of bugs, ideas, and typos. I’m sure it’ll need to be adjusted with use, but at least now the immortals should be better able to keep track of where the problems are and what needs to be done with them.
Category Archives: Basternae and ModernMUD
Fixing Communication Routines
So I ran into a few glitches and buffer problems in the communication routines. I rewrote a few functions, simplifying them in the process, and things are a lot smoother now. At the very least they should be more stable than communications on Basternae 2.
The character creation process has a handful of glitches to work out. I’ll probably do that before I take on area loading, which will be the largest of the data format conversion projects.
The Joy of the Flags Enumeration
In the old days of MUDs it became a rather common occurrence to use each bit of a 32-bit integer as a binary flag to set or unset a value. This was far more efficient than using an array of 32 boolean values because the integer consumed 4 bytes, while an array of 32 booleans consumed anywhere from 32 to 128 bytes, depending on the system architecture.
This helped to save a great deal of storage space and memory, which was incredibly important given the machines of 1991 or so: a 33MHz 80486 processor with 16 megabytes of RAM was pretty typical.
Even with systems a lot cheaper and a lot faster now, the amount of RAM and processor time you use is still a pretty big determinant of the type of hosting plan you will need to purchase. Saving RAM is still a good idea.
Take, for instance, the “system flags”. It’s a group of flags that control certain things about the game, such as whether equipment wears out in combat and whether mobs are limited in the number of spells they can cast in battle. Stored in the new XML format as an integer, a typical value is:
<_actFlags>8256</_actFlags>
That’s not very descriptive.
We can make it a little more descriptive by using an enum tagged with the FlagsAttribute. Here’s how we implement it:
[Flags]
public enum MudFlags
{
none = 0,
turbolevel = 1,
equipmentdamage = 2,
mobcastslots = 4,
mobslootplayers = 8,
autoprice = 16,
walkableocean = 32,
nameapproval = 64
}
In our system data class we have a variable that is a MudFlags type.
As with an enumerated value, this type uses the flag names for serialization and deserialization. Now we can see what things are turned on with a quick glance at the system data file:
<_actFlags>equipmentdamage autoprice</_actFlags>
So, equipment damage and automatic pricing is turned on.
This is also handy for displaying what flags are set — we can just use the ToString() method of the MudFlags type to get more info.
Magma MUD Codebase 3.02 Released
During a trip this Thanksgiving I updated the Magma codebase a little. All I really did was fix a few compile errors that have come up in the past seven years due to changes with the C language and compilers — it’s otherwise the same code.
There’s a solution file that builds in Visual Studio 2005 (express or full version) and it also has a makefile that will compile in Ubuntu Linux 6.06.
This is the C code from the Basternae 2 rebuild era, released in 2000. This is _NOT_ the code I’ve been working on over the past few months.
Why’d I convert it? Boredom mostly. It’s posted on FindMUD in case anyone is interested.
.Net XML Serialization
Most of the saving and loading of MUD data in Basterne 2 was handled by low-level C functions that used a custom text file format for each data type. That is just a bad idea.
Save and load functions were typically dozens or hundreds of lines of code with lots of room for error and data loss. If a word was off by one space or a character was miscapitalized, a file load would fail and nothing could be done. Players of Basternae 2 probably remember pfile corruption issues with anything but fondness.
Every time we changed a file format by adding another value to an object, we had to build a translator that would convert the old file to the new version. Usually this wasn’t too hard, but it did result in a LOT of extra code lying around where it didn’t belong.
.Net makes this a lot better by giving us XML serialization.
With XML serialization, data is dumped out in a neat little XML file that can be read not only by our MUD server, but by just about anything that can read XML, which these days is just about every application in existence. Gone are the days of low-level custom file formats and painful data corruption issues (I hope).
So, how do we do it?
For example, I just rewrote the saving and loading of fraglists to use XML serialization. The code started as 174 lines of custom textfile saving and loading with lots of recursive looping to save and read the arrays containing numbers on the frag list by race and class.
Now the code looks like:
public void Load()
{
XmlSerializer serializer = new XmlSerializer( this.GetType() );
Stream stream = new FileStream( Database.SYSTEM_DIR + Database.FRAG_FILE, FileMode.Open,
FileAccess.Read, FileShare.None );
fraglist = (frag_data)serializer.Deserialize(stream);
stream.Close();
}
public void Save()
{
XmlSerializer serializer = new XmlSerializer( this.GetType() );
Stream stream = new FileStream( Database.SYSTEM_DIR + Database.FRAG_FILE, FileMode.Create,
FileAccess.Write, FileShare.None );
serializer.Serialize( stream, this );
stream.Close();
}
Cleaner and better, just the way we like it. I still hate the way WordPress formats source code.
The compile error count is now down to 1,649.
More Progress
The compile error count is now down to 2,654.
I’ve also added a codebase section to FindMUD since it seems that codebase downloads are getting harder and harder to track down these days. There are only a handful posted, but I expect that the list will grow as the site evolves.
A New Website
I’ve spent the past day putting together a site called FindMUD. It’s pretty similar in concept to The MUD Connector, but done in my own way. I’m not sure the world needs another MUD listing site, but it was fun to create. Each listing can be rated by users, so as the site develops you’ll be able to get a pretty good picture of what the best MUDs are.
Feel free to visit the site, create a login, and add listings for your favorite MUD(s). Let me know what you think of the interface and whether I left anything important out of what is asked for when submitting a new MUD.
Since the site is brand new (only 6 listings so far!), I’m sure there will be plenty of room for improvement.
A Custom Client
I don’t believe I’ve mentioned it here yet, but around 2005-2007 wrote a custom MUD client using wxWidgets and SDL. It runs fairly well on Windows and tolerably on Linux (there are one or two glitches). I designed it to work with Duris and its prompt system. It shows maps for a zone if they’re available and keeps track of hits, mana, and moves via a meter system. The graphics are pretty simple, although I had planned to improve them a bit. This program was written from scratch and could form the basis for a custom hybrid 2-d/text-based Basternae client at some point.
Here’s a screenshot (while playing Duris):
The Idea Evolution of Basternae: Technical
Over the past few days I’ve spent a while working on rewriting the Basternae source code in C# (even though the original code is not completely object-oriented yet). Ideally I’d like to have it run as a standalone application linked to a SQL server for data storage. This is doable in C++, but in C# it’s far easier.
I still have the C++ version, but after some experimentation it looks like I’m going to stick with the idea of migrating to C#. It’s been my favorite language lately, and it offers a bit more power than C++ does. What better way to fully master a language than port a 115k-line project to it? I will still have the old code to fall back on, but I hope to have the new code working in less than a month (which is the timeframe I had planned for the original string conversion anyway).
For now I’m pretty sure that I’m going to stick to C++ for the client, especially since it’s already written and just needs some slight modifications to work with the new engine and to work with Linux (ideally it will be able to run on Linux, Windows, and MacOS, but I have no access to MacOS so that’s a “maybe eventually” thing) .
As a techie I usually focus on the tech stuff first, but sometime soonish I’ll probably be posting about the gameplay changes I have in mind.
Combat Bug Fixed
It was far easier to fix than I had expected.
Here’s what I was doing:
std::list<CharData *>::iterator it;
CharData * wch;
for( it = CharList.begin(); it != CharList.end(); )
{
wch = *it;
<stuff happens to wch here, and during combat wch could potentially be killed and deleted>
}
When there was a death in combat, the iterator would get corrupted, because in the destructor for CharData an iterator would remove the CharData from the CharList, corrupting the iterator in the violence update. Due to the chain of function calls between the violence update and where the CharData was actually deleted, there was no way to update the original iterator or do any sort of “safe” removal.
After tinkering around a bit, reading some message boards, it turns out that using a second iterator saves me. One of the iterators is incremented safely, the other damaged/destroyed, and things move on happily because we’re not referencing the broken iterator.
std::list<CharData *>::iterator it;
std::list<CharData *>::iterator jt;
for( it = CharList.begin(); it != CharList.end(); )
{
jt = it++;
ch = *jt;
<dangerous deletion stuff happens here>
}
Pretty strange, but it works.
Goodbye Mobprogs
Mobprogs – a neat idea, but mostly useless.
The idea was to have a scripting language that could be used to write actions and triggers for mobs and objects that would give them a little more life. They weren’t ever used much, and I think I may have been the only one to write one. At least, I only see the ones I wrote on the old backups I have.
The code, unused as it is, has been lying around and hindering development efforts because it’s not much but more files that have to be modified every time a major change is made.
Today I’ve finally dropped them.
I’m pondering the idea of setting up Python scripting, even though I know it’ll likely not be used by anyone except maybe me. It wouldn’t be for anything other than to figure out how to do it, but it would probably be fun.
Plenty To Fix
I’ve ran some testing of this new rewrite-in-progress.
As expected, there were a few problems with some of the string functions. Those were easy enough to fix.
But, it’s now time to break out valgrind, because there are a few things that need some re-engineering and I need to see exactly how they’re broken. One thing that might take some work is a problem that happens if a character gets deleted in combat.
The violence updates iterate through a std::list. Each character in that list gets to take their swings at another character. If that target character dies, but is due to take some swings later in the list, the list gets borked because it has no way of knowing that that character was actually killed, deleted, and removed from the list (because that happens elsewhere in the program).
Since this is a combat-based game, fixing that is not optional.
I’m not sure whether it’ll take returning “hey that guy’s dead” info, or whether it’ll be a complete rewrite of combat updates, but I’m sure I’ll figure it out. I recall dealing with deaths at odd points in the code was always a hassle with Basternae 2, so rather than put a band-aid on it, I’d like to come up with a universal solution that works everywhere in the code.
Too Many Levels
I’ve played on a handful of MUDs, and just about every one of them has regular player levels from 1 to 50, especially those from the family tree of Basternae. I’ve always thought that was just too many. Although the levels from 1 to 20 have usually been pretty quick and kind of fun, especially if you’re learning to play a new class, it soon starts to become a grind. Levels 30 to 40 always have been especially grind-y and really not very much fun.
The “exp grind” is a part of experience-level-based games. That’s not going to change anytime soon.
However, I’m finally getting rid of some of those extra levels. The level cap for Basternae 3 will be 40. All of the skills, spells, abilities, etc. have been compressed, so you’ll get new skills and spells every 4 levels instead of 5.
I’m not lowering mob levels, at least not yet, so some things will be a lot more challenging and downright dangerous. Mobs may need to be adjusted, and they might be fine how they are, but the trick with getting things balanced out is to make your major changes first so that the minor adjustments can be made around them as things settle into place.
Even with the decrease in number of levels, I’ve made experience level progress a lot steeper. Expect to need slightly more experience to get to level 40 than you needed to get to 50. The goal here is not to be able to get a character to level 40 in a day, or even under a week.
It’s generally been thought that there really isn’t much to the game until you hit level 50 (or 46, depending on your outlook). That’s one of the things I’m trying to change here.
Repop Points
Setting up hometown repop points for the various races and classes in Basternae 2 was a real pain.
We had, in code, a two-dimensional array that would be referenced to get the room number for each particular race/class combination. Any special cases where someone had a choice or more than one hometown had to be hand-coded. If an area was added or removed, the code would have to be touched. This invariably resulted either in zone admins editing code (a bad idea), or in zone admins waiting for a coder to add their changes (better idea, but slower).
I’ve created a way to automatically generate hometown lookups based on areas loaded and give a new player a choice of which hometown to take when they create a character. It’s simple enough in theory: each zone can list the races that can choose it as a hometown and lists the room numbers where the various classes respawn. When the zones load, the MUD builds a list and everything is spiffy, no code editing involved.
Behind the scenes it’s a little complex. I’m using a two-dimensional vector of lists that point to integers ( std::vector< std::vector< std::list<int *> * > * > ). The standard library is awesome and makes life much easier, but it can result in excessive punctiation, as seen in the previous sentence.
I really prefer moving as much as possible out of code and into configuration files. I mean, isn’t it silly to have to recompile to, say, make a troll mercenary spawn one room to the left?
The Road To C++
I’ve gradually been changing more pieces of code from C to C++. As each piece goes from being a random function dangling somewhere to a piece of a coherent class the code begins to make a little more sense.
So many bits and pieces are going to benefit from being private data members. In a lot of cases there were hoops that had to be jumped through to handle values properly. Race, for instance. If a character was polymorphed into another race, some of the functions in the game would react to the new race and others would react as if nothing had happened, so a troll turned into a lizard might still lumber into the room.
With the ability to make some data members private, we can make it so that nobody can look at player->race, but instead have to call player->GetRace() which will be smart enough to check for polymorph. The same can be said for checking or adjusting ability scores, hitpoints, and all sorts of things that can be modified by spells.
It’s a phenomenally massive undertaking, but with the proper amounts of time and insanity it can be done right.
Hammering and Tinkering
I’ve been ripping and tearing a bit more and there have been quite a few internal changes:
- Much of the use of char * has been replaced with std::string, which is a bit more powerful and somewhat safer.
- Almost all of the shared string routines are no longer being used and the shared string manager will be removed soon. This means that the MUD will take up more memory, but will ultimately end up being more stable and less prone to “string weirdness”, segfaults, etc. The shared string manager was a fine idea in 1996 when the typical desktop system was a Pentium 1 running at somewhere between 60 and 150 MHz and would typically have about 16-32MB of RAM. In fact, most MUD codebases were designed to run well on a slowish 486 PC (i.e. 486DX-33). I remember Illustrium Arcana, the first MUD I worked on, being SUPER SUPER FAST on a Cyrix 6×86 200MHz processor with 32MB of RAM.
- Many of the “new” and “free” routines that were used in the C code have been converted to constructors and destructors. This is good because we don’t have to worry whether a creation or destruction routine is called on a mob, object, room, etc. The initialization and deinitialization how happens automagically.
- Two zones have been connected and are loading – the Thri-Kreen hometown Thannik’Tzil and the Kobold Village, both written by me.
- I’ve been exchanging emails with Thendar and she has granted permission to use all of her zones – Plateau, L’strizzen, Ice Palace, Gypsy Quest, Court of the Muse, and a few others.
It’s still just running on my home PC and is far from stable (I still have quite a lot of major changes to make too), but it is gradually coming together to look like something.
And It Begins
I’ve been working with the old Magma 3.0 codebase for almost a week now. Things I’ve done so far:
- Removed all of the old outdated OS-specific code (Sequent, Apollo, Amiga).
- Removed all instances of sprintf (a very dangerous function prone to buffer overflows). These have been replaced with snprintf, which is safer. Over 1000 functions have been replaced.
- Converted all major structs to classes (a C to C++ conversion).
- Broken the single monolithic merc.h header file into many different header files in order to make the code easier to maintain.
I’m also working on removing or replacing most of the old predecessor code and adding support for just about every area format I can find. When this is all over it will be a full codebase rewrite in C++.
The goal is to build an engine that is easily extended and modified, with OLC, easy-to-add mods and scripts, and plenty of ways to add fun things to the game without it being too unstable. Original Magma was incredibly unstable because it was built in a hurry — the codebase was based on reverse-engineering the format of existing areas for Basternae 1. There was very little testing at first and it took years for the engine to fully develop into a robust form.
It may take a few years for this new engine to develop into a fully robust form, too, but this programmer knows a vast amount more than he did eight years ago when building Basternae 2 was begun.