Creature Customization (Savage)

From HLKitWiki
Revision as of 20:51, 10 February 2009 by Rob (Talk | contribs)

Jump to: navigation, search

Context: HL KitAuthoring Examples … Savage Worlds Walk-Through 


In the previous sections, we managed to get creatures working smoothly. However, there was one area where we definitely could have provided a bit more flexibility. We allowed the user to customize attributes and skills from their starting points, but we did not do so for derived traits and abilities.

What if the GM wants a creature that's a little bit faster or tougher than normal? The answer is that we should allow him to adjust any of the creature's derived traits in whatever way suits his needs.

What if the GM wants to create an undead version of a normal creature? The simple solution would be to add the "Undead" ability to that creature, which will automatically add the Toughness adjustment and other notes for reference.

The focus of the sections below is on adding that flexibility to our data files.

Customizing Derived Traits

At this point, we have the basic framework in place for applying custom adjustments to derived traits for creatures. What we need to add now is the ability for the user to further tailor the derived trait values for a creature. Attributes and skills utilize an incrementer that allows the user to modify the values. If we use the same basic approach for derived traits, then the user can adjust the values freely.

Leveraging the "trtUser" Field

This approach would entail assigning the adjustment value to the "trtUser" field for each derived trait. But that field is not used 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 use the "trtUser" value instead of "trtCreatur" within the calculation of "trtFinal". 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.

Before we go down this path, though, we should think through the implications and how it's all going to work. The starting adjustments will be loaded into the "trtUser" field, which will be modified by the user. We'll want to bound that field so that it can be adjusted within a reasonable range of the starting value (e.g. -3 to +3). This will allow the GM to tailor a creature appropriately without going completely wild. We'll then add the user value into the final trait calculation. This all seems perfectly sound.

Unfortunately, there's a critical liability in this approach. All of the current mechanisms we've implemented are built around the "trtUser" field being used with attributes and skills. The field will need to be handled in many different ways for derived traits. Our bounding limits will be different, the initial value will have to change, and how the field is utilized in various places will need to be revised. The one field will need to be used in two very different ways for different traits. That's going to make implementing our changes more complicated and will make maintaining our data files in the future much more difficult.

On top of this, there is a subtle timing issue that comes into play with the bounding of the user value. Since the "trtUser" field is also used for normal (i.e. non-derived) traits, the bounding logic needs to be changed for our derived traits. That can be handled, but the bounding must be performed before the initial calculation of "trtFinal" within the "Traits" component. For our purposes, we need to access the net values calculated for our derived traits in order to do proper bounding. For example, the Toughness trait must be calculated so that we know what the bounding range can be. This means we'll need to do the bounding after "trtFinal" is calculated. We have a chicken-and-egg problem.

The bottom line of all this is that we really shouldn't consider using the "trtUser" field for our purposes with derived traits.

Alternate Field for User Adjustment

The good news is that we can simply create a different field for our needs and proceed with our original plan. Our new field will behave very similarly to "trtUser", so we'll start by cloning it. The field is only needed for derived traits, so we'll add it to the "Derived" component.

We can re-use the "trtMinimum" and "trtMaximum" fields for bounding, and we won't need to do any special handling of the bounds. We want to display the final value within the incrementer, which we can pull from the "trtDisplay" field. Lastly, we want to allow the user to edit that value directly, so we'll include delta handling for our field. Our resulting field should look like the following.

  name="User Value for Creature"
  <!-- Bound the user value to the limits established for the trait -->
  <bound phase="Traits" priority="5500" name="Bound trtUserCre">
    <before name="Derived trtFinal"/><![CDATA[
    @minimum = field[trtMinimum].value
    @maximum = field[trtMaximum].value
  <!-- Display the final calculated value to the user -->
    @text = field[trtDisplay].text

With the field in place, we need to set it up properly. To accomplish this, we'll revise the Creation script for the "Creature" component. In addition to setting up the attributes, we must also assign the initial user values for the derived traits. This consists of adding the following lines of code to the script.

~assign the appropriate adjustment values to derived traits
hero.child[trPace].field[trtUserCre].value = field[crePace].value
hero.child[trParry].field[trtUserCre].value = field[creParry].value
hero.child[trTough].field[trtUserCre].value = field[creTough].value
hero.child[trCharisma].field[trtUserCre].value = field[creCharis].value

The next step is to include the "trtUserCre" 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 change the code to use in the "trtUserCre" field for creatures instead of the "trtCreatur" field, as shown below.

~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[trtUserCre].value

User Manipulation of Derived Traits

At this point, we have the basic internal workings in place. Before we spend more time here, let's see how things work within the interface. We 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.

    @text = "Adjust this trait by clicking on the arrows to increase/decrease the value assigned."

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
  portal[value].visible = 0

~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 and have appropriate initial values displayed. We should be able to make adjustments via the incrementers and see the corresponding values change for the creature.

Bounding the Derived Traits

At this point, we've got a user-modifiable value that will be setup properly and factored into the final adjustment calculation. The final piece we're missing is the bounding, since the user can freely modify the value to silly numbers. We'll achieve this by setting up appropriate values for "trtMinimum" and "trtMaximum", ensuring that the user can adjust the starting value within reason.

In order to properly bound the value, we need to know the original starting value, since we'll establish our limits relative to that value. We also need to know what the absolute minimum value is for a given trait (i.e. its "floor" value). This value differs for each derived trait, since Charisma can go negative, Pace can't drop below one, and the others can't drop below two.

We already have the starting value in the "trtCreatur" field. However, we need to introduce a new field on the "Derived" component to track the "floor" value. Since this value will be setup appropriately by the creature, it is a simple derived value. The new field should look like below.

  name="Creature Floor"

We need to setup the "floor" value every evaluation cycle, just like we're already doing for the "trtCreatur" field. This is done by augmenting the existing Eval script on the "Creature" component. We simply need to assign the appropriate values to the field for each derived trait, which is accomplished by adding the following lines of code to the script.

~setup the proper floor values for each trait
hero.child[trPace].field[trtFloor].value = 1
hero.child[trParry].field[trtFloor].value = 2
hero.child[trTough].field[trtFloor].value = 2
hero.child[trCharisma].field[trtFloor].value = -4

We can now utilize these values for properly bounding the "trtUserCre" value. We'll define a new Eval script on the "Derived" component for this purpose. Our minimum and maximum will range from -3 to +3 from the original starting value. We'll also use the floor value to verify that we don't let the user drop a trait below its absolute minimum. The only special detail about this script is its timing. We have to schedule this script after all standard traits are calculated and before the Bound script is evaluated on the "trtUserCre" field. This results in the new Eval script below.

<eval index="3" phase="Traits" priority="5300">
  <before name="Bound trtUserCre"/><![CDATA[
  ~setup a minimum at 3 below the starting value; if our minimum will yield a
  ~value below the minimum for this trait, limit it to the minimum
  field[trtMinimum].value = field[trtCreatur].value - 3
  var bonus as number
  bonus = field[trtBonus].value + field[trtInPlay].value
  if (field[trtMinimum].value + bonus < field[trtFloor].value) then
    field[trtMinimum].value = field[trtFloor].value - bonus

  ~now setup a maximum at 3 above the starting value
  field[trtMaximum].value = field[trtCreatur].value + 3

Reload the data files and use our test creature. Let's attempt to adjust the derived traits. Decreasing Parry stops us at "2", since we set that up as our absolute floor. However, we can increase Parry a total of three notches. Our Toughness defaults to "4" and has the same behaviors. Our Pace is limited to a range of "2" to "8". Everything checks out.

Setup the Delta

All of our behaviors are in place, except for one. Derived trait values are simple numbers and not die types. Consequently, we want to let the user edit the value directly within the incrementer where they are shown. However, we want the user to edit a value that makes sense to him (i.e. the final result) instead of the adjustment value. To do this, we need to set the "delta" for the "trtUserCre" field to the difference between the actual user value and the value the user will see. Once that's done, HL will handle the rest for us. The new Eval script should look like the following.

<eval index="4" phase="Render" priority="5000"><![CDATA[
  if (hero.tagis[Hero.Creature] <> 0) then
    field[trtUserCre].delta = field[trtBonus].value + field[trtInPlay].value + herofield[acNetPenal].value

We should also verify our delta is working properly. Reload the data files and select our test creature. Click within the incrementer showing the Parry and it should show the current value for editing. Enter the new value "4" and everything appears to work fine. Now try entering a value of "9". The new value is automatically bounded to the maximum we established of "6" (i.e. our initial value of 3 plus and additional 3). Derived traits are fully operational.