When I started this project, I attempted to research existing design patterns for rule engines. I found surprisingly few references. The information that I did find hand-waved away the mess of a real RPG. It was clear that to build anything more complicated than a hand-held JRPG, original research would be required.
Aside: It is important to note that I was not working from a completely blank slate. Selecting C# as my programming language introduced a variety of benefits, constraints, and biases. Another bias is my preference for strongly-typed solutions. http://en.wikipedia.org/wiki/Strong_typingAs I mentioned above, the most notable thing about pen & paper RPG systems is that they are messy. They are basically just layer after layer of rule exceptions that get increasingly more specific. In some sense, that's a good thing because if any one feature or spell is too difficult to implement, it can be removed without causing a great deal of damage to the overall experience. (The classic example of this being the open-ended wish spell.) On the other hand, that modular nature also makes writing a generic rule engine difficult.
The ACID properties of modern database design provide a good set of guidelines for how feats, spells, and effects should be implemented. All operations should be independent of each other and reversible. Due to the diversity of the effects involved, particularly for player characters as they level up, I decided to make each individual change responsible for both applying itself and cleaning up after itself. Nothing fancy, just an ITeachable interface that specifies a method for teaching and unteaching an object that can be added to a character. Most of these methods are only a couple of lines.
|Yes, unteach is a word.|
Similarly, the spell effects also manage adding, maintaining, and removing themselves. Effects derive from a common abstract class that defines the effect's name, duration, turn added, and group. A series of methods handle timing: OnAdded, OnRemoved, OnTurnStart, OnTurnEnd, OnEncounterEnd, OnShortRestEnd, and OnLongRestEnd. The majority of effects don't need to implement these methods, because they will be cleaned up automatically based on their specified duration. These callbacks are for handling behavior such as "when this enchantment is dispelled, take 1d4 damage". In the future, a variety of other methods will probably be added to this list, such as OnOpportunityAttack.
To handle more complicated effects, there is an EffectsList class that extends Effect to produce more complicated effects through composition. A standard StatEffect can increase the value of one or more stats by a single amount, but an EffectsList of StatEffects would be used to represent something like a spell that granted +4 strength and +2 dexterity. Since EffectList implements the Effect interface, consumers of the EffectList, such as the character class, can interact with complicated and simple effects in the same way.
Using these techniques, the majority of basic and passive effects can be represented without writing new code for each feat or character trait. Highly specific rules (such as a feat that grants +1 armor while wielding two one-handed weapons but only if the character is wearing light armor and one of the weapons is a dagger) can be quickly created by writing a custom class that extends Effect and overrides a couple of methods.
The next dev diary entry will discuss my approach to statistics, and some ideas for calculating cumulative bonuses of mixed types in a generic way.