Derived Traits (Savage)

From HLKitWiki
Revision as of 14:59, 19 February 2009 by Rob (Talk | contribs) (Toughness Trait)

Jump to: navigation, search

Context: HL KitAuthoring Examples … Savage Worlds Walk-Through 


The next thing to address in our conversion process is the set of derived traits for Savage Worlds. There are a small number of derived traits in the game, and each is interesting in a particular way, so each is described in the sections below. These traits should be added to the file "thing_traits.dat". [Note: For the moment, do not delete any of the existing derived traits.]

Traits in General

Each derived trait uses its own Eval Script to calculate the derived trait value. Within these scripts, the "#trait" macro can be used to get the value of a trait, but the trait is assumed to exist for the character. In the case of skills that may not be added to a given character, the "#traitfound" macro is recommended instead, since a missing skill will result in a value of zero being used as a safe default.

Since derived traits are based upon other traits, we have a "chicken-and-egg" situation with respect to script evaluation timing. The "Trait" component auto-calculates the final value for the traits it derives from. But derived traits need to calculate themselves based on the final value of other traits. To solve this, the "Derived" component provides a second calculation of the trait's final value that occurs after the initial calculation, thereby allowing the final value of base traits to be utilized. This means that each derived trait must determine its own adjustments between the evaluation of these two scripts. We accomplish this by scheduling the Eval script for each derived trait to occur at Traits/4000, splicing it between the two component scripts.

The value of each derived trait is calculated as a "bonus" for the trait, using the "trtBonus" field. This field is used by the Eval script within the "Derived" component, just as it is used by the "Trait" component. We could introduce a new field for this purpose, but it's not necessary, provided we get our script timings correct.

In general, trait bonuses and penalties can arise from multiple sources. Consequently, we need to add our calculated value to the "trtBonus" field instead of setting the value. There are some situations where we need to explicitly set the value of a derived trait based on other picks and other situations where we need to add an adjustments based on other picks. The "Pace" trait is a perfect example of this, where certain races and edges set a new base trait value, while other effects can simply adjust the pace. This gets more complicated, and we'll discuss it in detail with the "Pace" trait below.

Appropriate tags must be assigned to each derived trait to control where and how the trait is displayed to the user, as well as the order in which the traits are displayed.

Toughness Trait

The "Toughness" trait is probably the simplest trait, so we'll start with it. Since the "Toughness" trait is needed both in and out of combat, we assign it tags to ensure it gets displayed in all the appropriate places. Other than that, the key item of interest for the "Toughness" trait is its calculation. 

The rules stipulate that "Toughness" is calculated as "2 plus half your Vigor". In addition, the effects of any armor are also added. Since our "Vigor" attribute is already being tracked as a range of 2-6 and then doubled for display, our internal value is the "half" value that we need to add. We're going to defer handling any overage beyond a "d12" rating until later. To make our life easier, the Skeleton files pre-define a "#trait" macro for use in scripts that will readily access the value of a trait for us.

Putting it all together, the "Toughness" trait should look something like the "thing" element shown below:

  description="Description goes here">
  <fieldval field="trtAbbrev" value="Tgh"/>
  <tag group="explicit" tag="9"/>
  <tag group="DashTacCon" tag="Combat"/>
  <eval index="1" phase="Traits" priority="4000">
    <before name="Derived trtFinal"/>
    <after name="Calc trtFinal"/><![CDATA[
    ~toughness is 2 plus half the character's Vigor, but we track attributes at
    ~the half value (2-6), so we add Vigor directly
    field[trtBonus].value += 2 + #trait[attrVig]
    ~equipped armor should add to the Toughness, so we add that from the armor

As indicated in the script above, the effects of armor also need to be added to the "Toughness" trait. However, that must be done separately by each piece of armor that the character has equipped. We can accomplish this within the "Armor" component. Open the file "equipment.str" and take a look at the "Defense" component. There is an Eval script (#2) that applies the effects of equipped armor and shields. In Savage Worlds, shields work differently from armor and don't apply direct adjustments when equipped, so this script should be moved to the "Armor" component. Delete the script from the "Defense" component and then add it to the "Armor" component beneath.

Now we need to modify the script to apply the correct adjustment. Instead of adding the defense rating of the armor to the "trDefense" trait, it needs to be added to the "trTough" trait, so make the change and we're done. The finished script should look something like below:

<eval index="2" phase="PreTraits" priority="5000">
  <before name="Calc trtFinal"/>
  <after name="Equipped"/><![CDATA[
  ~if this gear is not equipped, skip it
  if (field[grIsEquip].value = 0) then
  ~apply the appropriate trait adjustments for the equipment
  #traitbonus[trTough] += field[defDefense].value

According to the rules, only armor on the torso of the character is applied to the "Toughness" trait. However, we have no way of determining that yet, so we'll have to add that detail later when we work on equipment. Situations like this will come up at times when developing your data files. The easiest way to handle it is to make a note so that we remember to go back and handle it later.

Parry Trait

The "Parry" trait is very similar to the previous trait. Since the "Parry" trait is combat related, we assign it tags to ensure it gets displayed in appropriate places pertaining to combat. Other than that, the key item of interest for the "Parry" trait is its calculation. The rules stipulate that "Parry" is calculated as "2 plus half your Fighting skill", or simply the value 2 if the character has no "Fighting" skill. We're going to defer handling any overage beyond a "d12" rating until later. So the interesting detail here is that the character might not actually possess the "Fighting" skill. 

Normally within HL, if a pick is accessed by a script that does not exist on the character, the line of script code is aborted as invalid and a runtime error is reported. We don't want that to occur. One option would be to write the code to check for the existence of the pick and proceed based on its presence. But there will likely be lots of places within your data files where you would need to add code like this, so something more convenient would be much better.

To support this, HL provides the "childfound" context transition in addition to the "child" transition. The "child" transition reports the error described above, but the "childfound" transition quietly returns a value of zero for a pick that isn't found. This means that we can simply use the "childfound" transition and have everything work exactly as we want in both cases.

To make things even easier, the Skeleton files pre-define a script macro of "#traitfound" that we can use to conditionally access the value of a trait, so all we need to do is use the macro. This results in an Eval script for "Parry" that should look something like what is shown below:

<eval index="1" phase="Traits" priority="4000"><![CDATA[
  ~parry is 2 plus half the Fighting skill, but we track all skills at the half
  ~value (2-6), so we simply add the Fighting skill that is already halved; we
  ~use "#traitfound" in case the character does not possess the Fighting skill
  field[trtBonus].value += 2 + #traitfound[skFighting]

Charisma Trait

The "Charisma" trait is similar to the previous ones, but it has its own wrinkle that needs to be handled. In the interest of efficiency, we'll focus on that one wrinkle here. The wrinkle is that the final Charisma trait value needs to be applied as a bonus/penalty to Persuasion and Streetwise rolls. We can't do this until after the derived trait value is calculated, so we need to add a separate Eval script to the "Charisma" trait that performs this task. We can use the "#traitroll" script macro that we defined earlier to access the appropriate adjustment fields. This results in the following script being added to the "Charisma" trait:

<eval index="2" phase="Final" priority="1000">
  <before name="Calc trtDisplay"/><![CDATA[
  #traitroll[skPersuade] += field[trtFinal].value
  #traitroll[skStreet] += field[trtFinal].value

Pace Trait

The final derived trait for Savage Worlds is "Pace", and, as with Charisma, we'll focus only on its differences here. The "Pace" trait is different from the others in that it can be adjusted due to some effects and set to an explicit value by other effects. For example, the "Dwarven" race assigns a lower starting "Pace" to a character, and hindrances like "Lame" set an even lower starting "Pace". So we can't just assume everything is an adjustment - we need to handle assignment as well.

The simplest way to handle this is to break up the processing into separate stages, with each stage being assigned to an appropriate evaluation timing. The default value of "6" for "Pace" can be assigned as the default field value for the "trtBonus" field, replacing the need for an Eval script to calculate the value. After that, there are three basic stages that need to be supported, each in order. First is the racial selection, so we pick an appropriate timing such as Setup/1000 for when this should occur. Next is effects that set a fixed value and override any racial effects, such as the "Lame" hindrance, so we pick a later timing such as Setup/10000 for these effects. Lastly, there are the adjustment effects, which can be applied at any time after the previous two.

The last thing of interest with the "Pace" trait is that the value can never drop below one. As such, we need an Eval script to enforce this rule. The script must occur immediately after the "Derived" component calculates the new trait value - before any other script accesses the final trait value.

We can now put it all together and get the following for the "Pace" trait:

  description="Description goes here">
  <fieldval field="trtAbbrev" value="Pace"/>
  <fieldval field="trtBonus" value="6"/>
  <tag group="explicit" tag="6"/>
  <tag group="User" tag="Combat"/>
  <tag group="DashTacCon" tag="Combat"/>
  <tag group="DashTacCon" tag="Column1"/>
  <eval index="2" phase="Traits" priority="6001">
    <after name="Derived trtFinal"/><![CDATA[
    if (field[trtFinal].value <= 0) then
      field[trtFinal].value = 1