Difference between revisions of "Final Cleanup (Savage)"

From HLKitWiki
Jump to: navigation, search
(Identify Major Hindrances in Sheet Output)
(Identify Hindrance Severity in Sheet Output)
Line 248: Line 248:
 
<pre>
 
<pre>
 
var major as string
 
var major as string
if (tagis[component.Hindrance] + field[hinMajor].value >= 2) then
+
if (tagis[component.Hindrance] <> 0) then
  major = " {font Wingdings}" & chr(181) & "{revert}"
+
  if (field[hinMajor].value <> 0) then
 +
    major = " {font Wingdings}" & chr(181) & "{revert}"
 +
    endif
 
   endif
 
   endif
 
@text = field[shortname].text & major & "{/b}  {size 32}" & field[summary].text
 
@text = field[shortname].text & major & "{/b}  {size 32}" & field[summary].text

Revision as of 16:58, 8 February 2009

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 do another 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.

New Characters Report Wrong Attribute Points

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>

Widen Description 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

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>

Identify Hindrance Severity in Sheet Output

The list of hindrances that are output within the character sheet use the same mechanism as edges and racial abilities. However, hindrances can be taken in both major and minor forms, and that aspect of each hindrance is important to the player.

We can modify the Label script currently in use within the "oAbilPick" template to detect the presence of a hindrance and output it specially. We'll only flag major hindrances, and we'll do it by including a special symbol. This will keep the space requirements to an absolute minimum. We can pick a suitable character from the "Wingdings" font, resulting in the following revised Label script.

var major as string
if (tagis[component.Hindrance] <> 0) then
  if (field[hinMajor].value <> 0) then
    major = " {font Wingdings}" & chr(181) & "{revert}"
    endif
  endif
@text = field[shortname].text & major & "{/b}  {size 32}" & field[summary].text

Next