Traits (Savage)

From HLKitWiki
Jump to: navigation, search

Context: HL KitAuthoring Examples … Savage Worlds Walk-Through 


When converting the Skeleton data files to the new game system, the first thing to address is the traits. So that's where we'll begin with the Savage Worlds data files.

What Are Traits?

Virtually every game system utilizes an assortment of game mechanics to represent facets of a character. For example, there are attributes, skills, abilities, and a host of other possibilities. Each of these has one thing in common: a single rating. Any facet of a character that includes a single measurement or rating is considered to be a "trait".

Since the nature of components allows extensive reuse of fields and behaviors, the notion of traits is important. All of the shared behaviors of traits can be encapsulated within a single, reusable component. As a result, the Skeleton files define a shared "Trait" component. Each different type of trait has its own component that manages only the facets that are different for that particular trait. This approach makes data file development significantly easier, because you don't have to worry about re-implementing the same basic logic multiple times for attributes, skills, abilities, and whatever else is employed by the game system.

General Traits Versus Specific Traits

In many game systems, there are multiple facets of the game mechanics that share common behaviors, and there are some facets that are distinct. When looking at traits in general (attributes, skills, abilities, etc.), the question of commonality is critical. The Skeleton data files define a "Trait" component that embodies these common behaviors, and there are other components for the differences. So the game system must be assessed to determine which behaviors are shared and which are distinct. Shared behaviors should be modeled within the "Trait" component and the others in the appropriate, separate components.

The first thing to do is review the game system to identify the shared behaviors that apply to multiple traits. These behaviors can then be folded into the "Trait" component for general use.

Die Types for Traits

The most obvious facet of traits in Savage Worlds is the use of different die types instead of raw values. HL doesn't have any built-in support for something like this, so we need to improvise a solution. Fortunately, it's an easy task. The various die types use by Savage Worlds are a d4 through a d12, in increments of two. That translates to a range of 2-6, in increments of one, with the final value multiplied by two.

This can be easily implemented by setting up traits to have minimum value of two and a maximum value of six, with a script to convert the value displayed to show the proper die type instead. We can modify the data files to support this. Looking at the data files, the "Attribute" component has a script that sets the minimum value to one. We can move this script to the "Trait" component so it applies to all traits, and we can modify it to assign the appropriate minimum and maximum values. The resulting script should look something like the one below:

<eval index="2" 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
  field[trtMinimum].value = 2
  field[trtMaximum].value = 6

Once the value is tracked correctly, we need to convert it properly for display. We plan on using an incrementer to allow the user to adjust attributes and skills, just like is done for other game systems. Incrementers rely on the finalized value of the corresponding "user" field to display the proper value. Consequently, we need to ensure that the Finalize script for the "trtUser" field retrieves the correct text to display. However, we may also want to display the generated text in other situations, such as within character sheets and other places in the interface. The solution is to define a new field where we'll synthesize the text for display - we'll call this field "trtDisplay". The "trtUser" field can then grab the contents of this new field within its Finalize script. The field should look like below.

  name="Net Roll to Display"

The text for the "trtDisplay" field needs to be generated prior to its use in the Finalize script of the "trtUser" field. The problem is that a field with a Finalize script cannot use the finalized value of a different field. The solution is to use a script that is timed to occur very late in the evaluation process. Since we can only compute value-based fields in a Calculate script, and we need to synthesize a text-based field, we must generate the text in an Eval script. Our script gets the final value for the trait and multiplies it by two to yield the proper die type, which can be output with an appropriate notation (e.g. "d6"). After our data files get more fleshed out, we can replace the simple "d6" notation with a suitable bitmap for the die type.

There are two drawbacks with this approach that we must handle. First of all, in-play adjustments can potentially increase or decrease a trait beyond the standard limits. Consequently, we must bound the value appropriately before we attempt to convert it. Secondly, all traits are now shown using the die type syntax, but traits include derived traits, and derived traits are simple values and not die types. As such, we need to ensure that derived traits display as the actual value and do not get converted. This can be achieved by amending the script to handle derived traits specially. Since every derived trait inherits from the "Derived" component, we can identify a derived trait based on the "component.Derived" tag and handle it specially.

The resulting script looks something like the one below:

<eval index="3" phase="Render" priority="5000" name="Calc trtDisplay">
  <after name="Calc trtFinal"/><![CDATA[
  ~if this is a derived trait, our display text is the final value
  if (tagis[component.Derived] <> 0) then
    field[trtDisplay].text = field[trtFinal].value

  ~bound our final value including in-play adjustments
  var final as number
  final = field[trtFinal].value
  if (final < 2) then
    final = 2
  elseif (final > 6) then
    final = 6

  ~convert the final value for the trait to the proper die type for display
  var dietype as number
  var display as string
  dietype = final * 2
  display = "d" & dietype

  ~put the final result into the proper field
  field[trtDisplay].text = display

Once everything is in place, reload the data files and test everything out. The sample attribute should display as "d4" on the Basics tab. If you adjust it up and down, it should properly display the next die type, capping at "d12" at the upper end and "d4" at the lower end.

NOTE! Instead of relying on a range of 2-6 and then multiplying by two, we could use the actual die type (4, 6, 8, etc.) and ensure that all adjustments apply a delta of two. For example, we could define all incrementers for editing attributes and skills to have an "interval" of two. Either approach is valid. However, in Savage Worlds, the "half" value of the die type is often used, so we'll either be adding/subtracting/dividing by two everywhere or simply multiplying by two before displaying the results. It's easier to just multiply by two immediately before showing the info to the user and simply adjusting by one step everywhere, so that's the approach we've chosen.

Bonuses and Penalties

There's another important facet of traits in Savage Worlds that needs to be addressed. Traits can have bonuses and/or penalties applied to the actual roll - which is distinct from adjustments to the die type. For example, a character might have a trait roll of "d6+1" due to a suitable bonus. These adjustments need to be both tracked and displayed appropriately.

Tracking the adjustments can be readily accomplished by adding a new field. Since the adjustment applies to the actual roll, we'll call the field "trtRoll". Scripts can modify this field, which defaults to zero, to confer bonuses or penalties to rolls for a given trait.

But wait. The "Profession" edges within Savage Worlds work differently than normal adjustments to traits. By default, adjustments to rolls will "stack", meaning that a "+1" from one source combines with a "+1" from another source to yield a net adjustment of "+2". However, the "Profession" edges are specifically defined to not stack with each other - but they do stack with other adjustments. Since adjustments that do stack can be combined with adjustments that don't stack, we need to track the two independently. This means that we actually need to add one more field to track the non-stacking "Profession" adjustments. To keep everything clear, we'll call this field "trtNoStack".

Now we need to display the adjustments properly. This entails factoring the adjustment values (there are two) into the text that is displayed for the trait. So we need to add the code below to the Eval script we created above to tack on the net adjustment value. This extra logic should be inserted after the die type is synthesized and before the final value is copied into the field.

~if there are any bonuses or penalties on the roll, append those to the final result
var bonus as number
bonus = field[trtRoll].value + field[trtNoStack].value
if (bonus > 0) then
  display &= "+" & bonus
elseif (bonus < 0) then
  display &= bonus

Scripts are going to need to access our new fields to get and set the values. Since adjustments are pretty common in Savage Worlds, we're going to need to do this in lots of places. To make this easier throughout our scripts, we can define script macros that provide us with a shorthand for accessing the fields. Just below the "behavior" element within the file "definition.def", you'll find a small number of macros already defined. We'll add two new macros that correspond to our new fields, adding the two XML elements shown below.

<scriptmacro name="traitroll" param1="trait" 
<scriptmacro name="traitprof" param1="trait" 

Switching the Display

The Skeleton data files are centered around the "trtFinal" field of the "Trait" component, but we've just changed everything so that the "trtDisplay" field actually contains what we want to show to the user. Consequently, we should go through the data files and assess all the references to "trtFinal" to determine which of them should be converted over to the "trtDisplay" field. Wherever we need to utilize the value, "trtFinal" is still the proper thing to use, but we should use "trtDisplay" anywhere that the final result should be displayed to the user.

Doing a search of all the data files, we find quite a few references to the "trtFinal" field. We'll examine each and convert as appropriate. Remember that the "trtDisplay" field is a "text" field, so we need to use "field[trtDisplay].text" to access it.

In the file "definition.def", both references are within script macros, and those macros need to specifically manipulate the "trtFinal" field value. So no changes are needed here.

In the file "traits.str", there are multiple references, but they are all part of the proper handling of traits that we just implemented above, so nothing needs to be changed.

In the file "tab_basics.dat", there is one reference. It's in a table that displays traits as picks. Since we're showing picks - not things - we should convert to the new field. So we change the reference to the line of script code shown below.

@text = field[trtDisplay].text

In the file "summ_basics.dat", there is one reference. Since this is a summary panel, it's definitely a situation where we want to display the final text to the user. So the field associated with the portal must be changed from "trtFinal" to "trtDisplay".

In the file "form_taccon.dat", there are two references. Both are used to display traits in generic fashion, so both must be converted.

In the file "procedures.dat", there are four references. The first is used for displaying traits in general ("dshrolls" procedure), so it must be converted. The next is found in the "dshcombat" procedure and must be converted for the same reason. The remaining references are in the "dshbasics" procedure, where the first is for power points and must remain a value (i.e. no conversion), while the other two are for display of traits like attributes, so they can be converted.

In the file "sheet_standard1.dat", there are five references. Only two of the references pertain to attributes and skills, with the other applying to traits where a numeric value is needed. So we only change the references to "trtDisplay" in the templates "oAttrPick" and "oSkillPick".

In the file "out_statblock.dat", there are two references. Both are used for the display of traits, so both can be converted. The second instance is specifically for derived traits, so the conversion is unnecessary, but it is still safe (and well-advised) to make the change.