Yearly Archives: 2007

Global Domination

Or more like, “getting owned by globals”.

As part of the conversion to C++ one of the major tasks is moving all of the global functions into classes. There are a lot of them – each spell, command, skill, bard song, et cetera has been handled by a global in the past. Even with all that’s been moved, I still count 1,467 global functions that need to be moved into classes. This is down from about 2,000 when I started.

Some of this is easy. It’s obvious that a get_object_weight function belongs to the object classs and an initialize_mob function belongs to the mobile class. Other things might not be so easy, like functions that belong to both characters and objects like give_object_to_char. It could just as easily belong to either one. It’s not a big deal if I just make an arbitrary decision and stick it somewhere, but it is something that I have to think about.

String Conversion Update

I’m still working on converting char * strings to std::string strings. Here’s the recent progress:

Reference

5-27-07

5-29-07

6-1-07

6-4-07

6-10-07

6-15-07

strncat

772

723

641

605

606

581

snprintf

1199

1166

1096

1079

1064

1032

const char *

343

287

317

330

341

330

MAX_STRING_LENGTH

2404

2313

2062

2011

2000

1955

MAX_INPUT_LENGTH

471

446

153

153

142

108

Since 5/27 we’ve gone from about 5200 references down to 4000. There’s still PLENTY to do.

Because of the way strings are handled, the number of references “to const char *” will fluctuate during the conversion and probably not decrease much until we’re nearly complete.

Inheritance = A Good Thing

No, I didn’t just have some rich relative kick off and leave my name in the will. The closest I have to a rich relative is an uncle who can afford to buy a new pair of shoes every two years.

In the original MUD code and in most C-based codebases I’ve seen, mobs have one set of data and players have a similar but different set of data. They all have things like hitpoints and movement points, but mobs have things like AI scripts and behavior flags while players have extra things like skill values and guild memberships.

There are two ways to handle this in C: either have completely different sets of data for each, or have a core set of data and a pointer to the extended data depending on which type it is (player or mob). The first way is sloppy and dangerous, while the second is sort of a “poor man’s inheritance”.

I’ve rewritten this to use parent and derived classes and all of a sudden some things that were very hard to do are incredibly easy. It’s also become incredibly easy to have things work differently for mobs and players by being able to use overridden functions for the player versions of code.

For example, since mobs don’t have skill training, almost every check for something like a bash or headbutt is based on a combination of that mob’s level and racial statistics. For players the check uses skill values modified by actual attributes like dexterity and strength.

Instead of writing a huge function riddled with lots of “if” statements, I can now just write different versions for each. The code is cleaner, easier to write, and automatically smart enough to “do the right thing”.

I love C++. Even though I’m getting a bit addicted to C# lately, it still rocks.

I never could have made this change without massive use search-and-replace, since the way to access most of the data members of players has changed completely. I did not want to change 2000 data references by hand.

Visual Studio 2005

I’ve been using Visual Studio .Net 2003 for a long time. I’ve finally upgraded to 2005, and some of the changes are interesting.

One of the things I’ve been doing is converting a lot of the c-string functions to STL std::string. It turns out that the old string functions I’m gradually eliminating have been deprecated:

_snprintf: “This function or variable may be unsafe. Consider using _snprintf_s instead.”
strncat: “This function or variable may be unsafe. Consider using strncat_s instead.”
stricmp: “The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: _stricmp”
fopen: “This function or variable may be unsafe. Consider using fopen_s instead.”
strncpy: “This function or variable may be unsafe. Considre using strncpy_s instead.”

I will gladly avoid using those functions. I hate them.

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.

SourceMonitor Update

Ahh, the joy of code metrics.

Files: 132
Lines: 113,584
Statements: 58,853
% Branches: 29.2
% Comments: 8.8
Class Definitions: 52
Methods/Class: 6.86
Average Statements/Method: 14.7
Max Complexity: 477
Max Depth: 8
Average Depth: 1.86
Average Complexity: 11.47

For the first time, the number of lines of code has gone down.  This is because MobProgs were removed.  We also lost about another 400 or so lines because the change to saving objects using XML allowed us to eliminate some duplicate code (repeat after me kids: Duplicate code is BAAAD!)   The average complexity also seems to be gradually decreasing a little.  In general that’s a good thing, since maintainability of code is generally thought to be the inverse of its complexity.

XML Objects!

It’s done – objects save and load as XML data rather than some ad-hoc text format.  I’m sure there will be a few extra details to work out, but the saving and loading of basic objects works now.  The change cleared up a bug or two that would come up once in a while due to formatting inconsistencies.

I still have to fix the violence update problem that I mentioned on the 1st.  I’ve tried a few minor changes in the hope that the problem would be resolved, but it really does look like a full combat-process rewrite is in order.  The original method is fundamentally flawed, so we need to work out a better way.

More XML Conversion

I’ve started tackling the conversion of all object saving to XML. Player saving was easy, since players tend to be pretty much the same and have all the same data fields. However, with all the different types of objects, nesting, affects, extra descriptions, etc. objects are a bit more of a project to convert. Objects are also saved in more than one type of file — corpse data file, player files, storage chests, etc.

So far I have them saving to XML the way I want them to. The next step is loading, which will take a good solid afternoon or evening of codework.

I’ve been mulling over the idea of moving data files over to an SQL database rather than XML, since XML is DREADFULLY SLOW. It’s not dreadfully slow on this fast machine, nor will it be on any server that I use, but it could become an issue at some point.

SQL would be a good thing and a bad thing. I’d get the automatic field matching and data integrity at the expense of greater complexity. Although there are tools to edit SQL databases directly, it’s not as easy as editing a text file if I needed to change a value in a file. Backups would also be a bit more complicated.

One thing at a time — I need to finish this first.

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.

Server Options

I know it’s a little premature at this point, but I’ve started to think a bit about server options. There are a lot of different things I could do, with various cost/reliability/control considerations.

The primary decision is broken into one of four options:

1. Host it from home.
This would require configuring dynamic DNS and would probably be a slow connection. I have 512Kbps upstream, so that wouldn’t be too slow unless a significant number of players connected. What it would save in hosting costs it would certainly eat up in electricity costs. I’m not sure the exact amount, but leaving a computer on 24 hours a day costs me somewhere betwen 25 and 40 dollars per month in electricity. All in all a generally bad idea.

2. Sign up for co-location or a dedicated server.
An expensive route, but there is no shortage of control. At somewhere like aplus.com I could get a nice dedicated server for $99/month and a passable (celeron 1.7GHz w/512MB) server for $49 per month. I would be able to host all of my other domains and webspace all in one place. Other services offer similar packages, but pretty much none start under $50. This would probably be overkill for the web presence I have.

3. Try to get “free” hosting.
I might be able to find someone I know with server space available or find someone willing to host the MUD. This would be a good idea right until the point where whoever was hosting got sick of doing so. Too dangerous considering you get what you pay for.

4. Sign up for shared MUD hosting. This is probably the most sensible route for now. Here’s a comparison of what can be had at the various hosting services for $20 per month:

MudMagic: 300MB disk, 50MB RAM, 4% CPU, unlimited connections.
GenesisMUDs: 400MB disk, 55MB RAM, 15% CPU, unlimited connections. [actually $19.50/month]
Silverden: 100MBdisk, 20MB RAM, (CPU not listed), unlimited connections.
MUDDrake: 500MB disk, 48MB RAM, (CPU not listed), unlimited connections.
Dune Internet: 500MB disk, 60MB RAM, (CPU not listed), unlimited connections. [actually $16.00/month]
InfoLaunch: 600MB disk, 45MB RAM, 10% CPU, unlimited connections.
Mu-Host.com: 500MB disk, 75MB RAM, (CPU not listed), connections not listed.

Silverden is offering the most ridiculous of packages, woefully inadequate to any sort of task, and since they are offering a Pentium 200 dedicated server I’ll have to assume that they’ve gone out of business and just hadn’t bothered to take their site down.

Dune internet and Mu-Host look the best, especially since the major consideration has always been RAM more than processor in any MUD I’ve worked on. Right now, with only a couple zones connected, the server uses 18 megabytes of RAM. This is the debugger-profiler version, so it’s a bit more bloated, but it’s not unrealistic to expect to be using about 40-48 megs when fully assembled.

The irritating thing, though, is that most of the companies that list CPU percentage don’t bother to list the TYPE of CPU you’re getting a percentage of. For all I know MUDMagic could be offering 4% of a Pentium 200 while GenesisMUDs is offering 15% of Pentium Core Duo e6700. All CPUs are not the same.

If I were to pick a service I would probably go with Dune.

New String Functions

I’ve written new string functions based on std::string. They’re the typical case insensitive compare, check prefix, compare with list of names, get first argument, get last argument, and other string manipulation functions you’d normally see used with a MUD. The difference is: They’re not very prone to buffer overflows and pointer errors because they’re using std::string and it’s safer.

Not everything uses std::string natively yet – there’s still too much char * for my comfort.

Next tasks:

– Testing the string functions to see whether they work as I’d expect.
– Running the whole thing through valgrind to see whether I can catch any memory (stack-smashing) errors.

This new incarnation won’t be crashproof (software never is), but the goal is to have it never crash “randomly” like Basternae 2 did. I have a lot of good command tracing and error logging code and have written quite a bit more over the past few weeks, so if something explodes it should tell me exactly what and where.

Even Higher Warning Levels

When compiling code in MS Visual Studio .Net I always turn warning levels up to 4 (/W4). I’ll end up getting some warnings about things I don’t care about, such as converting an int to true or false when it’s something I did on purpose or not referencing a formal parameter of a function, but it’s still better for spotting potential problems.

For example, one warning that many people often ignore but can be a big problem is “C4706: assignment within conditional expression”. It’s the difference between:

if( ( x = a ) )
{
do something;
}

int x = a;
if( ( x ) )
{
do something;
}

Both pieces of code do the same thing: Set the variable X equal to A and then check whether X is nonzero. The problem with the first piece is that someone reading your code has no idea whether you meant to assign A to X or check whether X is equal to A. Even worse, one small typo and you could be doing assignment when you meant comparison. It’s better to break it into two statements like the second piece of code so you and everyone else knows what you intended to do AND that it actually happens the way you intend.

When compiling using g++ on Linux, I’ve always used -Wall. Since around 1995 I’ve been using that thinking it would warn me about everything it saw. There are a few things that Visual Studio would warn me about that g++ wouldn’t catch and the more it happened the more I started to wonder why. Cue the documentation.

AHA! While browsing the commandline parameter list for the GNU compiler, I found two interesting switches: -Wextra and -pedantic.

If you use those two switches, you’ll get what Visual Studio gave you and more. Much more. The bad news: I’m doing far too many ‘deprecated string conversions’. The good news: I’ve spotted even more potential problems and can fix them before they become big problems.

Some people think warnings are just annoyances. It’s a better idea to treat them as run-time errors. It might be more of a pain in the butt when writing code, but it’ll help in the long run. Thank your nitpicky compiler, it’s trying to protect you.

It’s amazing what you can do when you read the documentation…

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?

SourceMonitor Update

I just love these statistics tools…

Files: 127
Lines: 116,320
Statements: 60,630
% Branches: 29.5
% Comments: 8.5
Class Definitions: 50
Methods/Class: 7.04
Average Statements/Method: 10.8
Max Complexity: 477
Max Depth: 8
Average Depth: 1.87
Average Complexity: 11.71

The number of files, class definitions, and methods per class has increased, while the statements per method and average complexity has decreased.

XML Files

Traditionally MUDs I’ve seen have used raw text files for player files. Some might use binary, and some might compress them into a gzipped format.

There’s a problem with using this sort of file: in general, they are written or read a single line at at time. In Basternae 2 we had a significant problem with player file corruption. If the save or load engine had a problem with one of the values found in the file, you were pretty much screwed. If this happened during a file save, you pretty much had to restore the player file from a backup and hope that the backup was recent.

In order to banish this problem to the realms of oblivion, I’m incorporating the XML-based file save/load code that I used on my AlgoRhythmia 3 application. The difference with this code is that instead of opening the file and writing it line by line, dealing with values as they come, instead it creates an XML data store in memory and populates that with the values, and then serializes it to disk after all the values are checked.

So, intstead of having a half-written player file if the values are bad, it’ll be an all-or-nothing. If the data store fails to build, the file won’t be written, so instead of losing up to a week worth of play data, at most about 15 minutes would be lost, and that assumes the player hadn’t picked up or dropped any items in that time.

Once I have this code done I may give serious thought to setting up all of the game files to use XML. In addition to the build-then-save, XML also some built in checks and guards, so a typo in a hand-edited file would be much less likely to destroy the universe.

The downside: XML files are larger and slower.

The other upside: Data files written in XML can be easily processed by other applications, such as a web script that posts dynamic game stats on a page.

Project Line Counter Update

I’ve done a lot of writing, rewriting, replacing, and rearranging lately. Here are the current totals:

115,384 lines total
93,783 lines of code
9,859 lines of comments
2,071 mixed (code + comment) lines
13,813 blank lines

I guess you could call that a small but not quite negligible increase.

WinMerge

One of my biggest complaints when booting into Linux is that I don’t have access to a merge tool quite as nice as WinMerge. The current version of WinMerge doesn’t run under Wine, but apparently an older version, 2.0.2, will.

Some things are optional when writing code, but a good merge tool is not one of them. WinMerge is the perfect example of what a merge tool should be.

The Meld diff viewer is the best merge tool that I’ve seen on Linux, but it’s just a bit less usable than WinMerge.

SourceMonitor

I found a code analysis and metrics program called SourceMonitor today. I downloaded version 2.3.6.1 and fed it the source files for Basternae. Here’s what it came up with:

Files: 120
Lines: 114,121
Statements: 59,490
% Branches: 29.2
% Comments: 8.6
Class Definitions: 43
Methods/Class: 4.41
Average Statements/Method: 14.2
Max Complexity: 477
Max Depth: 8
Average Depth: 1.87
Average Complexity: 12.41

The check_command() function in the command interpreter is the most complex function. The complexity rating of 477 comes from the fact that it has 477 different branches of execution – essentially 1 for each different command the MUD understands.

Since this code is hybrid C and C++, the average number of statements per method metric doesn’t giev a full picture of things. It’s still pretty interesting to see the statistics on things.

SourceMonitor keeps track of “checkpoints” and lets you look at how code evolves over time, so it’ll be interesting to fire it up and post statistics now and then.