I am working on a turn-based, dice roll-based roleplaying game simulation engine. Here are a few relevant bits about it:
Background
The system is d20 based. The complexity of the system is somewhat less than well-known RPG systems like Pathfinder or D&D, but not so simple as to make it trivial. As an example, we have 3 primary stats (hitpoints, stamina and mana), 3 secondary stats, 3 classes, each with 3 specialization options (pick one), and a total of 30 unique abilities, some active (player spends their turn to do it), some reactive (occurs at the player's option after a certain condition is met, possibly outside of their normal turn), and some passive (always in effect). Players get to pick certain abilities to "equip" into their character, based on their rank and progression through our advancement system.
There is a group of people in my MMO guild who maintain the design of the system. They issue "patches" a handful of times per year. The scope of a patch can be fundamental, like introducing an entirely new system (rare; yearly or less often), or minor, like changing the name of an ability, or increasing/decreasing the numerical resource cost or effect of an ability, like adjusting the maximum amount of hitpoints you can heal with one heal spell.
The cadence and scope of the patches is outside my control, with me being the designer of the simulation engine. A patch may require a lot of work for a program to support a new system or major change to the rules, or it may just be changing a number or a string somewhere.
My goal is to get a core "engine" built that fully models the mechanics of the system, then have several components leveraging it:
4(a). A Monte Carlo simulator, that generates pseudo-random battles based on tunable criteria; for example, simulate 1,000,000 1v1 fights between a Warrior and a Mage.
4(b). A persistent database of all our characters with their up to date "builds" along with all the metadata associated; e.g., name, race/species, abilities slotted, max HP/SP/MP, rank and progression boolean flags, etc.
4(c). A web app providing a user interface to the database and a "live" combat simulator, where players can make choices according to the rules of the system, are restricted from taking actions that violate the rules, but can effectively play out a fight, either PvP (against other players) or PvE (against NPC enemies created by a game master). Players can do things like use abilities, attack, heal, or attempt to retreat from battle. NPCs are given configurable character sheets that can have custom-designed abilities above and beyond what players can normally do.
Describing Combat System Patch Trends
In this system, there are certain "near-constants" that are unlikely to ever change; or if they do, it's only once or twice a decade. I'm really not worried about them changing, because if they do, I will simply accept the development cost of implementing them, even if I have to do a lot of rework to existing code. Examples: our core dice roll is based on a d20. Our core stats are hitpoints, stamina points and mana points. Our three classes are Warrior, Rogue and Mage.
Then, there are less constant things, that will change, on average, about once a year. Examples include the list of abilities -- we may add or remove entire abilities, or completely change the mechanics of an ability for balance's sake. It may be possible to describe these changes in terms of general purpose rules or fundamental operations; for example, you could build up an ability's effects out of a grab bag of predefined primitives, like "deals damage," "heals a given target," "confers a bonus to attack rolls for X rounds," and so on.
And at the bottom of the pyramid, there are tweaks, which are just extremely isolated changes that happen somewhat frequently, but are easiest to describe in code, like the number of points of damage a certain attack ability deals. I'd like to be able to implement these very quickly and easily ("DRY" if possible) and have all the downstream tools/programs adopt the new change without any other code changes to them.
My Questions
How much of the system should I directly hard-code using the programming language I'm planning for my project (probably Zig)? How much, if anything, should I encode in a data format, like JSON?
I've looked at the design of larger, more complex games before while modding, like Sins of a Solar Empire, Skyrim and Starcraft 2. I notice that many major game designers build their game systems based on the following architecture:
- Certain basic attributes of their game system are hard-coded into the game code. A modder, for example, is unable to change these aspects of the game in closed-source games. In my case, if the system maintainers decide to change these aspects of the game, I would have to change my code. That's OK with me, as long as I anticipate these things and make it so the code changes can be small and infrequent most of the time. Frequent, major code churn is undesirable for obvious reasons.
- They create fundamental functions that perform tunable, primitive operations, then have some kind of data definition files that describe most of the game content and mechanics. User-visible skills/effects/weapons/etc. have their mechanics described as a series of these primitives. A closed-source game is "moddable" by applying these primitives in different ways to adjust the behavior of existing game assets or abilities (weapons, items, skills, ...) or even creating entire new ones.
For a game system whose code will be 100% open source on GitHub, is there any reason to define game mechanics in data files as opposed to just hard-coding it all? Do games only create these extensible elements for modding purposes for the community, or are there maintainability advantages of doing this?
Example: Sins of a Solar Empire defines most of its ships and abilities in terms of JSON files. These JSON files will define, say, the list of individual weapon mounts equipped on a ship class -- maybe it has 3 lasers and 2 missile launchers. When the game starts up, it reads all the JSON files and builds a representation of the full game's rules dynamically in memory by creating data structures at runtime for each of the ship classes, weapon types, etc. defined in JSON.
Obviously, going with this dynamic, data-driven approach is much slower than having native code directly describe the behavior of the system. Performance-wise, hard-coding wins, hands down (both for loading time and for actually executing the simulation of the game mechanics). With Zig, I may be able to chop down much of the loading time using comptime
, but there is still going to be a cost there.
Is there a maintainability advantage to defining content/systems/abilities/items/etc. in data files as opposed to hard-coding them? Does it take more work to update these things in code and then propagate them through the whole stack, as opposed to loading them dynamically from data files?
Are there any best practices to figure out where to make that division or layering separation between things that are hard-coded and things that are data-defined?
Why I'm asking
My biggest "fear" is that I'll complete the implementation of everything, and then a patch will come along that will necessitate major re-work in the database, and the core engine, and the UI, and the network code (API) between client/server.
I'm certain that some changes to the system will make this absolutely unavoidable, and I've accepted that reality. But how do I minimize the frequency of having to do those heavy changes, and ideally just be able to adjust things in one place? It feels like using data files to describe system mechanics will help with that a lot. But I'm still not sure exactly what data to define to achieve a good balance between flexibility and expressiveness.
I know that taking this idea to the other extreme ends up producing something like the Enterprise Rules Engine anti-pattern (inner platform effect), but hard-coding everything also feels like a foolish design.