Final Cleanup (Savage)

From HLKitWiki
Revision as of 16:53, 20 February 2009 by Rob (Talk | contribs) (New Characters Report Wrong Attribute Points)

Jump to: navigation, search

Context: HL KitAuthoring Examples … Savage Worlds Walk-Through 

Overview

Our data files are complete. It's time to do a final round of cleanup. We'll be eliminating any extraneous mechanisms that we inherited from the Skeleton files. After that, we'll go through and implement every individual thing in the rulebook. This includes the complete list of edges, hindrances, skills, weapons, gear, etc. Lastly, we'll do one last round of testing so we can fix anything that has slipped past us.

Skeleton File Remnants

The Skeleton files provided us with a working set of functionality, and we used the vast majority of it. However, there are some bits left over that we didn't use. Before we consider our files complete, we should prune that material out, leaving ourselves with only material that is associated with our game system.

WARNING! Deleting information from the data files will result in lots of warnings about orphaned material when you load saved portfolios. This is normal and can be ignored, since we are intentionally deleting the information. If you encounter errors like this when you aren't intentionally deleting material, be sure to verify that you haven't made an inadvertent mistake.

We'll focus our attention first on the various things that are defined. That's because any internal mechanisms we might eliminate or change will impact these things, so it's much easier to remove the dependencies before removing the mechanisms that are depended upon. All of these things will be defined within the data files that start with the prefix "thing_". The Skeleton files included a variety of thing definitions that we're no longer using. Those we can readily identify and delete.

We'll start with any things defined for the purpose of validation. Using the logic above, validation rules often build on other things, so we eliminate them before eliminating the things they depend on. All validation rules are found in the file "thing_validate.dat", so we'll go through the each thing defined in the file and determine if we're still using it. There appears to be only one, the "valCP" thing used for testing character points. We can delete that and move on.

Proceeding down the dependency hierarchy, the next group of things to focus on are any resources or trackers, which are defined in the file "thing_miscellaneous.dat". There are two resources in this file that we inherited from the Skeleton files are no longer using. These are the "resCP" and "resAbility" things, so we'll delete them.

Once we do that, we'll discover a couple of old dependencies on those two things within the "resXP" thing. We're definitely using the "resXP" resource, but the Eval script that adjusts the available quantity of those resources based on the XP is extraneous. Once we delete that, this file is cleaned.

However, there is a second dependency. The "Ability" component we inherited from the Skeleton files has an Eval script that accrues the usage of ability points. With the elimination of the "resAbility" thing, we must also eliminate the script.

We can now go through all the other data files in which we defined things. Our goal is to eliminate any sample things that were provided or that we copied, adapted, and left hanging around. The list below spells out the various places we need to check.

  • Sample attributes
  • Sample skills
  • Sample abilities
  • Sample adjustments
  • Sample advances
  • Sample weapons
  • Sample armor
  • Sample gear
  • Sample races
  • Sample edges
  • Sample hindrances

After going through the data files in search of the above items, there should only be one file left that we haven't assessed and in which things are defined. That's the file "thing_traits.dat". The Skeleton files provided us with a number of traits that we didn't use, and there should no longer be any references to them. Consequently, we can now delete them easily. The set of things to remove are "trHealth", "trInit", "trDefense", and "trPowerPts".

Our data files should no longer have extraneous things lurking within them, so we can shift our focus to the internals. In general, we don't really need to worry much about mechanisms built into the various components. Most of those mechanisms have been fully adapted to our purposes for Savage Worlds. The few exceptions are causing no trouble and will require a fair amount of work to isolate and remove. So it's best to just leave them alone, unless they are either obvious or a source of potential problems.

In our case, there are two places that are worth addressing. The first is the "Ability" component. We changed this component so that it provides a number of shared behaviors, but the Skeleton files originally defined it without that in mind. As such, there is an important behavior provided by the component that is either redundant or a potential problem. The component defines an identity tag group and assigns a corresponding tag to the hero for every ability. Each separate type of ability already does this with its own identity group, so we can delete the identity group definition and the Eval script that forwards the tag to the actor.

The final cleanup task we should do is review the contents of the "Actor" component. For many game systems, at least a few mechanisms provided in this component will be unused. Since this component is rather extensive, keeping it pruned of unnecessary logic will simply make our data files easier to maintain. We can go through the list of fields and identify those that are now orphaned. This consists of the fields "acStartCP" and "acStartAbi", which we can delete. Scanning through the rest of the component, everything else is being used.

At this point, our data files have been properly cleaned of any unused remnants of the Skeleton files.

NOTE! If our game system did not use the advancements mechanism, we could also delete the data files specific to advancements and all the associated references.

Identifying Timing Problems

One of the details we should verify during cleanup is whether we have any timing problems within our data files. Unless we did an exhaustive job of establishing and maintaining timing dependencies during development (we didn't), it's quite likely that we're going to have at least one or two holes in our logic. These holes might not even be visible right now, but future changes or user extensions could expose them.

The best thing we could do right now is to go through and establish thorough timing dependency relationships everywhere. Any task that relies on other values can be assigned a named "after" timing dependency on the scripts that calculate those values. Alternately, the scripts that calculate can be assigned a named "before" dependency on the script that uses the value. That's going to entail a healthy amount of work, so it's probably not a practical solution.

What we can do instead is some spot testing to see if we can uncover problems. Whenever we do uncover a problem, we can take the time to add the appropriate timing dependency relationships to the involved tasks. This won't be perfect, but it will be much faster and will ensure that we begin improving our use of enforced timing dependency relationships.

Sample Timing Problem

When we create a new character, the number of attribute points is being displayed as "-12 of 5" above the table of attributes. The moment that we make any change at all to the character, everything behaves correctly. We obviously have a timing problem.

The value "-12" is quite suspicious, too. We have a total of six attributes defined (including the hidden one for super powers). That translates to a difference of two points per attribute. It appears that we broke this when we changed the default value of each attribute from two to zero.

The attribute values are being properly bounded to two, as evidenced by everything being corrected by making any change to the character. A change triggers a new evaluation pass. This means that the field on the resource that contains the message for display is probably being synthesized before the bounding of the field value is occurring. Unfortunately, the message is being synthesized during the Render phase, so that's not the case.

Looking a bit more closely, the message being synthesized is using the "resLeft" field. That field is being calculated at a timing of Final/1000. However, the bounding is occurring at a timing of Final/10000. We've uncovered the source of the problem.

We need to change the timing of one or both actions. Before we can do that, though, we must first assess what other timing dependencies exist on these two actions. The calculation of the "resLeft" field is named, so we need to assess what other scripts depend on it. There are two, but both of them must occur before the calculation, so we are free to move the calculation later if we wish.

The bounding script is also named, and there is one dependency. The calculation of the delta must occur before the bounding is performed, and the delta depends on the fields "trtBonus" and "trtInPlay". Both of those fields must be in their final state before they are used to calculate the "trtFinal" value, and that happens at a timing of Traits/3000. This means that we can freely move the delta calculation to substantially earlier if we wish.

The pieces of the puzzle are now in place. The best solution is probably to change the timing of all three scripts. We'll calculate the delta for "trtUser" at a timing of Final/1000. Then we can move the timing of the bounding to Final/5000. Lastly, we can move the timing of the "resLeft" calculation to Final/7000. We'll also specify an "after" timing dependency of the "resLeft" calculation upon the bounding so that we don't run into this problem again in the future.

Once we make these changes, everything should work properly again. Unfortunately, the problem remains. We found a piece of the problem, but there is something else amiss still.

After adding some "debug" statements to our scripts, we'll confirm that "resLeft" calculation is yielding the wrong results. So we need to focus on the information that the "resLeft" calculation depends upon. This would be the "resMax" value and the "resSpent" value. Doing a quick search of where the "resMax" field is referenced in the data files confirms that the "resMax" value is not the culprit, so that leaves the "resSpent" field. Doing another scan of where the "resSpent" field is referenced uncovers that it is being tallied for attributes via an Eval script at a timing of Setup/5000. That's way before the bounding occurs, which explains the problem. If we change the timing to Final/6000 (i.e. after the bounding), everything starts to work correctly.

To make sure this problem doesn't arise again, we'll add a timing dependency to the erroneous Eval script. We'll ensure that this script always occurs after the bounding of the "trtUser" field.

But wait a minute. The script we fixed only applies to attributes. A similar script exists to tally the points spent for skills. Since skills are traits, they also need to be tallied after the bounding is performed. Skills start with a default "trtUser" value of zero, so we aren't noticing the error. However, if we changed the default value, we would see the same problem. The solution is to make sure that the Eval script on the "Skill" component is also changed to the same timing and that a suitable timing dependency is also added.

The only other use of the "Trait" component is with derived traits. Since there is no resource being tallied for derived traits, there are no other places where this problem could be lurking.

Rank and XP Shown for Creatures

There are a number of places where XP and rank are being shown for creatures. We eliminated this from a few obvious spots, but we overlooked some as well. We need to go through these locations and verify that the XP and rank are omitted for creatures. We can identify all the places that we need to check by doing a search for references to the "acRankName" field name.

The first instance we find is within the LeadSummary script in the definition file. We can wrap the logic for including the XP within an if/then block, as shown below.

~append the rank and XP (only for non-creatures)
if (hero.tagis[Hero.Creature] = 0) then
  @text &= " - " & herofield[acRankName].text & " - " & herofield[acFinalXP].value & " XP"
  endif

The next instance is within the Eval script that synthesizes the ally recap within the "Actor" component. We can use the same technique and wrap the code within an if/then block. However, this instance requires a little more tweaking than just an if/then block, since the punctuation output assumes the XP is always included. The revised logic should look like the code below.

~output any race
recap &= field[acRaceName].text

~output the XP and rank (only for non-creatures)
if (hero.tagis[Hero.Creature] = 0) then
  recap &= ", " & field[acRankName].text & "  (" & field[acFinalXP].value & " XP)"
  endif

There are two instances within character sheet output that need to be addressed. One is on the first page, where the character details are synthesized in the "oHeroInfo" portal. We can again wrap the logic within an if/then block, as shown below.

~append the rank and XP (only for non-creatures)
if (hero.tagis[Hero.Creature] = 0) then
  @text &= "; " & herofield[acRankName].text & "  ("
  @text &= herofield[acFinalXP].text & " XP)"
  endif

The other instance within the character sheet output is on the second page, within the output for allies. We can use an if/then block again, although we need to be sure to keep the newline outside of the conditional block. The resulting code is below.

~output the rank and XP (only for non-creatures)
if (hero.tagis[Hero.Creature] = 0) then
  @text &= "; {b}" & herofield[acRankName].text & "{/b} ("
  @text &= herofield[acFinalXP].text & " XP)"
  endif

~insert a newline before continuing our output
@text &= "{br}"

The XP and rank are also shown within the "Basics" and "Journal" tabs. However, we have already dealt with both. On the "Basics" tab, the portal with the information is hidden for creatures. Similarly, the entire "Journal" tab is hidden for creatures.

Derived Traits Positioning Within Sheets

It turns out we didn't properly anticipate some of the nuances of derived traits when we first implemented character sheet output. All of the portals are positioned on a left-to-right basis within the "oDerivPick" template. However, if a derived trait is more than one character in width, everything ends up being aligned oddly.

We need to change the behavior so that the "adjust" portal is placed against the right edge. Once that's done, we can then place the "value" portal relative to the "adjust" portal on a right-to-left basis. This will ensure that everything always lines up consistently, regardless of the width of the "value" portal.

We can accomplish this change by replacing the code within the Position script that handles horizontal placement. While we're at it, we'll also assign a "marginhorz" attribute of "25" to the template, which allows us to simply place both the "name" and "adjust" portals flush against their respective edges. The revised code for the Position script is shown below.

~position everything horizontally
portal[name].left = 0
perform portal[adjust].alignedge[right,0]
perform portal[value].alignrel[rtol,adjust,-20]

Show XP and Rank on Basics Summary Panel

There is plenty of available space at the bottom of the "Basics" summary panel, and we should consider putting that space to good use. One thing that would be potentially helpful on the panel is the character's current rank and XP. Adding it is easy, so let's do that now.

To keep things as simple as possible, we define a single label portal for displaying the rank and XP. We'll use the same approach as we did on the "Basics" tab. In fact, we might as well clone the "baRank" portal from that tab and adapt for the summary panel. After we change the unique id, the only detail we need to modify is the style. All other particulars remain the same, resulting in the new portal shown below.

<portal
  id="smRank"
  style="lblSummary">
  <label>
    <labeltext><![CDATA[
      @text = herofield[acRankName].text & "  (" & herofield[acFinalXP].text & " XP)"
      ]]></labeltext>
    </label>
  </portal>

We now need to integrate the portal into the layout. We can add a "portalref" element and then simply auto-place the new portal beneath the "status" portal. In keeping with the policy we instituted a little bit ago, we also need to make sure that the new portal is not visible when the character is a creature. The revised layout should look like the following.

<layout
  id="smBasics">
  <portalref portal="smAttrib"/>
  <portalref portal="smDerived"/>
  <portalref portal="smStatus"/>
  <portalref portal="smRank"/>

  <position><![CDATA[
    ~the rank is only visible for non-creatures
    if (hero.tagis[Hero.Creature] <> 0) then
      portal[smRank].visible = 0
      endif

    ~position and size the tables to span the full layout
    perform portal[smAttrib].autoplace
    perform portal[smDerived].autoplace[20]
    perform portal[smStatus].autoplace[20]
    perform portal[smRank].autoplace[20]
    ]]></position>

  </layout>

Tailor Display Widths

There are a number of dynamic tables that present choices with lengthy descriptions. In order to better accommodate these descriptions, we need to increase the space afforded for showing those descriptions. This is controlled through the "descwidth" attribute on the table portals. The following changes should be made.

  • Skills table - increase to 350
  • Edges table - increase to 350
  • Hindrances table - increase to 350
  • Abilities table - increase to 350
  • Race chooser - increase to 350
  • Creature chooser - increase to 350

There are also a small number of other places where we can tweak the widths to better accommodate the information being displayed. These include the following:

  • Widen the "stRace" chooser on the static form by 10 pixels to accommodate all races
  • Widen the "arWpnThing" template on the "Armory" tab by 25 pixels
  • Widen the "arDefThing" template on the "Armory" tab by 25 pixels and shift the defensive value over the same 25 pixels
  • Widen the "adjust" portal within the "abAttrPick" template on the "Basics" tab by 10 pixels so that a bonus of "+10" (for creatures) doesn't get clipped. This requires also changing the alignment gaps of both the "adjust" and "value" portals to "-8".
  • Widen the "adjust" portal within the "skPick" template on the "Skills" tab by 5 pixels so that a bonus of "+10" doesn't get clipped. This requires also changing the alignment gap of the "adjust" portal to "6".
  • Widen the "attack" portal within the "smWeapon" template on the "Armory" summary panel by 10 pixels to show attacks better than "d12" (e.g. "d12+1")

Consistent Terminology with Rulebook

The Savage Worlds rulebook sometimes uses terminology that is different from what the Skeleton data files provide. When that occurs, we need to be sure to use the terminology that is consistent with the rulebook. We neglected to do that on the "Skills" tab, where we use the term "domain" instead of "focus".

We can easily change the label within the "skPick" template to show the term "Focus". However, if we modify the "Domain" component directly, we'll also change the behavior for domains with other game elements, such as hindrances. The proper way to customize the behavior of the "Domain" component is by defining a new tag within the "DomainTerm" tag group that has the proper term to be used. The new tag is shown below and must be defined within the "DomainTerm" tag group.

<value id="Focus"/>

We can then assign the tag to the "Skill" component, which results in all skills inheriting the proper term, as shown below.

<tag group="DomainTerm" tag="Focus"/>

There is one other place where we need to handle this properly. The advancements mechanism also prompts for domains, but it doesn't use skills directly. The various advancement things are used instead. So we need to assign the same tag to the advancement thing used for adding new skills (i.e. "advSkill").

After making these changes, we can reload the files and verify that all references to the term "domain" are correctly being mapped to "focus" for skills.

Description Text for Hindrances

The description text shown for hindrances should include the appropriate minor or major designation. The best way to address this is to handle it within the "Descript" procedure. By defining a new procedure for handling the custom aspects of hindrances, the "Descript" procedure can call the new procedure and synthesize everything properly

The new procedure must distinguish between things and picks. For picks, any user-selected severity must be shown. For things, the fact that a hindrance has a user-selectable severity must be indicated. The new procedure for hindrances is shown below. This procedure should be called when the "component.Hindrance" tag is encountered.

<procedure id="InfoHinder" context="info"><![CDATA[
  ~declare variables that are used to communicate with our caller
  var iteminfo as string
  iteminfo = ""

  ~center the severity rating
  iteminfo &= "{align center}"

  ~if the severity is user-selected, report that fact; otherwise, report
  ~whether the hindrance is major or minor
  if (!ispick + tagis[User.UserSelect] >= 2) then
    iteminfo &= "(Minor or Major)"
  elseif (field[hinMajor].value <> 0) then
    iteminfo &= "(Major)"
  else
    iteminfo &= "(Minor)"
    endif

  ~terminate the line and stop the centered alignment
  iteminfo &= "{br}{align left}"
  ]]></procedure>

Description Text for Arcane Backgrounds

Arcane backgrounds a conferred via the corresponding edge. Consequently, the arcane background itself is never shown anywhere, so we don't provide the user with any of the details regarding the various arcane backgrounds. We should display that the details of each arcane background within the description for the pertinent edges.

One solution would be to enter all the details for each background into the description for the edge. However, it would be much better to maintain the description text for the arcane backgrounds separately and pull that text into the description for the edge. We can accomplish this with a little bit of scripting.

We'll define a new procedure that synthesizes the description particulars of an edge. Then we can call this procedure from within the "Descript" procedure when an edge is being rendered. Within our new procedure, we can use a "foreach" statement to access the bootstraps assigned to the edge. We'll only access the arcane background things, and we'll integrate their description text appropriately. We can also use a nested "foreach" statement that pulls the details of any drawback associated with the arcane background. The resulting procedure should look like the one below.

<procedure id="InfoEdge" context="info"><![CDATA[
  ~declare variables that are used to communicate with our caller
  var iteminfo as string
  iteminfo = ""

  ~if this edge does not possess an arcane background, there's nothing special to do
  if (tagis[Arcane.?] = 0) then
    done
    endif

  ~output the particulars of the arcane background
  foreach bootstrap in this where "component.Arcane"

    ~append the description of the arcane background
    iteminfo &= eachthing.field[descript].text & "{br}"

    ~append any drawback associated with the arcane background
    foreach bootstrap in eachthing where "component.Drawback"
      iteminfo &= "{br}{b}" & eachthing.field[name].text & ":{/b} "
      iteminfo &= eachthing.field[descript].text & "{br}"
      nexteach

    nexteach
  ]]></procedure>

While we're in here, we should also identify the type of edge (e.g. background, social, etc.) within the description text. We can do that easily by inserting the following code before the check for an arcane background.

~report the type of edge
iteminfo &= "Type: " & tagnames[EdgeType.?] & " Edge{br}"

Description Text for Races

In the same way that we included the arcane background and drawback details above, the display of races should present the list of racial abilities. We'll define a new procedure for this purpose and integrate it into the "Descript" procedure when a race is being handled. This time, we'll do some special formatting of headers above the racial abilities and the racial description text itself. The resulting procedure is shown below.

<procedure id="InfoRace" context="info"><![CDATA[
  ~declare variables that are used to communicate with our caller
  var iteminfo as string
  iteminfo = ""

  ~setup formatting details for re-use below
  var setup as string
  var wrapup as string
  setup = "{align center}{b}{i}{text 79cfcd}"
  wrapup = "{text 010101}{/i}{/b}{br}{align left}"

  ~output the particulars of each linked racial ability
  iteminfo &= setup & "- Racial Abilities -" & wrapup & "{vert 4}"
  foreach bootstrap in this
    iteminfo &= "{b}" & eachthing.field[name].text & ":{/b} "
    iteminfo &= eachthing.field[descript].text & "{br}{br}"
    nexteach

  ~introduce the race description
  iteminfo &= setup & "- Racial Description -" & wrapup & "{vert -12}"
  ]]></procedure>