So maybe at one time your Thinghad a Brainwhich had a Controllerwhich had a Managerwhich had a MiddleManager…

I think many of us have made choices about codebase design/architecture that we might redo if we were starting from scratch (yes, I'm contemplating writing (yet another) codebase, so I seek the collected and hard-won experience of MB).

What lessons have you learned about real-world use and needs that would influence you designing a mud codebase today? What do you really need? What 'abstraction' wasn't really necessary?

The first lesson that comes to mind is that event callback hooks can be a real Faustian bargain; there are a lot of use cases they're flawless for, but there are many more where they look good (and easy) and are actually vastly inferior to a data-driven mechanism that permits introspection into the configured behavior instead of presenting you with a callback that may as well be a brick wall.

For me, a big thing I learned to hate from DikuMUD-style codebases is the unnecessary complications caused by treating different game objects as different classes of data structures.

There's no fundamental difference between a room and an "object". A room has an inventory and an internal description. An object may or may not have an inventory, and has an external description. An NPC is also just an object that happens to be "alive" and have a controlling program. A player is just an NPC that has a socket and command interpreter as the controlling program.

Making all of those share the same structure wastes a bit of memory, but allows you to easily transform anything into anything else, which can be great fun when, years later, you decide you want to code a shrink ray that lets someone crawl inside a backpack.

Also, get rid of using one value to mean multiple things. I'm looking at DikuMUD's bitflags….. again, a memory saver back in the day, now just an annoyance. if(thing.is_dark && thing.is_electrified) is much cleaner and easier to work with than if(thing.flags & IS_DARK|IS_ELECTRIFIED), especially when you want to store those things in a database.

We've been able to put people in bags on Alter Aeon since 1997. In a lot of ways, it wasn't the greatest design decision, and I'm not sure I'd recommend it for a new game. Very little gain for the required work, though I did learn a lot about software engineering doing it.

In my observation, a lot of people designing a codebase would have done better if they were actually designing a game. If you don't have a fully playable game at the end of your project, how do you know that your codebase even works? Of course, it's nice if you keep the engine logic reasonably separated from the game logic, but having only engine logic is simply half-baked (unless your engine comes with awesome building tools for content and clients, cross-platform support, or any of the other standard features of today's free middleware offerings).

The other thing I'd like to point out is that it's a huge project, with many boring parts. Like any huge project, break it up into smaller ones or you'll go crazy.

My observation has been that codebase design is talked up and hand wrung over a lot more than actual game design, leading to some pretty turrible games.

I tend to worry a lot more about the mechanics of my game when I'm writing something custom. Evennia was initially meant to be for a space-based sci-fi game. I knew what my game needed, and that's what ended up in Evennia initially.

I've started my codebase from the Java textbook example with a Server and ServerThread classes. Players are just a database and HashMap entry. I'm pleased with that architecture: it's simple and functional. I only wish I had made it coordinate based. The code relies heavily on the concept of room and it would be painful to upgrade it into an MMORPG.

I tend to worry a lot more about the mechanics of my game when I'm writing something custom. Evennia was initially meant to be for a space-based sci-fi game. I knew what my game needed, and that's what ended up in Evennia initially.

That's probably why Evennia is one of the best options out there for people who want to design their own modern MUD.

So maybe at one time your Thinghad a Brainwhich had a Controllerwhich had a Managerwhich had a MiddleManager…

To me, this is a great way to sum up some of the widest-spread pitfalls that codebase writers seem to fall into, more or less willingly. When someone writes code with no specific game (and no specific audience, even other devs) in mind, they worry about general code design patterns more than about solving a specific problem. It's design from the top down, whereas you could be designing from the bottom up, starting from the features you want to have, and working your way up into the clouds.

This pitfall is common not just in coding, but in creative writing as well. If you sit down and tell yourself you're going to write a story about Love, it's just not going to be as good as when you sit down because you have to write a story about a kid who broke 3 ribs snowboarding down a roof, and then 5 drafts later you discover it's actually about love.

Seconded. Your "Brain" class and your "controller" class are doing the same things, so merge them, and your "MiddleManager" class has been doing all the work for a while so fire your "Manager". Don't be afraid to delete code that is redundant or that you know is bad, and replace it with something better (if it needs replacing at all).

If you're relying on external libraries, make sure that you understand how they work, and what constraints they impose on your design. Most of the truly scary code I've written is in order to adapt a library's conventions to fit my project, when it would have been better to understand that library and work with it rather than fighting it.

I'd also agree that it's best to start with a game and factor out the engine part of it. Once you've gotten to the point of having a working game you'll have a better idea of where to draw the game/engine division, and your engine will be better for it (since it's functional). Bonus points if you implement a second game using the engine and backport (if reasonable) all of the fixes to the original.

Seconded. Your "Brain" class and your "controller" class are doing the same things, so merge them

Speaking as someone who has actually been using the Brain/Thing approach for the last 12 years, and made the initial mistake of merging the AI controller into the MobileBrain: Don't. Use aggregation, the same way as you'd handle the relationship with a socket object in a HumanBrain. It'll make the code much easier to maintain when you expand the AI to support specialised behaviour for certain types of mob. Let the Brain act as a filter for incoming and outgoing data, and track its current states (who it's fighting, who it obeys, etc), but use the Controller to make the decisions about what to do next.

Making the Controller a separate class also means it can be used by other Brains or elsewhere in the code - for example, I did split off the WarBrain decision-making. As a result, I'm able to reuse the same functionality to generate step-by-step advice for newbies playing a tutorial war, offering them the same suggestions for what to do next that the AI uses for its actual actions. On the other hand, when I take control of a mob's Thing (the equivalent of "switch" in a DikuMUD) it stops moving and fighting - I have to control it all manually, because the controller is built into the MobileBrain, and I've just swapped that out for my HumanBrain. Some might argue that the controller code could be moved into the base class, but that would be very sloppy design, as it's extremely rare that it's used for anything other than mobs (and it still wouldn't allow the code to be used outside of the Brain).

On the other hand, when I take control of a mob's Thing (the equivalent of "switch" in a DikuMUD) it stops moving and fighting - I have to control it all manually, because the controller is built into the MobileBrain, and I've just swapped that out for my HumanBrain.

I think in most use cases where an immo switches to a mob, they would want to control it manually and disable any automatic behaviors it may have.

That said, it seems to me that if the AI controller and the socket are not mutually exclusive, it can open up some interesting possibilities for mortal gameplay. (You can still choose to unplug the AI controller when a QM switches to a mob they want to "numbify".)

I've been working towards a design that more or less completely erases the line between a PC and an NPC. This would, for instance, allow players to "mortal-switch" between their characters and their characters' NPC followers while still keeping the automated behaviors that make their NPC followers easier to manage. Conversely, allowing for an "AI" to actually control a PC can make PC's easier to manage as well. (Another word for a PC AI is the set of triggers, macros, or advanced scripts that power players may know how to configure in their client–but what if they could write "mobprogs" for their PC's on the server, or at least select from behavior presets already in use by NPC's in the world).

Giving both PC's and NPC's "MobileBrains" and "AI controller" can free the player (essentially, "the socket") to focus on higher-level tactical decisions, e. g. decisions that alter the set of behaviors that all entities within their control are executing automatically. This could be very interesting in heavily combat-oriented gameplay where the player is allowed to control more than one "creature" at a given time.

Seconded. Your "Brain" class and your "controller" class are doing the same things, so merge them

Speaking as someone who has actually been using the Brain/Thing approach for the last 12 years, and made the initial mistake of merging the AI controller into the MobileBrain: Don't. Use aggregation, the same way as you'd handle the relationship with a socket object in a HumanBrain. It'll make the code much easier to maintain when you expand the AI to support specialised behaviour for certain types of mob. Let the Brain act as a filter for incoming and outgoing data, and track its current states (who it's fighting, who it obeys, etc), but use the Controller to make the decisions about what to do next.

Making the Controller a separate class also means it can be used by other Brains or elsewhere in the code - for example, I did split off the WarBrain decision-making. As a result, I'm able to reuse the same functionality to generate step-by-step advice for newbies playing a tutorial war, offering them the same suggestions for what to do next that the AI uses for its actual actions. On the other hand, when I take control of a mob's Thing (the equivalent of "switch" in a DikuMUD) it stops moving and fighting - I have to control it all manually, because the controller is built into the MobileBrain, and I've just swapped that out for my HumanBrain. Some might argue that the controller code could be moved into the base class, but that would be very sloppy design, as it's extremely rare that it's used for anything other than mobs (and it still wouldn't allow the code to be used outside of the Brain).

Yeah, I didn't have a particular implementation in mind when I said that. My comment was contingent on the brain and controller classes doing essentially the same job, whereas it seems like you've drawn a clean line between the functionality of the two. In that case, I agree that it's more flexible to have both.

Probably that while storing data as character delimited lines in a text file is very handy when you start out and for manual database editing it's hellish if you want to change the format of a line or suddenly realize there's more data you want to store. Very soon, you start wishing it worked like some where you just have lines that end in a character (~ anyone?) and a line with a character (# anyone?) on it that separates groups of lines. After that idea, you then realize that both systems assume no data will become corrupted in an evil fashion and that characters won't be misplaced and that your code must ensure that you must never use those in any data that will be stored in a field that might be put in your puny "database". Of course, when you are using strings, your string could theoretically end up with any sort of legitimate character in it. At which point you decide that JSON might a good idea, but you have to find a way to do the serialize, deserialize bits… Of course, I never got past the first setup because it's adequate when you don't have any sort of test game running or any valuable data…

tl;dr You really need to have a sense for all the kinds of data you intend to store before you start coding much of anything, much less the code that stores/retrieves that data. If you don't you will be having contant FUN trying to make sure things are persisted and reloaded correctly. Either that or you have to either pick a fairly flexible system that can handle being changed when you want to store other data. A real database would work, but be a hassle if you needed to add a field.

A flat file database, if it's not kept extremely simple, commits you to recreating many of the features of a database from scratch (such as maintaining consistency), so from that angle I agree with you. But serializing objects is pretty trivial in most languages, whether it's to json, xml, or some other format. I recommend that if you're struggling with that, that you look up good json libraries, which should be able to inspect arbitrary objects and return a representation which you can save out to a file. It will really be easier than you seem to think.

Well, I'd kind of like to use google-gson, but dealing with code licenses beyond my own code is confusing. Google-gson and a few other json libraries are license Apache 2.0. It's not clear to me how that affects my own code if at all. Especially given that i'd like to use the TypeAdapter stuff.

Trying to write a bunch of code and figure out to how to interface unfinished code to something like MySQL seems like a poor idea for a beginner at this. So, unless you use json, xml, flat files are the easy way to go.