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

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:

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:

So what is the "this.name"? Well, each of these &lt: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:

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

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:

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.