<--Refactor ^--xmld20--^ Version 4-->

xmld20 - an XML Schema for d20 gaming systems - refactor, continued

Ouch.

Development on Xscr has been slow, mainly because of a lack of time, but even now that I've found a bit of time, I've really realized that a general-purpose XML scripting language is not going to cut it. As I work on Xscr (or rather, as I now put it aside as a secondary project, because I'd rather work on xmld20), I realize that a general-purpose XML scripting language would make xmld20 just way too wordy. I don't want xmld20 designers to have to say "look for an XML item that is named 'Feat', which has an attribute called 'name' that has the value 'weapon finesse', and then..." when really we want to be able to say "If they have the weapon finesse feat, then...". At first, I had thought that I would write a secondary "friendly" scripting language for xmld20 that would convert to Xscr, but I now wonder if that's worth it.

I think it comes down to how I want to code to work, and what the result should be. At the core, there are certain calculations that need to get done, such as total ability score, attack information (per attack), saves, etc. These are basic concepts, values that are needed for character sheet display, stat blocks, and even computer-driven gameplay. The core code needs to be able to "ask" the character sheet and the datasets to compute or otherwise gather the relevant information, since we've deemed hardcoding is just out of the question. So, how should this result or relevant information be provided?

In the most recent implementation of xmld20, we had the idea that a weapon could inherit properties from a "super" version of itself, so a "+1 longsword of death" would go get the basics from the longsword (such as damage type, handedness, proficiency, etc.) and then add or replace certain tags within to change it to the updated version. I think something similar is what we might want the script code to do: to modify a block of numbers based on what it knows. For example, to find out a character's strength score, we know we should find out their rolled score (or point-bought score), any racial modifiers, any class modifiers, any equipment modifiers, level-up modifiers, etc. So perhaps the core code starts off with

	<Ability name="strength">
	</Ability>
and then calls out to all of the "objects" associated with the character to add their opinion. These "objects" would be some built-in things like the "stat roll" and the "level up", as well as dynamic objects that were added later, such as "race", "class(es)", "equipment", etc. The core code would enumerate all of these objects and iteratively pass them the above record, asking them to modify it as they feel they need to. Whether the core code first determines whether an object has any effect on this calculation (such as the <ToHitCalc> idea I mentioned above), or whether the core just tells every single object "look here, I'm figuring out the strength ability, and here's the result so far -- do what you want, if anything, to it, and pass it back" where most objects might ignore the request and return the result unmodified (such as a longsword or the Toughness feat might), is just an implementation issue.

So, let's say the code first passes this empty block to the "stat roll" object (which is a good place to start -- whether there should be some way of saying that this MUST be first is something we might have to consider later), which would return something like

	<Ability name="strength">
		<Base>16</Base>
	<Ability>
Then the "level up" object might get that result, and, seeing that the character did indeed decide to increase strength at levels 4 and 8, returns
	<Ability name="strength">
		<Base>16</Base>
		<Bonus bonustype="inherent">2</Bonus>
	<Ability>
(I'm pretty sure that's what the level-up bonus is considered -- I'll definitely have to check that!) Then maybe the "race" object gets it next:
	<Ability name="strength">
		<Base>16</Base>
		<Bonus bonustype="inherent">2</Bonus>
		<Bonus bonustype="racial">2</Bonus>
	<Ability>
and then maybe the "class" object(s):
	<Ability name="strength">
		<Base>16</Base>
		<Bonus bonustype="inherent">2</Bonus>
		<Bonus bonustype="racial">2</Bonus>
		<Bonus bonustype="class">2</Bonus>
	<Ability>
Okay, I don't know what the bonus from something like a monster class should be considered, though it's probably racial. Now the "equipment" objects all get a turn:
	<Ability name="strength">
		<Base>16</Base>
		<Bonus bonustype="inherent">2</Bonus>
		<Bonus bonustype="racial">2</Bonus>
		<Bonus bonustype="class">2</Bonus>
		<Bonus bonustype="enhancement">2</Bonus>
	<Ability>
That might be from our gloves of ogre power +2. And maybe the "buffs" objects now chime in:
	<Ability name="strength">
		<Base>16</Base>
		<Bonus bonustype="inherent">2</Bonus>
		<Bonus bonustype="racial">2</Bonus>
		<Bonus bonustype="class">2</Bonus>
		<Bonus bonustype="enhancement">2</Bonus>
		<Bonus bonustype="enhancement">4</Bonus>
	<Ability>
Oh look, maybe we have Bull's Strength cast as well. Assuming that's everything, the core code would get back the above data, and would then have to work with it. The general rules of d20 are that different bonus types stack, but like ones generally don't. So from the above, the core code should be able to figure that the strength is the base 16 plus 2 and 2 and 2 and 4 (taking the best of the two enhancement bonuses), to return 26. The code didn't have to know anything about level ups, or racial choices, or class choices, or which items to check for, or which buffs to look for -- it let these objects all add their own opinion.

Okay, it all sounds great, but what should it look like at the other end, where these values got added in? For a first stab at it, let's assume that all of these "objects" have something like

	<Code calc="Ability">
	...
	</Code>
The ... code inside is going to require two things: the work we've done so far on figuring out this Ability (the data block above), as well as access to the whole character. This is necessary for logic like we saw in the made-up feat, where decisions might have to be made; this strength calculation doesn't really show this, since all of the contributions to the calculation were unconditional.

We already support some of the actions needed from the current version of xmld20. Currently, each level of a class has a list of actions that it performs on the character XML as we load in the character. Instead, we will move a lot of these instructions into functions based on the calculation being done. For instance, the astral deva at level one had the following:

	<Level number="1">
		<Add type="Subtype">good</Add>
		<Increase type="HD" subtype="astral deva" racial="yes">1</Increase>
		<Set type="SpecialAbility" subtype="astral deva" name="spell-like abilities" class="cleric" casterlevel="1" ability="charisma"/>
		<Set type="ShortText" name="spell-like abilities"/>
		<Increase type="BAB">1</Increase>
		<Increase type="SavingThrow" name="fortitude">2</Increase>
		<Increase type="SavingThrow" name="reflex">2</Increase>
		<Increase type="SavingThrow" name="will">2</Increase>
		<Choice type="Skill">8</Choice>
		<Increase type="AC" name="natural">3</Increase>

		<Increase type="CR">1</Increase>
	</Level>
These would add or modify the current character sheet that we had built up. Instead, we now want to separate all of these features into the appropriate calculations. The idea of "calculation" might need to be stretched a little; for example, the first thing we see above is the addition of the Good subtype. Since the type and subtype of a creature or character is something that needs to be figured out based on different factors, we can call this a "calculation", and have something like:
	<Code calc="Type">
		<Add type="Subtype">good</Add>
	</Code>
This should be the only "calculation" in which this is necessary, since any other calculation that might care would depend on the result of the "Type" calculation. Let's see what else we might have:
	<Code calc="HD">
		<Increase type="HD" subtype="astral deva" racial="yes">1</Increase>
	</Code>
So far, we're still using the same general format that we used before... until we see a problem with it, we shall continue to do this. So the returned record would be
	<HD>
		<HD subtype="astral deva" racial="yes">1</HD>
	</HD>
This looks a bit weird... perhaps the record should be called "hp" or "HitDice" or something... semantics. We'll worry about it later.

Saves are done similarly:

	<Code calc="Save">
		<If this.name="fortitude">
			<Increase type="SavingThrow" name="fortitude">2</Increase>
		</If>
		<If this.name="reflex">
			<Increase type="SavingThrow" name="reflex">2</Increase>
		</If>
		<If this.name="will">
			<Increase type="SavingThrow" name="will">2</Increase>
		</If>
	</Code>
So what is the "this.name"? Well, each of these <:Code> blocks is going to be passed in two things -- the character as a whole, and the current record that we're calculating. The "this" refers to the record, and we'll have to come up with something for the character, such as... well, I don't know yet. I'll worry about it when I need to use it. Of course, the <If>-handling code needs to know how to handle these special attributes, as the existing <If> code just looks only in the character sheet, and only for specific elements. The "this.name". then, would match the "name" attribute of the current <Save> record -- if you recall above, we used a similar idea for the <Ability> record above when dealing with Strength.

The BAB is easy enough:

	<Code calc="BAB">
		<Increase type="BAB">1</Increase>
	</Code>
Again, this is going to end up with some redundant elements, a <BAB> inside a <BAB>, and we might now want to keep track of the different modifiers to the BAB, for whatever reason... I think the ability to mouseover a BAB total and see the breakdown can be a desireable ability, and would be supportable if each of the contributions to it were kept apart. So let's change that to:
	<Code calc="BAB">
		<Increase type="BAB" subtype="astral deva">1</Increase>
	</Code>
Skills used to be represented only as a <Choice> that could be read by a character-creator tool, showing that this requires "answers" from the character sheet on which were chosen. This is still a "calculation", so we might have
	<Code calc="Skills">
		<Add type="Skill" subtype="astral deva">7<Add>
	</Code>
This is a little different, but let's think it through. Because this is a "calculation", what we need to return as a "result" is a list of the skills that were either chosen (if this is a specific character), or what skills are to be chosen, in the future. This latter idea needs to understand the order that these skills were provided. Why? Because if a character takes a level of, say, rogue, then from now on, the rogue skills are class skills, even if the character takes other classes. This means that, if the character then takes a level of wizard, the skill points gained from that wizard level can be spent on wizard class skills, of course, but can also buy rogue class skills at class skill prices. Of course, the skills that aren't class skills for either rogue or wizard can still be bought as cross-class skills. By using <Add> each time, we can show in the record the order in which the skill choices were granted, and thus we know when and what are the class and cross-class skills.

Only a few more things from that level 1 of astral deva, so let's do the natural armor:

	<Code calc="AC">
		<Increase type="AC" subtype="astral deva" name="natural">3<Increase>
	</Code>
Note we added in the astral deva as the source, so again we can display a breakdown of the AC if really wanted. We have to watch this, though; AC is another instance where the "bonus" is something that doesn't necessarily stack, so if this character wore an amulet of natural armor +1, it should not be able to stack with the natural armor gained as a class. This is something we'll have to return to -- the idea of stackable and non-stackable -- a little later.

Let's finish off with the CR

	<Code calc="CR">
		<Increase type="CR" subtype="astral deva">1</Increase>
	</Code>
and finally the SpecialAbilities:
	<Code calc="SpecialAbilities">
		<Set type="SpecialAbility" subtype="astral deva" name="spell-like abilities" class="cleric" casterlevel="1" ability="charisma"/>		
	</Code>
All together, we now have this for the first level of the astral deva monster class:
<Level number="1">
	<Code calc="Type">
		<Add type="Subtype">good</Add>
	</Code>
	<Code calc="HD">
		<Increase type="HD" subtype="astral deva" racial="yes">1</Increase>
	</Code>
	<Code calc="Save">
		<If this.name="fortitude">
			<Increase type="SavingThrow" name="fortitude">2</Increase>
		</If>
		<If this.name="reflex">
			<Increase type="SavingThrow" name="reflex">2</Increase>
		</If>
		<If this.name="will">
			<Increase type="SavingThrow" name="will">2</Increase>
		</If>
	</Code>
	<Code calc="BAB">
		<Increase type="BAB" subtype="astral deva">1</Increase>
	</Code>
	<Code calc="Skills">
		<Add type="Skill" subtype="astral deva">7<Add>
	</Code>
	<Code calc="AC">
		<Increase type="AC" subtype="astral deva" name="natural">3<Increase>
	</Code>
	<Code calc="CR">
		<Increase type="CR" subtype="astral deva">1</Increase>
	</Code>
	<Code calc="SpecialAbilities">
		<Set type="SpecialAbility" subtype="astral deva" name="spell-like abilities" class="cleric" casterlevel="1" ability="charisma"/>
	</Code>
</Level>
This is a bit wordier than the older definition of level 1, but it's certainly more versatile. With this basis, I'm going to start the new xmld20 library -- version 4.
<--Refactor ^--xmld20--^ Version 4-->
©2002-2017 Wayne Pearson