<--Rethink, part 4 ^--xmld20--^ Status-->

xmld20 - an XML Schema for d20 gaming systems - rethink, part 5

Now that we've gotten three classes written up, and a sample character (Placide), I think it's time that I try writing code that can access both the character sheet and the class tables, and can output a coherent character. From that point, we can modify the older code as needed to handle our new layout.

The first thing I want to address is keeping classes in separate files. In the previous incarnation, I had the whole Monster Manual (well, the entries I had made) in one large file. This is a problem when using a build system that checks for file modification, because a change to one monster entry caused them all to be rebuilt.

Under XSLT 1.0, we couldn't return nodesets from template calls, so we couldn't have a routine that could build a full set of monsters, or classes, or characters, from a list of external files. Now that we've moved to XSLT 2.0, however, we can do the following:

<xsl:variable name="classes" select="xmld20:loadClasses('classlist.xml')"/>

<xsl:function name="xmld20:loadClasses">
<xsl:param name="classlistfile"/>
	<xsl:copy-of select="document(document($classlistfile)//ClassFiles/ClassFile)//Class"/>
</xsl:function>

<xsl:function name="xmld20:getClass">
<xsl:param name="classname"/>
	<xsl:copy-of select="$classes[@name=$classname]"/>
</xsl:function>
The classlist.xml file contains
<ClassFiles>
	<ClassFile>barbarian.xml</ClassFile>
	<ClassFile>bard.xml</ClassFile>
	<ClassFile>fighter.xml</ClassFile>
</ClassFiles>
The end result is that $classes contains all three classes, accessible with the getClass function to find the required entry. We can use the same strategy with character sheets, spells, tables, feats... this allows for a drop-in strategy for new datasets, where the only changes that would have to be made are an addition to the classlist.xml file, the spelllist.xml file, etc. And of course, those filenames will eventually be passable as arguments to the XSLT system instead of hardcoded.

Note that here, I'm loading all of the classes into one big nodeset, even ones that I may not use. This is perhaps wasteful, and eventually the getClass function might just search through the listed class files instead of pre-loading and searching the result. Depending on the application, we may end up calling getClass or getSpell multiple times, however, and unless we also implement caching, this will get expensive. The current method, while loading class and spells and whatnot that might never be used, ensures faster access at the expense of resources; memory versus time. I'm fine with that for now.

I've also got a similar getCharacter function, which for now only returns the character sheet. Remember that this really only expresses the choices made by the player, and not a full version of the character. This isn't likely what we want, which means the "meat" of converting character sheets (and class tables) into meaningful characters should be done here.

Let's look at Placide again:

<Characters>
  <Character>
    <Bio>
	<Name>Placide deMorgan</Name>
	<Race>human</Race>
	<Player>Wayne Pearson</Player>
    </Bio>
    <Levels>
	<Level number="0">
		<Set type="Ability" name="strength">12</Set>
		<Set type="Ability" name="intelligence">10</Set>
		<Set type="Ability" name="dexterity">14</Set>
		<Set type="Ability" name="constitution">14</Set>
		<Set type="Ability" name="wisdom">12</Set>
		<Set type="Ability" name="charisma">12</Set>
	</Level>
	<Level number="1" class="fighter">
		<Increase type="Skill" name="climb">4</Increase>
		<Increase type="Skill" name="jump">4</Increase>
		<Increase type="Skill" name="swim">4</Increase>
		<Add type="Feat" name="cleave"/>
		<Add type="Feat" name="dodge"/>
		<Add type="Feat" name="power attack"/>
		<Increase type="hp">10</Increase>
	</Level>
	<Level number="2" class="fighter">
		<Increase type="Skill" name="climb">1</Increase>
		<Increase type="Skill" name="jump">1</Increase>
		<Increase type="Skill" name="swim">1</Increase>
		<Add type="Feat" name="mobility"/>
		<Increase type="hp">4</Increase>
	</Level>
    </Levels>
  </Character>
</Characters>
The heart of what we need to do is iterate over the Level blocks, applying these player choices to the information in the accompanying class progressions. One thing we need to remember, however, is that the character sheet represents a character that has already gone through its levelling up, so there is going to be some redundant information in the class progression table. For instance, the fighter's first few levels look like this:
<Class name="fighter">
  <Levels>
    <Level number="1">
	<Increase type="HD">10</Increase>
	<Increase type="BAB">1</Increase>
	<Increase type="SavingThrow" name="fortitude">2</Increase>
	<Increase type="Skill">2</Increase>
	<Add type="Feat" subtype="fighter"/>
    </Level>
    <Level number="2">
	<Increase type="HD">10</Increase>
	<Increase type="BAB">1</Increase>
	<Increase type="SavingThrow" name="fortitude">1</Increase>
	<Increase type="Skill">2</Increase>
	<Add type="Feat" subtype="fighter"/>
    </Level>
    <Level number="3">
	<Increase type="HD">10</Increase>
	<Increase type="BAB">1</Increase>
	<Increase type="SavingThrow" name="reflex">1</Increase>
	<Increase type="SavingThrow" name="will">1</Increase>
	<Increase type="Skill">2</Increase>
    </Level>
  </Levels>
</Class>
The <Increase type="HD">10</Increase> is rubbing me the wrong way. Really, we're not increasing the HD by 10, we're increasing it by 1, and that one is a d10. We need to keep track of which HD was gained, and how many, but also need to keep track of what was rolled at level-up time, if a character, and otherwise need to use the average roll, if this is a monster entry. Perhaps something like this?
	<Increase type="HD" subtype="fighter" name="10">1</Increase>
The reason we want to subtype this is because character or monster entries that have HD from multiple sources, even when the same HD size, display them separately, such as
	hp 42 (4d10+2d8+2d10)

checking

How much checking should the code do to ensure that what the character sheet has matches what the progression table says it should have? I say none.

Checks of this nature are part of the code that created the character sheet, so it should have ensured that the number of skill points listed in the character sheet match what the level progression says. At this point, the

	&Increase type="Skill">2</Increase>
should be ignored as a "variable" feature, something that should have been sorted out already, and verified. If we're worried about hand-editing, then we might write a verifier another time, but I don't believe it should be inherent to the codebase.

problem

A little while ago, we talked about perhaps having some conditional data, such as
	<Add type="Resist" name="acid">5
		<Unless type="Immune" name="acid"/>
	</Add>
In this instance, we decided that we'd keep these values around anyway, and that it was up to the processing code to know how to deal with Resist and Immunity for the same type.

Another case has come up, however, that isn't so easy to fix. The ranger class has the idea of a Combat Style, which is chosen at 2nd level. The style that is chosen affects the abilities gained later on, at 6th and 11th level. For instance, if the archery combat style is taken, then they get Rapid Shot at 2nd level, Manyshot at 6th, and Improved Precise Shot at 11th. If the two-weapon combat style is taken, then they get Two-Weapon Fighting, then Improved Two-Weapon Fighting, and finally Greater Two-Weapon Fighting.

We could say that the code, when looking for appropriate feats, might check if the character has ranger levels, look up the combat style, and figure out which feats the character must have. This has a few problems, including making feat lookup ugly, and hardcoding a specific class's features. What if we need other choices?

The rogue, when it hits higher levels, gets access to Special Abilities. Starting at 10th level, the rogue can choose an ability from a list, and then choose another every three levels after. This is a bit trickier that feats, which are chosen from all available (or perhaps from a subtype of "fighter" or "metamagic"), because they're specific abilities, not feats, and because most, but not all of them can only be chosen once.

This is going to make our data ugly, but I think we have to resign ourselves to the fact that it needs to be done. In fact, I see it as a bit of a blessing, because of some of the code that I've written, and the way in which it is being handled.

If you recall, we supported the addition of explicit feats using something like this:

	<Add type="Feat" name="dodge"/>
and we noted the choice of feats like this:
	<Add type="Feat" subtype="fighter"/>
With this setup, we'd have to have code that understands that the second example doesn't fully detail a complete gain, so at level-up time the user must be asked which feat is gained, and at display time this entry must be ignored, because the data is kept in the character sheet (which, remember, is used to store player choices only).

While I had initially said that that was the plan, I don't really like it. I think that a Choice should be noted more clearly, so it can be more readable in the class description, and of course to make the code easier to write. Thus, I think this last entry should now become

	<Choice type="Feat" subtype="fighter"/>
And skill gains? They, too, require choice, but we also need to note that they're not Adds, but Increases. Don't we? Or is the presence of data in the element enough, as seen here?
	<Choice type="Skill">6</Choose>
The character levelling code knows the rules for max ranks and cross-class skills, so can enforce those. This is a marker only to character-generating code, and would be ignored by character-displaying code.

So what about the ranger and the rogue? The rogue can be handled with something like this:

	<Choices count="1">
		<Choice type="SpecialAbility" type="rogue" name="crippling strike"/>
		<Choice type="SpecialAbility" type="rogue" name="defensive roll"/>
		<Choice type="SpecialAbility" type="rogue" name="improved evasion"/>
		<Choice type="SpecialAbility" type="rogue" name="opportunist"/>
		<Choice type="SpecialAbility" type="rogue" name="skill mastery"/>
		<Choice type="SpecialAbility" type="rogue" name="slippery mind"/>
		<Choice type="Feat"/>
	</Choices>
This takes care of the extra hiccup that the Special Ability can just be another feat. And what about the fact that you can't (or at least, it doesn't make sense to) take crippling strike twice, or defensive roll twice, but you can certainly take skill mastery twice? This is something that we leave to the tables, that we mentioned before (The SpecialAbilityTypes tables that would also keep track of whether an ability was supernatural or extraordinary, etc.).

And the ranger? It's still not as easy as we've seen, because it's not really a choice; yes, the 2nd level has the choice of either archery or two-weapon fighting as a focus, but after that, the gains are fixed based on that original choice.

	<Level number="2">
		<Choices count="1">
			<Choice type="Feat" subtype="ranger" name="rapid shot"/>
			<Choice type="Feat" subtype="ranger" name="two-weapon fighting"/>
		</Choices>
	</Level>
	...
	<Level number="6">
		<If type="Feat" subtype="ranger" name="rapid shot">
			<Add type="Feat" subtype="ranger" name="manyshot"/>
		</If>
		<If type="Feat" subtype="ranger" name="two-weapon fighting">
			<Add type="Feat" subtype="ranger" name="improved two-weapon fighting"/>
		</If>
	</Level>
	<Level number="10">
		<If type="Feat" subtype="ranger" name="rapid shot">
			<Add type="Feat" subtype="ranger" name="improved precise shot"/>
		</If>
		<If type="Feat" subtype="ranger" name="two-weapon fighting">
			<Add type="Feat" subtype="ranger" name="greater two-weapon fighting"/>
		</If>
	</Level>
The If element must be processed at both character-generation time and character-display time, since it can, in theory, embed both Choice and Add elements, elements that each need to be processed at different times.

On the surface, this doesn't look so bad. Yet. The If is simply checking for existence of a value. Can we keep it to this? Can we keep it from needing binary operations ("if we have this AND this")? We'll have to see as we go further.

For now, I'm going to go and change the existing entries to conform to these new changes, and then change the processing code.
<--Rethink, part 4 ^--xmld20--^ Status-->

©2002-2017 Wayne Pearson