Until recently, the only code I’ve released as open source has been the Magma MUD codebase.
In the process of posting the Magma source on Github, I kind of got hooked on posting code online. Since then I’ve posted the source for a handful of applications, a mix of Linux and Windows desktop apps. A 12-day commit streak so far, yay!
I’m also considering open-sourcing the Basternae 3 codebase. The main things that make it easier to work with than old-timey C-based MUDs are the use of C#, which has amazing exception handling and debugging capabilities (no more attaching gdb to a core dump), and speaks XML natively, so data files are Human-readable, portable, fairly robust, and extensible. The editor is also getting to be pretty good.
To open source Bast3, I’d need to write a lot more documentation, and I’d need to “genericize” a lot of things that are specific to Basternae. It’d be a lot of work, but I think it’d be a fun project. The source is already in a private repository on Github, but that’s the easy part.
A while back I made a start on building the client using WPF, the Windows Presentation Foundation. It only made sense to give it a shot since the whole MUD is based on C# and .NET.
That effort stalled due to a roadblock or two, probably because I didn’t know about routed events and partly because I didn’t understand RegEx very well. I have a better handle on regular expressions thanks to spending some time with the Django web framework.
In the process of trying to rework the C++/wxWidgets-based client to support the new tiled map graphics I’ve experienced a lot of pain. The kind of pain that can only be explained by the fact that a string is not a first-class object in C++ and that text parsing can get to be unbearable when you have a complex stream over TCP.
That has prompted me to take a look at the WPF version of the client again. Rather than being based on some ugly recursive character-by-character text processing code, it’s based primarily on regular expressions — one to parse out the ANSI codes, and one to process the XML tags for the various client data (life meter, map info, room description, etc.). The first one was done when I left off, but the second piece hadn’t been started yet. Well, it’s in place now and I can continue building the rest of the newer client.
Getting the WPF version up to and past the usability of the wxWidgets version will probably be faster than untangling the text parsing in the spaghetti code.
Zone data encapsulation is complete now. It was a long, tedious process, but it’s all for the greater good.
Last time the code was Fx-Copped, It ran 778,249 checks and found 10,850 issues. This time, it ran 865,441 checks and found 10,370 issues. It’s an improvement, but not a huge one.
We now have the power to enforce sanity. For instance, when an object’s condition is set, we cap it at a max of 100% (perfect) and a minimum of 0% (destroyed).
Just a progress report — things are moving along slowly but steadily and it shouldn’t take all that much longer to finish data encapsulation for the zone data classes.
I ran FxCop against the Basternae source for the first time today. It ran 778,249 checks and found 10,850 issues.
While some of these really are design flaws, some minor and some serious, many of them are not applicable to this project, such as the security declarations and assembly signing. Here’s how I break down what FxCop has complained about:
* Array fields should not be read only.
* Avoid type names in parameters.
* Avoid uncalled private code.
* Avoid unnecessary string creation.
* Collections should implement generic interface.
* Consider passing base types as parameters.
* Do not cast unnecessarily.
* Do not concatenate strings inside loops.
* Do not declare visible instance fields. (This is what the encapsulation I’m working on is all about, and was a significant percentage of what FxCop complained about.)
* Do not initialize unnecessarily.
* Do not name enum values ‘Reserved’. (This was a surprise.)
* Do not pass types by reference.
* Do not raise reserved exception types.
* Enums should have a zero value.
* Flags enums should have plural names. (Dang, this one is pretty nitpicky.)
* ICollection implementations have strongly typed members.
* Identifiers should be cased correctly. (Yes, it actually complains about that, and there were a lot found.)
* Identifiers should have correct suffix.
* Identifiers should not have incorrect suffix.
* Identifiers should not match keywords. (Alias, Object, Event, and Exit classes.)
* Implement standard exception constructors.
* Interface methods should be callable by child types.
* Lists are strongly typed.
* Mark all non-serializable fields.
* Mark assemblies with assembly version (Been doing this, but missed one.)
* Mark ISerializable types with serializable. (Fixed these immediately. Duh.)
* Mark members as static. (It’s not the right solution for the ones FxCop found, but there are problems with what it found.)
* Nested types should not be visible.
* Non-constant fields should not be visible. (Encapsulation again.)
* Operations should not overflow.
* Operator overloads have named alternatives.
* Override equals and operator equals on value types.
* Provide correct arguments to formatting methods.
* Remove empty finalizers.
* Remove unused locals.
* Rethrow to preserve stack details.
* Review visible event handlers.
* Static holder types should not have constructors.
* Test for empty strings using string length.
* Type names should not match namespaces.
* Types that own disposable fields should be disposable.
* Use properties where appropriate.
* Validate arguments of public methods. (Note that FxCop does not appear to be Contract-aware, so some of these will be non-issues.)
Non-applicable issues found:
* Assemblies should declare minimum security.
* Assemblies should have valid strong names.
* Mark assemblies with CLSCompliant.
* Mark assemblies with ComVisible.
* Specify CultureInfo. (This is not an internationalized application.)
* Specifiy IFormatProvider. (Yet again, not an internationalized application.)
* Specify MessageBoxOptions. (This refers to right-to-left reading language support, also not applicable.)
Issues I don’t agree with:
* Do not catch general exception types. (Sometimes it really is the simplest and best solution.)
* Do not declare read only mutable reference types.
* Do not expose generic lists.
* Do not pass literals as localized parameters. Use a resource table instead.
* Identifiers should be spelled correctly. (FxCop didn’t like my use of “x” and “y” as variables in a coordinate class.)
* Identifiers should not contain underscores. (There are a ton of these, about 30% of the Cop’s finds. Pretty much all of our flags are named using underscores.)
* Long acronyms should be Pascal-cased. (Side effects of the previous issue.)
* Only FlagsAttribute enums should have plural names. (Stop whining about names already!)
* Prefer jagged arrays over multidimensional. (No. There’s a specific reason for multidimensional arrays.)
* Short acronyms should be uppercase.
* Used preferred terms.
So yes, there are quite a few things to be fixed. I’m not going to spend any extra effort on that right now — some of these problems will resolve themselves as part of the in-progress changes. Even so, it’s nice to be aware of what doesn’t match preferred style, even if it accomplishes nothing other than having a solid knowledge of which rules are being broken on purpose. I’m sure I’ll post updates on FxCop stats as the code evolves.
You have passed the halfway point and are nearing your next codebase.
There’s a lot to do, but I’m halfway through encapsulating all of the data for the zone classes. This will give us the power to validate data before assigning it to a variable and will help us reduce the number of boundary checks in the code by moving them to the property we’re trying to set. End result: code that’s easier to maintain and modify.
I love the refactoring support in MS Visual Studio. It makes certain things like field encapsulation incredibly easy.
For instance, thanks to its origins in C, most of the Basternae 3 codebase doesn’t have encapsulation yet. This means that there are tons of class member variables declared like this:
public string _keyword;
Setting the _keyword member variable to private and creating a property named Keyword with getters and setters that reference the _keyword variable would take about 30 seconds.
With Visual Studio it’s easier: Just right-click on the variable and select “Encapsulate Field”. It will come up with a reasonable property name and automatically generate the code and set the variable to private.
Pretty nice, but nothing to write home about.
But, here’s the magic:
All references to that variable in code are AUTOMATICALLY changed to refer to the property. If that member variable was used in 50 different places, Visual Studio just saved you the trouble of making 50 different changes or doing a search-and-replace that may or may not get everything on the first try.
Of course, this doesn’t automatically update any XML files that have been saved using the old variable name. To take care of that you can do one of two things.
1. Do a search and replace in every XML file that your class would have been serialized to and hope you didn’t miss one.
2. Use the XmlElementAttribute on your property to map the saved attributes to your new type:
public string Keyword
#2 is obviously safer and easier, especially since it doesn’t require changes to existing data. Of course, your data files might be clearer to read if they used the exact property names, but do you want to go through the trouble? Likely not.
Yesterday I opened up the source code for the Basternae Client in order to make a few changes, fixes, and updates. What I had forgotten in the two years since I had been programming C++ actively is what a pain in the behind it is to get anything done in C++.
Sure, you can do anything with it, but there’s so much tedium and overhead involved that it takes forever to get anything done. Sure, it’s great for low-level code where you’re tossing bits and bytes around, but for user interface development it’s just too unwieldy.
Out of frustration, I sat down and rewrote most of the client in C# in a few hours. Mind you, it’s not fully implemented, but it’s usable as a telnet client. I’d say about 8-10 more hours of development time and it’ll be ahead of where the other client was.
That makes this the third version of the client. The first version was pure SDL with C++. The second version was SDL combined with wxWidgets. The third version is C#.NET and WPF.
Another benefit of switching to WPF is that the CPU utilization of the client has gone down tremendously, mainly because we’re using only one interface drawing library now.
In the process or getting an alpha version of the editor ready I’ve found that I had to separate the game code, a.k.a. “business logic” from the data, a.k.a. “object model”. Otherwise I’d have to include the entire MUD engine in the editor download. Since that’s not something I want to do I’ve had to pull them apart. It’s a lot like pulling apart a cold grilled cheese sandwich, and as expected, the bread did tear a little.
The “core rewire” still needs some work, but I should have an “alpha”, a.k.a. “try it and see how broken it is or isn’t” version of the zone editor out sometime this month.
The connection state management code in the socket layer of Basternae has always been what I call “spaghetti code”. It was a single method containing a huge switch statement with pretty sizable blocks of code for each case. Tracing program flow for characters that were not actively playing was always difficult due to the complexity and verbosity of this 1600-line function.
Today I refactored it into a bunch of more sensible pieces that go together a lot more smoothly. The 1600-line function is now 80 lines, I can actually tell what’s going on during program execution, and a handful of confusing and unused variables have been eliminated. Huzzah!
It wouldn’t have been unrealistic for the designers of .NET to find a way to make this work:
foreach( Item i in ItemList )
if( i.ShouldBeRemoved )
What happens is you get a ‘collection modified’ exception and you’re hosed. You can’t move to the next item in the list because removing the item broke the list. It wouldn’t have been that hard for the design of IEnumerable (the thingy that makes foreach possible) to keep track of where the next item was even after a removal.
Instead of being able to use the above code, something like this clunky bit is required:
for( int i = (ItemList.Count – 1); i >= 0; i– )
if( i.ShouldBeRemoved )
It’s like telling someone they can avoid head-on car collisions by always driving in reverse.
Anyhow, there was a bit of a problem with the Select() command and socket management, and this was the solution. The socket code is now functional and stable enough that I can log in and run around trying to play the game. It’s not terribly playable yet — I have a *LOT* more work to do, but it’s still theoretically possible that a development server could go up i mid-but-more-likely-late May.
I completed the last of the code for the command processing engine today. Not much detail to report — it’s done and seems to work well enough.
Command processing in the old Basternae was pretty klunky. The command interpreter would just pass on any text that was entered and each command function would have to do a lot of parsing to split up the command strings and figure out what the user actually intended. About half of each command would be devoted just to breaking up strings in some cases.
Luckily with .Net we have all these nice string functions — Split, Join, Substring, IndexOf, Remove, and many more.
With the new command processing engine I’ve just written the command functions are a lot easier to read, maintain, and create more of.
Of course, I’ve had to redo every command in the process and that’s only 80% done so far, but it’s a far better system overall.
And, of course, I still have to finish the replacement spell system.
I came across this article on Scripting with C#.
Quick summary: It tells how to load and compile C# code from within a running application so that you can dynamically load scripts.
This is something I’ve been meaning to find out how to do for a while. I’ve always wanted to create a file-based spell system that loads and compiles all spells at boot time so that they aren’t so tightly integrated into the engine. As it was historically, if you removed a single hardcoded spell, such as “armor”, the entire mud would crash or at least be very unhappy.
Well, what good are 400+ hardcoded spells going to do you when you use the mud engine for a sci-fi, historical fiction, or contemporary post-apocalyptic setting? None at all. That’s why getting them once-removed from the core is something I wanted to do. Super-long-term there’s not just one MUD coming out of all this effort.
I don’t mean just writing a basic telnet chat server with a few objects and commands you can interact with. Any amateur programmer can do that.
No, I mean writing a full-featured MUD engine that supports all of the features MUDders have come to expect from a game like Basternae 3. It’s really dang hard.
I’ve spent hundreds of hours working on this rewrite and it’s still pretty far from done. The area format is still changing quickly enough that I don’t have a working zone editor I can pass out, and I still have a whole spawn engine to write (for those of you who know MUD internals, it’s more-or-less the Basternae equivalent of “resets”.) I also have to write some sort of mob-action-scripting engine.
It’ll be worth it when I’m done because I’ll have an engine that has been completely written by me and nobody will be able to say what I can and can’t do with it, but it sure is arduous — it takes a damn long time to write 100,000+ lines of code (as you can see by noting that the B3 blog posts beginning about a year ago). And I’m no newbie coder — I’ve been writing code for more than 20 years, MUDs for 6-8 years depending on how you count the gaps, and commercial code for 3 years now.
That’s why Basternae 3 isn’t open yet. I could easily have taken the old zones and code up and just went with that, but that’s not something that supports my long-term plan. Maybe I’ll mention more about that plan at some point.
A little less than a week ago I mentioned that I had broken the heck out of the codebase.
Well, it’s all back together again. The MUD engine and the zone converter are as healthy or healthier than they were before, the weather’s nice, and all is well with the world.
In the interest of making sure the MUD can be modified easily well into the future I’ve broken the heck out of the codebase.
I’ve been refactoring, rearranging, renaming, and restructuring things so they make a lot more sense. That has inevitably broken a few things, but it will be far easier to add new types, flags, and enumerations in the future — things like adding a new terrain type without breaking existing terrain types or having to recompile the editor and re-convert all of the zones.
Believe me, we had quite a headache with zone file format changes on Basternae 2 and I don’t want to repeat it.
This means that it’ll take a few days before the codebase will build again and I’ll have some work to do on the converter and the editor, but it’ll be worth it.
I’m still looking at putting up a test server in May and so far Slicehost is the most likely candidate. Feel free to offer suggestions. Since it’s out-of-pocket and non-income-producing, my budget isn’t any more than $20 per month (should I be accepting donations for this?)
Now that the communication routines are a lot smoother I’ve been able to test and debug more of the rewritten code. I fixed a bug in the new command interpreter and one in the string builder today. I also came up with a better way of handling per-area repop points so it’ll be a lot easier to set up new hometowns with race/class spawn points, especially since I’ve added repop point editing to the zone editor.
One thing that has always been a little annoying is updating/changing the about box every time the program version or date changes. Here’s a neat little way to make it pull the values from the assembly info:
private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
Assembly assembly = Assembly.GetExecutingAssembly();
AssemblyCopyrightAttribute copyright =
assembly, typeof( AssemblyCopyrightAttribute ) );
AssemblyTitleAttribute title =
assembly, typeof( AssemblyTitleAttribute ) );
System.IO.FileInfo info = new System.IO.FileInfo( assembly.Location );
DateTime date = info.LastWriteTime;
” version ” +
” released ” +
“.\nThis application is ” +
“\nWritten by Jason Champion (Xangis).\nFor the latest version, visit http://www.basternae.org.”,
“About ” + title.Title );
With the following AssemblyInfo:
[assembly: AssemblyTitle(“Basternae Editor”)]
[assembly: AssemblyCopyright(“Copyright © 2006-2008 Zeta Centauri, Inc.”)]
We get the following about box:
It’s pretty easy to customize it to grab other assembly attributes once you know how to retrieve them.
I spent quite a lot of time manually converting the race files to XML. The file format didn’t lend itself well to automatic conversion, nor was much of it an easy search-and-replace setup. Some of the values were strings of flags, some integers, some full sentences, without much consistency.
It wasn’t fast and it wasn’t easy, but the race files are now loading in the new Basternae.
Converting area files should be a lot easier since they have a more consistent format, but we have a lot to convert before we get to that point — class files, for instance, which will be very similar to race files.