Open Source d20 API/Engine

nopantsyet said:
Re: conditional modifiers

Yeah, that's one part I'm still thinking a lot about. How to capture the conditions (both character and game) that trigger certain rules and rule changes.

I've been discussing how to handle this this on the other thread (the one linked to earlier by JimAde I think).
 

log in or register to remove this ad

Here's an example of how I would model the "+1 vs goblinoids" so it can be resolved by an engine. This is off the top of my head and probably won't withstand closer analysis.
First, we'll need to define a goblinoid. Note that this could actually be an anonymous class defined as part of the bonus rather than a named class as in the example.
If something has creature subtype goblin and is a humanoid, we're calling it a goblinoid. A more accurate definition would use oneOf to enumerate the full list of subtypes IIRC.

Code:
<owl:Class rdf:ID="http://example.org/srd#goblinoid">
  <owl:intersectionOf rdf:parseType="Collection">
    <owl:Class rdf:about="http://example.org/srd#humanoid">
    <owl:Restriction>
      <owl:onProperty rdf:resource="http://example.org/core#creatureSubtype" />
      <owl:hasValue rdf:resource="http://example.org/srd#goblin" />
    </owl:Restriction>
  </owl:intersectionOf>
</owl:Class>

Now, let's define the bonus. It's anonymous as it will typically be defined as part of some other rule, such as a racial trait.

Code:
<core:Bonus>
	<core:appliesTo resource="http://example.org/core#attack" />
	<core:type resource="http://example.org/srd#racial" />
	<core:Condition>
		<rdfs:label>vs goblinoids</rdfs:label>
		<core:conditionType rdf:resource="http://example.org/core#runtime" />
		<srd:goblinoid>
			<runtime:Role rdf:about="http://example.org/core#target" />
		</srd:goblinoid>
	</core:Condition>
	<core:formula>1</core:formula>
</core:Bonus>

So, at runtime, when a creature of subtype goblinoid is assigned the "target" role relative to the character, the formula is evaulated and applied to the attack roll subject to stacking rules for bonus types.
Here, I'm telling the processor this condition can be checked at runtime. If, at that time, there is a goblinoid with the 'target' role, apply this bonus.
Some conditions can realistically only be evaluated by a DM (see the preqs for assassins, for example). For those I would stick with a simple text description like my original example.
Now, this is a lot of work, so I'm sticking to a small, well-defined ontology to begin with (stuff in the "core" namespace, above), and building tools to extend the ontologies on top of that. So all the stuff in the SRD namespace would be the result of using my BookBuilder application. Definitions like goblinoid can be built by example rather than typed out (I haven't even thought about the book builder UI yet).
 

I know some people have objected to the idea of the history on the basis that they don't want to worry about strict level by level compatibility or force developers to do so. I don't think it would necessarily do so. Different developers could do it different ways, but I can see either using a little AI to assign things to levels to come up with a legal character behind the scenes or just writing tags in the history that basically say "character built at {X} level and this value was assigned". Other programs wouldn't be able to make sense of it for down-leveling purposes but as long as they add up the points it would still work.

I also understand there are some wierd instances like the stat decrease example. I would think that a program that wanted to handle this by allowing people to decrease a different stat would be able to just ask the user which stat to decrease and then apply a "decreased stat when downgraded to level {X} " tag. It's a history, after all. It should keep a history of all the changes made to the character and keeping track of a strict progression from 1st to 20th level is only one possible application.

I suppose I just see a lot of these things being a function of the program and programmer manipulating the data properly.
 

I think I'm swayed to making history optional. I've been trying to formulate a good argument against it because I felt pretty strongly it should be mandatory, but nothing has held up to my own scrutiny and I'm out of arguments.

JBowtie - I like the direction you're going with this. Have you read the other thread, about implementing the core mechanic? I'm curious what your thoughts are on Ardargor's approach of deconstructing his existing d20 schema into an ontology using Protégé.
 

nopantsyet said:
Yeah, I see your point in that context. Just as easy to work backwards as forward. I'm fine with the idea of storing the score rather than the base, and separating the aggregate to work backwards.

I was actually thinking Python since it's appropriate for semantic processing and it's easy to embed. But if some functional XML syntax is capable of providing the same level of functionality without embedding script, I'm liking that.

I decided on a XML for a couple of reasons:

- It's extremely simple and intuitive for the common user to read and fiddle with.
- No dependency issues (other than XML libraries which can hardly be called dependencies nowadays)
- Doesn't foster argument over language issues ("This architecture would be great if they had used Java/Perl/Python/whatever")
- It creates only a single learning curve. Once you get into editing data files (such as a character), it's a natural progression to edit rules files. Also simplifies the architecture a little (though this isn't that big a deal with the libraries available).
- Licensing free. I didn't want to get into the muddled mess of using GPL/CNRI (Python is CNRI, most language libraries are GPL) code. I just find it easier to deal with freely available closed libraries, which, in my experience, are better at maintaining backwards compatibility and packaging support. I usually try to work with the BSD license, which is how I planned on licensing mine.
- Language agnostic. While one could write a Perl application that uses Python script (for example), one would find that by using Python script, you would attract a lot of Python developers and few others.
- Extensible language. No language (other than Lisp) is extensible like XML. In fact, it can be said with more than a grain of truth, that any language that is as extensible as Lisp is itself a dialect of Lisp. With that in mind, I chose to design the XML language using Lisp principles (they parse in a similar fashion, so it's quite intuitive).

That said, I primarily develop in PHP and .NET. Using .NET for the application-level I think is the way to go so that people have language choices (C#, VB, C++, Perl, Python, PHP, Fortran, COBOL and many others all have .NET versions). Also, of the 3 major platforms (Wintel, Linux on Intel, and Mac), only Java Bytecode has better cross-platform support and user penetration than MSIL/Mono,but it sticks you with a single language.
 

JBowtie said:
Here's an example of how I would model the "+1 vs goblinoids" so it can be resolved by an engine. This is off the top of my head and probably won't withstand closer analysis.
First, we'll need to define a goblinoid. Note that this could actually be an anonymous class defined as part of the bonus rather than a named class as in the example.
If something has creature subtype goblin and is a humanoid, we're calling it a goblinoid. A more accurate definition would use oneOf to enumerate the full list of subtypes IIRC.

For comparison, my methodology follows:

Code:
<!--
	target and mod are variables passed by the attack event
	e is equals function
	I should also point out that the parser doesn't need to understand and, e, and if
	as they can be described with the simpler assert function in a library
-->
<creature-attack-bonus attach="attack" when="before" type="@" subtype="@" bonus="0">
	<assert>
		<and>
			<is-set>type</is-set>
			<e><a>type</a><a>target.type</a></e>
		</and>
		<if>
			<is-set>subtype</is-set>
			<assert>
				<e><a>target.subtype</a><a>subtype</a></e>
				<set name="mod"><add><a>mod</a><a>bonus</a></add></set>
			</assert>
			<set name="mod"><add><a>mod</a><a>bonus</a></add></set>
		</if>
	</assert>
</creature-attack-bonus>

In the dwarf creature all you have to do is:
Code:
<monster name="dwarf">
	<creature-attack-bonus type="humanoid" subtype="goblinoid" bonus="1" />
	<creature-attack-bonus type="humanoid" subtype="orc" bonus="1" />
</monster>
<monster name="gnome">
	<creature-attack-bonus type="humanoid" subtype="kobold" bonus="1" />
	<creature-attack-bonus type="humanois" subtype="goblinoid" bonus="1" />
</monster>

You can use the same function for Ranger favored enemy bonuses as they are chosen.
 
Last edited:

MaxKaladin said:
I know some people have objected to the idea of the history on the basis that they don't want to worry about strict level by level compatibility or force developers to do so. I don't think it would necessarily do so. Different developers could do it different ways, but I can see either using a little AI to assign things to levels to come up with a legal character behind the scenes or just writing tags in the history that basically say "character built at {X} level and this value was assigned". Other programs wouldn't be able to make sense of it for down-leveling purposes but as long as they add up the points it would still work.

I disagree with it on the basis that it kills interoperability. The main framework for defining a character should be as simple as possible so more clients can be created to use it. Forcing history to be kept (as you do if you aggregate the final value) raises the bar for creating compatible application dramatically.

The way I currently have it worked out is

Code:
<!--half-orc example-->
<character>
	<strength>12
		<modifier mod="2" source="race" />
	</strength>
	<dexterity>10</dexterity>
	<constitution>10</constitution>
	<intelligence>8
		<modifier mod="-2" source="race" />
	</intelligence>
	<wisdom>10</wisdom>
	<charisma>8
		<modifier mod="2" source="race" />
	</charisma>

Another app would simply read the first text element (I'm using the wrong term here) found in the strength element and would be fine.

I also understand there are some wierd instances like the stat decrease example. I would think that a program that wanted to handle this by allowing people to decrease a different stat would be able to just ask the user which stat to decrease and then apply a "decreased stat when downgraded to level {X} " tag. It's a history, after all. It should keep a history of all the changes made to the character and keeping track of a strict progression from 1st to 20th level is only one possible application.

My example allows that, but doesn't force it.

I suppose I just see a lot of these things being a function of the program and programmer manipulating the data properly.

Shouldn't force the programmer (or the program for that matter) to know the rules. It makes things difficult to change and update.
 

Just ran into a legal snag. Ability score generation is not open content. Unless I'm missing something. The Elite Array is in there, so you could use that. Or you could distribute 63 points evenly (giving the average) or some other method. But 4d6, drop lowest, arrange is nowhere to be found.

In the meantime, I'm going to put in ability score generation as a library function that can be removed later easily and isn't part of the core document.
 

Well, this is what I've accomplished since this discussion began (though a lot of it has been stewing in my head for quite some time).

Just wanted to post this up in case anyone wanted to take a look. This is the beginning documentation for what I call RPGML (RPG Markup Language). It includes most of the core functions that an interpreter must understand (it's missing the ones related to adding and deleting XML tags from a dataset). It should give you a more formal idea of what I've been blathering about in random spurts of XML. I've already coded a library of a dozen or so helper functions in this language to facilitate easier use, but I haven't had the time to document them.

Some of the helper functions include (C/Java equivilents with my function names in parentheses):

>= operator (gte)
<= operator (lte)
?: operator (if)
&& operator (and)
|| operator (or)
A simple switch statement (supports different branches on <, =, >) (compare)
A function that takes a number and normalizes it to a certain range (range)

In addition, I have the following rules coded up in RPGML:

The core mechanic.
Ability score modifiers.
Bonus spells based for high abilties.
A generic roll function that will take any-sided die with any modifier.
A function for each die (d6 function, d8 function, etc.).
Racial ability score modifiers.
Human bonus feat and skills.
Most Dwarven racial abilties (Stability was a pain, but I think I have the issue resolved now).

As you might imagine, I'm steadily moving my way directly through the SRD.

I currently left off on the racial rules to work on the core create-character function so that the engine knows what steps need to be taken (for instance apply race stuff AFTER ability scores) and I can work out how the language tells the app it needs input from an end user.
 

Attachments


reanjr said:
The way I currently have it worked out is

Code:
<!--half-orc example-->
<character>
	<strength>12
		<modifier mod="2" source="race" />
	</strength>
	<dexterity>10</dexterity>
	<constitution>10</constitution>
	<intelligence>8
		<modifier mod="-2" source="race" />
	</intelligence>
	<wisdom>10</wisdom>
	<charisma>8
		<modifier mod="2" source="race" />
	</charisma>
The thing is that history could easily be included in this. I said that I didn't think it should include calculated number or, at least, only calculated numbers. If we include both, the way it would work out is something like this:

Code:
<!--half-orc example-->
<character>
	<strength>13
		<modifier mod="2" source="race" />
		<modifier mod="1" source="level" level="4" />
	</strength>
	<dexterity>10</dexterity>
	<constitution>10</constitution>
	<intelligence>8
		<modifier mod="-2" source="race" />
		<modifier mod="1" source="level" level="8" />
	</intelligence>
	<wisdom>10
             	<modifier mod="-1" source="leveldecrease" level="8" />
             </wisdom>
	<charisma>8
		<modifier mod="2" source="race" />
	</charisma>

If someone wants to use the history, they can just look at the elements. If not, they can just look at the base tags for each ability to get the final number. Perhaps we can even enclose history in <history> tags so they can be further ignored if desired.

I suppose I'd see less need for something like this if I hadn't had such a hard time trying to fiddle with my NPCs in PCGen. Trying to take someone down a level in that program is one of the easiest ways to screw it up.
 

Remove ads

Top