|<--testing, part 3||^--xmld20--^||tables-->|
While reading about XSLT 2.0 I found out that it is exactly what I want. Not only does it let me define functions in XSLT, but it also allows me to assign node-sets (or, node sequences) to variables, which is something else I wish I had. Just that would allow me to simulate functions with templates.
There aren't many XSLT processors that deal with XSLT 2.0. So far, I've found three, but one was commercial. Of the remaining two, there are Saxon and Altova. Saxon is Java-only, and while that might be nice for coding at a later date, for now I want a command-line utility to process a given XML file with a given XSLT file.
Altova seems to work. The question now is whether I take advantage of XSLT 2.0, with the anticipation that general-purpose XSLT 2.0 libraries will be available when I'm ready to code applications, or if I stick to XSLT 1.0 to allow a larger audience to use the stylesheets.
So what should these libraries contain?
There are the common values needed (ability scores, or more likely the ability bonuses) for other calculations; there are end-result common calculations (saving throws, skill points), and perhaps the more obscure. Optimally, the XSL sheet should not know anything about the d20 rules; rather, it should only be concerned with laying out the data in another format.
I've hit a problem, though. I've tried using the document() XPath function, which lets you access data in another file. This was an attractive idea, because I wanted to keep tables (basically, the tables that the d20 system provides) separate from what a user of software might store in a file, which might include level-up information.
For instance, I attempted to move all of the skill information into tables, such as the following:
<Tables> <Skills> <Skill name="Appraise"/> <Skill name="Balance"/> <Skill name="Bluff"><SynergyTo name="Diplomacy">2</SynergyTo><SynergyTo name="Slight of Hand">2</SynergyTo><SynergyTo name="Intimidate">2</SynergyTo></Skill> <Skill name="Climb"/> <Skill name="Concentration"/> <Skill name="Craft"/> <Skill name="Decipher Script"/> <Skill name="Diplomacy"/> <Skill name="Disable Device"/> <Skill name="Disguise"/> <Skill name="Escape Artist"/> <Skill name="Forgery"/> <Skill name="Gather Information"/> <Skill name="Handle Animal"><SynergyTo name="Ride">2</SynergyTo></Skill> <Skill name="Heal"/> <Skill name="Hide"/> <Skill name="Intimidate"/> <Skill name="Jump"><SynergyTo name="Tumble">2</SynergyTo></Skill> <Skill name="Knowledge (arcana)"><SynergyTo name="Spellcraft">2</SynergyTo></Skill> <Skill name="Knowledge (architecture and engineering)"/> <Skill name="Knowledge (dungeoneering)"/> <Skill name="Knowledge (geography)"/> <Skill name="Knowledge (history)"/> <Skill name="Knowledge (local)"><SynergyTo name="Gather Information">2</SynergyTo></Skill> <Skill name="Knowledge (nature)"/> <Skill name="Knowledge (nobility and royalty)"><SynergyTo name="Diplomacy">2</SynergyTo></Skill> <Skill name="Knowledge (religion)"/> <Skill name="Knowledge (the planes)"/> <Skill name="Listen"/> <Skill name="Move Silently"/> <Skill name="Open Lock"/> <Skill name="Perform"/> <Skill name="Profession"/> <Skill name="Ride"/> <Skill name="Search"/> <Skill name="Sense Motive"><SynergyTo name="Diplomacy">2</SynergyTo></Skill> <Skill name="Sleight of Hand"/> <Skill name="Speak Language"/> <Skill name="Spellcraft"/> <Skill name="Spot"/> <Skill name="Survival"><SynergyTo name="Knowledge (nature)">2</SynergyTo></Skill> <Skill name="Swim"/> <Skill name="Tumble"><SynergyTo name="Balance">2</SynergyTo><SynergyTo name="Jump">2</SynergyTo></Skill> <Skill name="Use Magic Device"/> <Skill name="Use Rope"/> </Skills> </Tables>Note how I use a SynergyTo subelement to denote that a skill gives another skill a bonus, provided you have enough ranks (all skills that I know of that give synergy bonuses must have 5 ranks, but that would be a value used by default in a schema, with a RanksNeeded option available as well).
Unfortunately, I hit a wall. When you use the document() function, it changes your current node, or rather, your current file. This is a problem, because as I'm looking through the list of synergy bonuses, I need to refer back to the original document to see how many ranks the monster or character has, to determine whether I should now use the SynergyTo value in the skill calculation. Because the context is now in the d20tables.xml file (the above is an excerpt of that), I've lost access to my original input file.
I've tried some workarounds, such as forcing the user to pass in the input file as a parameter (all XSLT processors I've used support this), which allows us to set a global variable that lets us back into our original data file.
That's all well and good, but we've also lost context in which we were processing. For instance, our monster XML file had <Monsters>, with <Monster>s inside that. When we're calculating skill points, we're currently processing a specific <Monster>, and we've forgotten which one.
Now, I can also try to put the monster's name into a variable, but that means that that variable has to be propagated to all calls (such as strbonus. Instead, I've decided to require that both the file name and the entry name (in this case, "srd:PIT FIEND") are passed as parameters.
This has the obvious limitation that you can only process on monster at a time. Really, though, this is what most uses will be for: you're generating a monster manual page; you're generating a stat block. This means that if you were looking to translate a file of monsters from this format to another (say, d20-xml), then you'd have to iterate over every entry id to get them all.
I'm okay with this. We've been playing around with the idea of turning the monsters into monster classes, breaking up their skill and ability distribution into levels similar to that of a regular class or prestige class. There are guidelines in the Savage Species book for doing this, and if we can come up with some sort of scoring system for all of the various gains that can be received during levelling up as a monster, we can come up with some workable progression tables.
Who are we to make this up, you might ask? Well, for the most part, people are going to use monsters as they appear in the Monster Manual (or SRD), so once they have a fully levelled-up pit fiend, it's going to have the same abilities in the end, regardless of the progression scheme we derive, so it won't matter that we decided to give it a Strength boost here or its fly speed there.
This also works well with the eventual plan of this system being able to treat everything as classes, and allows players (or DMs with NPCs) to make partial monsters -- a pit fiend that has only reached 10HD instead of its full 18HD.
A lot of work? You bet. The nice thing is that for the time being, as long as the progression charts get the monster to its full ability by its highest level, it doesn't matter how we distribute it, so we can cheat for the time being. If we say that a pit fiend has an ECL of 28 (which is what we've decided for now, since the Monster Manual and SRD aren't helpful in this regard), then we can -- for the time being -- say that they get nothing from level 1-27, and at 28th level they get all of their abilities. Alternately, we could say that they get it all at 1st and nothing for the rest of its levelling up. In the end, the system will dump out a pit fiend with all of the abilities we'd expect.
|<--testing, part 3||^--xmld20--^||tables-->|