It appears I was only mostly right in what I had planned. I was having difficulty reconciling ‘data type’ and ‘data object’ in some cases. For instance, ‘rage power’ is a class feature, cool… but it can also be a data type. There are literally hundreds of rage powers. I know. I counted.
Turns out a solution is pretty simple. Time for some data modeling.
Good data projects really should have a data dictionary. I’ve worked (been stuck with) too many that don’t, so I’m starting one now.
Some of the names are subject to change, but I describe the purpose of the various types below.
This is the core element of the entire system. Almost everything I care about is a data object: feats, spells, skills, monsters, everything. A data object has:
- attributes, meta-information about the object that describe how the object is to be treated. Most data objects have default values for the attributes.
- one of the common attributes is ‘parent’, indicating the data object this is an example of (“Rage Power” has an parent of “Class Feature”). This will often be set more or less implicitly by a new object being subordinate to a data object or data type. “Rage Power” is defined inside “Class Feature”; I can set it explicitly but don’t need to.
- another attribute indicates that the data object can be used as a data type. For instance, “Class Feature” might be a data object (that probably never gets printed, oh well) marked “data-type=’class-feature'”. “Rage Power” is a class feature that might be marked “data-type=’rage-power'”. When I want, I can use a ‘rage power’ data type marker and all data objects under it are marked as being rage powers.
- another attribute indicates ‘index type’, how to mark objects of this type when they get indexed. “Rage Power” is a class feature, and is indexed as “Rage Power (class feature)”, while “Animal Fury” gets indexed as “Animal Fury (rage power)”.
- stat block (often empty): spells and monsters are examples here, but even feats can be said to have a stat block (type, prerequisites, type, and minimum level). May or may not be rendered as an actual stat block.
- content: block text, tables, lists, etc., and may have sections and subobjects.
Data type is actually much smaller and simpler than I’d anticipated. It’s basically a marker to show that the objects ‘inside it’ are of a particular type. Importantly, though:
- the data type markers are never printed, they exist only in data.
- there is a hierarchy to the data type Word styles, but only so they can be nested within other things (a bloodline has bloodline powers; structurally the data file might have
- Bloodline [data type]
- Draconic [‘bloodline’ data object]
- statblock (bloodline skill, list of bloodline feats, list of bloodline spells, etc.)
- Bloodline Powers [section heading]
- Bloodline Power [data type]
- Claws [‘bloodline power’ data object]
- Dragon Resistances [‘bloodline power’ data object]
- Breath Weapon [‘bloodline power’ data object]
- statblock (save DC)
- Wings [‘bloodline power’ data object]
- Power of Wyrms [‘bloodline power’ data object]
- while there is a hierarchy to the Word styles used, it does not reflect on the data hierarchy. “Rage Power” and “Bloodline Power” are both data types, even though “Bloodline Power” is typically used inside a “Bloodline” (as shown above). There is really no fixed hierarchy to the objects, though there is to the data types. If it turns out convenient to have a “Magic Item” inside a “Monster” (not common, but not unheard of) you can do it.
- Word style hierarchy probably goes
- data type 1 >
- data object 1 >
- data section 1 >
- data type 2 >
- data object 2 >
- data section 2 >
- data type 3 >
- data object 3 >
- data section 3
- If I somehow really need more levels (class -> class feature -> class subfeature -> class subfeature power: sorcerer -> bloodline -> draconic bloodline -> claws) I’m probably making it difficult for myself. The ‘bloodline class feature’ can stay here (it identifies that the class has that feature), but the bloodline definitions can themselves be higher-level objects.
- It occurs to me that this even lets me have objects that are children of the same kind of object. For instance, ‘monster group’ and “magic item group” can now be nested. “Dragon” > “Chromatic Dragon” can now work, as can “Rod” and “Metamagic Rod” or “Wondrous Item” > “Figurine of Wondrous Power”.
- Word style hierarchy probably goes
I have a practice of indexing the game content pretty aggressively. I can include indexing rules in the data objects so that when they are referenced as types the objects can be indexed the way I want. This definitely includes the type markers I have in the index, but can also include hierarchy displayed in the index.
I expect that I will be able to create indexes something like:
- Curse, see Curse (affliction)
- Disease, see Disease (affliction)
- Poison, see Poison (affliction)
- Bloodline (class feature)
- Claws (bloodline power)
- Domain (class feature)
- Draconic (bloodline)
- Earth (domain)
- Electricity Resistance (domain power)
- Elemental (bloodline)
- Fey (bloodline)
- Lightning Arc (domain power)
- Undead (bloodline)
Parts of this will be pretty easy, a few might be a bit tricky… but I’ve solved harder problems.
The semi-arbitrary/semi-abstract nature of the data structure also makes it so I can give each object a unique identifier, as long as the object is defined in basically the same way twice.
Data objects have two identifiers. The first is the group-id and consists of the type and name (modified). The “Rage Power” class feature has group-id=”class-feature.rage-power”. Some (parent) data types can be marked to be appended to the end of their children. For instance, while “Rage Power” has group-id=”class-feature.rage-power”, when described in the barbarian class (“Class” data type says “append to children’s IDs”) that object has id=”class-feature.rage-power.barbarian”.
The dual IDs let me group the objects by group-id (they are the same type and same name, and therefore equivalent for search purposes) while keeping the individual instances separate. This is important as there is more overlap, because I can then refine the data: I can have a single master definition of “Uncanny Dodge” that explains what it does, and the class-specific entry is reduced to a note saying when that class gains access to it.
Didn’t plan to write this tonight, already had a “Y Day” post (and we were supposed to go judo but I’m having vehicle problems… again). I had some notes scribbled on the bus and I thought I’d try to get them into more readable form.
Strangely enough, I didn’t actually look at them. It seems the act of writing them was sufficient to cement them in my mind for later. Which is good, because I can’t read the damn things.