Difference between revisions of "Creature Specification (Savage)"
|Line 397:||Line 397:|
Revision as of 02:19, 7 February 2009
It's now time to figure out how we're going to be handling the details of creatures. Each creature in the rulebook has die-types specified for each attribute. Creatures also have a list of skills, with the appropriate die-type for each. Derived traits are specified, incorporating any special adjustments beyond the standard derived value. Lastly, a list of gear and/or special abilities is given.
Our solution for creatures must enable all of these different facets to be readily assigned to each creature. We want a design that is both easy to use and also easy to utilize via the integrated Editor. For the latter, it's always easiest when tags are utilized, since users can simply pick from a controlled list that the Editor presents. The next easiest mechanism is simple fields, wherein the user can enter text or a value. After that, whatever works is going to be pretty much the same.
Given the list of requirements and goals above, we'll see about devising a good solution. As an added user benefit, we'll also strive to allow the user to customize the default values for each creature if he wants.
There are two different facets of traits that we'll need to handle. The first is the basic die-type for the trait, ranging from "d4" to "d12". The second is the roll bonus in situations where the creature has a trait that exceeds a "d12" rating.
These two facets are managed internally via two separate fields for each trait. As such, our first thought will likely be to do the same. We could have two separate fields that specify the starting die-type and bonus for each trait. An Eval script could be written that assigns the die-type value to the "trtBonus" field and the bonus value to the "trtRoll" field.
This would give us a workable solution, but it would be far from ideal. Since we're applying the values as bonuses, we run into a number of limitations. The most important limitation is that we are unable to allow the user to adjust the trait values. Since the attributes start at the minimum and the bonus is applied, users can increase the die-type but not decrease it. Consequently, we can't allow adjustment, since users will expect full range of adjustment to be possible. Another issue is that users would be unable to adjust the roll bonus. If the user wanted to change a trait from "d12+2" to "d12+1", there would be no way to do it, other than applying a permanent adjustment to the creature.
Giving the problem a bit more thought, a better approach would be to actually set the "trtUser" field of the trait to the proper die-type. This would make it possible for the user to then edit the die-type as he saw fit. Unfortunately, there are two liabilities with this technique. There would still be no way for the user to easily adjust any roll bonus, plus there would be nothing to prevent the user from modifying the die-type independently from the roll bonus. For example, a trait of "d12+1" could have only the die-type modified downwards one notch, which would result in a trait of "d10+1" instead of "d12".
Based on this analysis, we need a single unified value that represents both the die-type and the roll bonus. Once the value reaches a "d12" rating, increasing it one step would become "d12+1". The increase would steadily adjust the roll bonus as soon as the maximum die-type was reached. How can we do this?
The trick is to modify the behavior of the "trtUser" field when dealing with a creature. For a normal character, the "trtUser" field has a fixed range of 2-6, corresponding to the "d4" through "d12" die-types. However, we already have to handle the case of the die-type exceeding "d12" when determining what to display, so having a value of 8 will still result in a "d12" being displayed. This means we can change the maximum limit to higher than 6 and the die-type display will continue to work fine.
Armed with this knowledge, we can consider values larger than 6 to indicate progressive increases in the roll bonus. A value of 7 would translate to "d12+1", while 8 would translate to "d12+2", etc. We can write an Eval script to detect a value greater than 6 and automatically apply the excess as a roll bonus by modifying the "trtRoll" field.
Since the "trtUser" field is directly modifiable by the user via the incrementer, this approach affords the user with a simple, intuitive means of adjusting traits. They start out at "d4", progress upwards through "d12", and then continue to increase as "d12+N". Decrementing the trait works seamlessly in the other direction.
We've got our strategy mapped out, so let's put the basic mechanisms into place. There are two changes we need to make internally to support this approach. The first is to setup a different maximum value for traits when creating a creature. We'll support up to "d12+10", so we'll set out maximum accordingly. The Eval script where the ranges are specified can be changed to the following.
<eval index="3" phase="Initialize" priority="3000"><![CDATA[ ~since die types range from d4 to d12, in multiples of 2, we have a range of ~2-6 for traits - we'll convert to the die type for display in a separate script ~Note: Creatures treat excess as a roll bonus, so let the maximum go to 16 (+10). field[trtMinimum].value = 2 if (hero.tagis[Hero.Creature] <> 0) then field[trtMaximum].value = 16 else field[trtMaximum].value = 6 endif ]]></eval>
With the maximum relaxed, we can add the handling for a value that exceeds 6. We'll define a new Eval script that only does something when we have a creature with a trait that exceeds 6. If we do, then the excess is added to the "trtRoll" field and becomes a roll bonus. The new Eval script should look like the one below.
<eval index="5" phase="Traits" priority="8000"> <before name="Calc trtNetRoll"/><![CDATA[ ~for creatures with a die-type greater than 6, the excess is a roll bonus if (hero.tagis[Hero.Creature] <> 0) then if (field[trtUser].value > 6) then field[trtRoll].value = field[trtUser].value - 6 endif endif ]]></eval>
We also have to figure out a convenient way to let authors specify the die-type and roll bonus values for each trait. Whatever we come up with should be readily adaptable for use within the integrated Editor, thereby allowing users to add their own custom creatures easily.
As above, the first idea is probably going to be two separate fields that can be specified. Since we're only assigning starting values, we don't have to worry about having a smooth progression. However, two separate values does make it possible for the user to inadvertently specify a roll bonus with a die-type less than "d12", such as "d8+2". So a single value would be ideal.
If we only have a single value, we then need to assess what that value will be and how the user will specify it. We currently use a value of 2-6 for the die-types "d4" through "d12". Our internal solution calls for using values of 7+ to indicate the roll bonus beyond a "d12". While all this makes perfect sense for internal use, it's going to seem like nonsense to a user with no knowledge about the inner workings of our data files. An most users don't want to understand the inner workings - they just want to knock out a quick creature for use in the upcoming game and be done with it. That means field values are a poor solution.
The best solution is to use an assortment of tags. We could define a new tag group that contains tags corresponding to each internal value we want to support. Each tag could be defined with a suitable publicly visible name that presents something intuitive to the user. For example, the tag for the value "7" would have a name of "d12+1", so the user would select "d12+1" within the Editor and the proper value of 7 would be assigned internally. We can use the "tagvalue" target reference within the scripting language to extract a value from the unique id of each tag, so we simply need to ensure that the unique id of the tags is the value we want to use internally.
An example of what the tag group should look like is provided below. We define tags that extend up to a "+10" bonus on the trait, which will be the maximum we support.
<group id="DieType" name="Die-Type"> <value id="2" name="d4"/> <value id="3" name="d6"/> <value id="4" name="d8"/> <value id="5" name="d10"/> <value id="6" name="d12"/> <value id="7" name="d12+1"/> <value id="8" name="d12+2"/> <value id="9" name="d12+3"/> <value id="10" name="d12+4"/> <value id="11" name="d12+5"/> <value id="12" name="d12+6"/> <value id="13" name="d12+7"/> <value id="14" name="d12+8"/> <value id="15" name="d12+9"/> <value id="16" name="d12+10"/> </group>
At this point, we now have both our internal and external approaches mapped out. We can now begin implementing our solution.
We'll first setup the means for specifying the attribute values to be used when defining creatures. We need to use a group of tags to specify both the die-type and roll bonus for each attribute. However, if we only define one set of tags and re-use them, we'll run into the problem of determining which tag is associated with which attribute. This means we need a separate set of tags for each attribute. Each of the tag groups is shown below, and the full set of tags defined in the previous section must be specified for each tag group.
<group id="AgiDie" name="Agility Die-Type"> ... </group> <group id="SmaDie" name="Smarts Die-Type"> ... </group> <group id="SpiDie" name="Spirit Die-Type"> ... </group> <group id="StrDie" name="Strength Die-Type"> ... </group> <group id="VigDie" name="Vigor Die-Type"> ... </group>
We now have five tag groups, each with its own set of tags. Each creature can be assigned a single tag from each tag group, with each tag dictating the appropriate die-type and roll bonus for its corresponding attribute. For example, the creature below would be assigned die-types of "d4" through "d12" for the various attributes.
<thing id="creSample" name="Sample" compset="Creature" isunique="yes" description="Description goes here"> <tag group="AgiDie" tag="2"/> <tag group="SmaDie" tag="3"/> <tag group="SpiDie" tag="4"/> <tag group="StrDie" tag="5"/> <tag group="VigDie" tag="6"/> </thing>
The final piece we need to handle is taking the values dictated by the tags and assigning them to the appropriate attributes. Our first instinct will be to create a new Eval script. In that script, we can assign the value of each tag to the "trtUser" field of the trait. Unfortunately, that won't work.
The problem is that using an Eval script will result in the value being applied to the field during every evaluation cycle. If we do that, the user can modify the attribute all he wants, but we'll always keep resetting the value back to the initial value for the creature. That's not what we had in mind.
Fields of type "user" work differently from "derived" fields. A "derived" field is reset at the start of each evaluation cycle and must be set to an appropriate value within each cycle. In contrast, a "user" field is only ever changed when the user makes a change or a script assigns a new value. Consequently, what we need to do is assign the proper values to the "user" fields when the creature is created and never touch them again. That way, we initialize them and hand control to the user thereafter.
The Kit provides a mechanism to handle exactly this type of situation. Every component can define a Creation script. This script is invoked exactly once for every pick derived from that component - at the time the pick is first created. If we assign the values within this script, they will be setup once and never modified again. The appropriate Creation script for setting up the various attributes is shown below.
<creation><![CDATA[ ~assign the appropriate die-type ratings to attributes #traituser[attrAgi] = tagvalue[AgiDie.?] #traituser[attrSma] = tagvalue[SmaDie.?] #traituser[attrSpi] = tagvalue[SpiDie.?] #traituser[attrStr] = tagvalue[StrDie.?] #traituser[attrVig] = tagvalue[VigDie.?] ]]></creation>
Reload the data files, create a creature, and then select the sample creature above. If everything is working, each of the attributes should be initialized to the proper die-type. Modifying the various attributes up and down via the incrementer will properly adjust the attributes, allowing the user to fully control everything after the creature is first added.
The one limitation of this approach is that changing the creature will reset any adjustments made by the user. In general, that behavior is a good thing, since a new creature should always start out with the default values assigned in the rulebook.
The overall technique for assigning skills is very similar to the one used for attributes. However, there is a very important wrinkle in dealing with skills. In Savage Worlds, skills are not added to every character, so that means creatures don't begin with any skills. The technique we used for attributes assumes that the attribute is already on the character so that it can be properly adjusted.
The only way to add a skill to a character is by bootstrapping it. This means that each creature must bootstrap each of the skills that it possesses. Once we do that, though, we need a way to initialize the "trtUser" field of the skill appropriately. If we try to do anything within the Creation script of the "Creature" component, it will fail, since the script is invoked immediately upon creation, which is before any bootstrapped picks are added to the creature.
The solution is to leverage the Creation script on the skill itself. We can assign the appropriate die-type tag to the skill as part of the bootstrap process. That tag is considered part of the skill, so it can be utilized within the Creation script of the skill.
This means that we only need a single tag group for all skills. Since the proper tag will be assigned to each separate skill, no skill should have more than one such tag. So we can define a new tag group like the one below that contains all of the various tags for the different die-types.
<group id="SkillDie" name="Skill Die-Type"> ... </group>
With the tag group in place, we can assign a few skills to our sample creature for testing. Each skill is bootstrapped and assigned the proper tag for the die-type it should possess. The revised sample creature below demonstrates three skills be added to the creature. These skills are then assigned die-types of "d6", "d8", and "d10".
<thing id="creSample" name="Sample" compset="Creature" isunique="yes" description="Description goes here"> <tag group="AgiDie" tag="2"/> <tag group="SmaDie" tag="3"/> <tag group="SpiDie" tag="4"/> <tag group="StrDie" tag="5"/> <tag group="VigDie" tag="6"/> <bootstrap thing="skFighting"> <autotag group="SkillDie" tag="3"/> </bootstrap> <bootstrap thing="skGuts"> <autotag group="SkillDie" tag="4"/> </bootstrap> <bootstrap thing="skNotice"> <autotag group="SkillDie" tag="5"/> </bootstrap> </thing>
The skills are automatically added to the creature and assigned their default rating of "d4". We now need to do something with the tags to initialize the die-type ratings. We'll define a Creation script for the "Skill" component. In this script, we'll first check to make sure that we have a creature and a suitable tag with which to initialize the skill. We can then use the same approach as with attributes, assigning the value of the tag as the initial value of the "trtUser" field. The Creation script below shows the logic in action.
<creation><![CDATA[ ~if this is a creature with a skill rating, assign the die-type rating to the skill if (hero.tagis[Hero.Creature] + tagis[SkillDie.?] >= 2) then field[trtUser].value = tagvalue[SkillDie.?] endif ]]></creation>
At this point, skills should be working smoothly for creatures.
Assigning Derived Traits
Derived traits work quite differently from attributes and skills for normal characters. That's because they are 100% derived from facets of the character. For creatures, though, we need to handle them differently. Various creatures have values assigned for certain derived traits that are not purely derived. The most common trait is "Pace", but other traits vary as well.
The problem is that derived traits are always calculated in the end. We need to handle them in a way that will smoothly integrate with their calculated nature. Unfortunately, there is no clean way of doing that by having the derived trait value be specified explicitly for a creature. The only way to reasonably accomplish it is to utilize a delta that gets incorporated into the calculated final value.
This means that we need to define four new fields for the "Creature" component. We need one field apiece for the four derived traits, where each field specifies an adjustment from the standard calculated value. If a field is not specified, then the default calculation applies for that trait. Our new fields should looks like the following.
<field id="crePace" name="Base Pace" type="static"> </field> <field id="creParry" name="Parry Adjustment" type="static"> </field> <field id="creTough" name="Toughness Adjustment" type="static"> </field> <field id="creCharis" name="Charisma Adjustment" type="static"> </field>
Integrating these new fields into the calculation is relatively simple. All we need is an Eval script that applies the adjustment values to the traits via the "traitadjust" script macro. If we do that, then the adjustment included in the standard calculation of the derived trait, without any special handling.
Unfortunately, there is a serious limitation with this approach. The user will not be able to directly modify the values for derived traits. We've made sure that the user can easily adjust the values for attributes and skills via the standard incrementers. What we need is a similar solution for derived traits, and applying the adjustments via an Eval script won't get it done.
If we use the same basic approach as attributes and skills, then the user can adjust the values freely. That would entail assigning the adjustment value to the "trtUser" field for each derived trait. But that field is unused for those traits. We're going to need to change that behavior and use the "trtUser" field for derived traits on creatures.
Derived traits calculate their final value via an Eval script on the "Derived" component. Within that script, the "trtUser" field is never used. It would be easy to add the "trtUser" value to the calculation for creatures - we never want to include the value for non-creatures. If we properly initialize the "trtUser" field values to the adjustments via the Creation script of the "Creature" component, then we can expose the "trtUser" field via an incrementer and allow the user to modify it.
The first thing we need to do is revise the Creation script for the "Creature" component. In addition to setting up the attributes, we must also assign the adjustment values for derived traits. This consists of adding the following lines of code to the script.
~assign the appropriate adjustment values to derived traits #traituser[trPace] = field[crePace].value #traituser[trParry] = field[creParry].value #traituser[trTough] = field[creTough].value #traituser[trCharisma] = field[creCharis].value
The next step is to include the "trtUser" field value in the calculation of the final value. For this, we need to modify the Eval script that calculates "trtFinal" within the "Derived" component. After the value is calculated normally, we can add the lines of code below to factor in the "trtUser" field for creatures.
~if this is a creature, we need to add the user value as a custom adjustment if (hero.tagis[Hero.Creature] <> 0) then field[trtFinal].value += field[trtUser].value endif
There is another detail we need to address. The minimum and maximum values for the "trtUser" field are defined within the "Trait" component. The bounds specified are intended for use with attributes and skills. Since we're tracking an adjustment value for derived traits, we need to support a range that is very different. We want a value that can range from negative to positive, and a suitable range is probably from -20 to +20. However, we don't want to allow the user to specify a negative trait value, so we need to ensure that our minimum never goes beyond a value that will yield a zero result.
To accomplish this, we need to define our own Eval script within the "Derived" component that overrides the default bounds assigned to all traits. If our new script occurs after the one that sets the default values, our new values will be used instead. Our new Eval script should look like the one shown below.
<eval index="3" phase="Final" priority="1000"><![CDATA[ ~we only employ the "trtUser" field for derived traits with creatures, where we ~want the user value to be a custom adjustment to the default calculated values, ~so setup an appropriately wide range ~Note: We don't want the user to be able to specify a negative value for any ~ trait, so we must ensure our minimum stops at a net value of zero. field[trtMinimum].value = -(field[trtFinal].value - field[trtUser].value) field[trtMaximum].value = 20 ]]></eval>
At this point, we have the internal workings in place and need to expose the field value for change by the user. This entails modifying the "baTrtPick" template that is used to show derived traits on the "Basics" tab. If we want the user to modify the value, we need to add a new incrementer portal. We can use a simple incrementer style that is provided by the Skeleton files, which results in the new portal shown below.
<portal id="value" style="incrSimple"> <incrementer field="trtUser"> </incrementer> <mouseinfo><![CDATA[ @text = "Adjust this trait by clicking on the arrows to increase/decrease the value assigned." ]]></mouseinfo> </portal>
Within the Position script, we need to do two things. First, we must make sure that we only show either the new incrementer or the old "details" portal, which means controlling visibility based on whether we have a creature or not. This can be done at any point in the script. Second, we need to center our new incrementer in the same general region used by the existing "details" portal so that it occupies the same region. We have to do this after the "details" portal is positioned. This results in the following code being added to the Position script.
~the incrementer is visible if we have a creature, else the details if (hero.tagis[Hero.Creature] <> 0) then portal[details].visible = 0 else portal[value].visible = 0 endif ~center the incrementer over the details portal perform portal[value].centeron[horz,details] perform portal[value].centervert
We're now ready to give our changes a try. When we create a new creature, our incrementers show up where they should, except that there is no value shown. The problem is that the "trtUser" field relies on the "trtIncr" field to tell it what to display for its contents. We can setup that field appropriately within the Eval script that generates the "trtDisplay" field for the "Derived" component. Since we want to show the same contents as the "trtDisplay" field, we'll set it to the same thing by adding the lines of code below at the end of the script.
~if case we have a creature, we need to show the final value appropriately field[trtIncr].text = field[trtDisplay].text
Our incrementers are now showing us values, but they are all two points higher than they should be. This is because the "trtUser" field always starts out with a value of "2", which is being added to the final value. We need to force the field value to always start as zero for creatures. This is an easy fix. The default value for the "trtUser" field is given as "2" and we can change it to "0". Since the minimum value will automatically force a value of "0" to "2", everything will continue to work perfectly for normal characters after this change.
Let's put our changes to the test with an actual creature that applies adjustments. We'll modify our sample creature for this purpose. We can assign a few field values on the creature to apply our adjustments. We'll assign a "Pace" adjustment of "-2" and a "Parry" adjustment of "3". To do this, we'll add the following XML elements to the creature's definition.
<fieldval field="crePace" value="-2"/> <fieldval field="creParry" value="3"/>
Reload the data files and create a creature. Take a look at the default values shown for the derived traits. From the chooser, select our test creature. The "Pace" value should immediately change from "6" to "4", and the "Parry" value should change from "2" to "5". Our derived traits are working smoothly now.
The assignment of standard gear to creatures is incredibly simple. All that's needed is to bootstrap the desired gear onto the creature. If the gear is a weapon or armor, then you may also want to have the gear start out in the "equipped" state, which can be accomplished by assigning the "Equipment.StartEquip" tag to the gear. For example, a "Dagger" can be automatically assigned to a creature and pre-equipped via the XML element below.
<bootstrap thing="wpDagger"> <autotag group="Equipment" tag="StartEquip"/> </bootstrap>
Many creatures also possess a natural attack of some sort and/or natural armor. You can add these as weapons in the same way as other gear. However, you're much better off using the customizable abilities that we defined earlier for this purpose.
The assignment of abilities to a creature is comparable to assigning gear. Once the ability is defined, a simple "bootstrap" element is all that's needed. The definition of abilities has already been outlined previously. For example, the "Rollover" special ability of an Alligator can be defined as shown below and assigned to a creature via the bootstrap beneath.
<thing id="abRollover" name="Rollover" compset="RaceAbil" isunique="yes" description="Description goes here"> </thing>
If a creature has a natural weapon, it's easiest to use the customizable weapon we defined earlier. No new ability needs to be defined, as the existing "Unarmed Attack" weapon can be tailored to the particulars necessary for the creature. For example, the "Bite" attack of the Alligator can be assigned to the creature via the following.
<bootstrap thing="abWeapon"> <assignval field="livename" value="Bite"/> <assignval field="abilText" value="+d6"/> </bootstrap>
A similar mechanism was defined for use with natural armor and can be readily employed. All that's needed is the defensive bonus conferred. For example, the thick skins of the Alligator confer a "+2" armor bonus, which can be assigned via the following.
<bootstrap thing="abArmor"> <assignval field="abilValue" value="2"/> </bootstrap>