Miscellaneous Cleanup (Savage)

From HLKitWiki
Revision as of 02:39, 24 January 2009 by Rob (talk | contribs)
Jump to navigationJump to search

Context: HL Kit … Authoring Examples … Savage Worlds Walk-Through 

Overview

Based on our status assessment, we've got a lot of little things to tweak, so we might as well get them all handled and removed from our list.

Power Points Tracker

The "Power Points" tracker is always appearing on the "In-Play" tab, regardless of whether the character possesses an arcane background. We need to ensure that it only appears when a suitable arcane background is possessed. That would be any arcane background except for "Weird Science", since gizmos each have their own power points to be tracked.

There are three steps in applying this fix. The first is to modify the "ipTracker" portal that shows trackers to use a List tag expression. The Skeleton files pre-define a "Hide.Tracker" tag that we can use, so we change the tag expression to exclude any trackers that are expressly hidden.

The second step is to assign the "Hide.Tracker" tag to the "trkPower" thing. This will ensure that the tracker never appears in the table unless we specifically make it visible. We can make it visible by deleting the tag.

The final step is to modify the "Arcane" component to delete the tag. Since we want to delete the tag for all arcane backgrounds, we'll delete the tag within an Eval script on the component. That way, all arcane backgrounds inherit the behavior. We actually want to suppress the tracker for the "Weird Science" background, which we can identify via the "Arcane.WeirdSci" tag, so we'll have a special exception. This results in the script below.

<eval index="3" phase="Render" priority="5000"><![CDATA[
  if (tagis[Arcane.WeirdSci] = 0) then
    perform hero.child[trkPower].delete[Hide.Tracker]
    endif
  ]]></eval>

The tracker should now be hidden unless an appropriate arcane background is selected.

Abilities on "Special" Tab

The "Special" tab contains a list of all facets of the character that are designated for inclusion on the tab. After the name of an entry, the nature of that entry is shown within parentheses. For the various special abilities, they show two different natures. For example, edges will show both "Edge" and "Ability", while hindrances will show both "Hindrance" and "Ability". They should only be showing a single nature, and the "Ability" designation is extraneous.

If we take a look at the way the "Special" tab contents are handled, there is a "source" portal within the template. This portal uses the "tagnames" target reference and requests all tags belonging to the "SpecialTab" tag group. So the problem is that all of our abilities are being assigned two different "SpecialTab" tags.

When we modified the "Ability" component to be shared among the three different component sets (edges, hindrances, and racial abilities), we overlooked this behavior. Since the "Ability" component is shared, it should not possess its own "SpecialTab" tag. So the solution is to delete the "SpecialTab.Ability" tag from the file "tags.1st" and then modify the "Ability" component to no longer possess the tag. Once we make these two changes, abilities on the "Special" tab appear with only a single nature associated.

Names of Tracked Resources

We can now return to the "In-Play" tab to look at the presentation of tracked resources within the "ipTracker" template. Each resource is shown with its full name in a bold font. Many of the items shown in the resource list have names that are longer than will fit, so we need to handle them appropriately to maximize the chance that they do fit.

There are two different techniques that we can use, and there's no reason why we can't use both. The first thing is to realize that the majority of the items shown within the resource table possess a shorter name via the "shortname" field. Not all of them have this field, but we'll use it if it exists. This can be integrated by change the "name" portal to use a Label script, wherein the script checks for the presence of the "shortname" component and uses the field if available. The revised portal is shown below.

<portal
  id="name"
  style="lblNormal">
  <label>
    <labeltext><![CDATA[
      if (tagis[component.shortname] <> 0) then
        @text = field[shortname].text
      else
        @text = field[name].text
        endif
      ]]></labeltext>
    </label>
  </portal>

The second change is that we can shrink the font used for the name if the name doesn't fit. This entails using the "sizetofit" target reference on the portal, after which we must re-center the portal. The script code shown below can be added at the end of the Position script to accomplish this.

~shrink the name portal if it doesn't fit, then re-center after the shrink
perform portal[name].sizetofit[32]
perform portal[name].centervert

At this point, we'll be doing the best job we can of squeezing resources into the table for display.

Simplify Encumbrance Display

Another item on our task list is to modify the display of encumbrance. If the character has items that have a fractional weight, the total encumbrance shown has up to two decimal places, which just looks wrong. What we need is to show the encumbrance as a simple integer value.

If we show an integer value, the first instinct would be to round the value off normally. That would mean that a value of 14.4 would be rounded off to 14 and a value of 14.5 would become 15. While this works great in concept, it fails in a very important way. If the character maximum load is 40 and the character has gear totaling 40.1 pounds of weight, the encumbrance exceeds the limit and will be flagged as a problem, but the total encumbrance will be shown as 40 with normal rounding. To avoid problems like this, we need to always round the value upwards, so a value of 40.1 becomes 41 when rounded.

We only need to worry about the rounding for the value that is actually displayed to the user. The internal value should be maintained with maximum accuracy. The value displayed is synthesized within the second Eval script for the thing "resEncumb". We can revise this script to round the value before displaying it, which looks like the following.

~show the total weight carried and the maximum for the current load level
var spent as number
spent = round(field[resSpent].value,0,1)
field[resShort].text = spent & " / " & field[resMax].value

Encumbrance of Stacked Items

The mechanism for tallying encumbrance appears to have a problem. Items that possess a quantity of one are being added correctly to the overall encumbrance total. However, an item with a quantity greater than one is only adding its weight a single time. If the quantity is greater than one, the total accrued weight must be the individual weight of the item times the total quantity possessed.

The accrual of weight for encumbrance is handled within the second Eval script for the "Gear" component. Fixing this problem entails properly recognizing when the quantity is more than one and multiplying by the quantity. This can be resolved by modifying the Eval script to the one shown below.

~if this piece of gear is held by something else, ignore it - we'll get it below
if (isgearheld <> 0) then
  done
  endif

~if this piece of gear is a topmost holder, ignore it - it's not on the character
if (tagis[thing.holder_top] <> 0) then
  done
  endif

~determine the net weight of the gear, being sure to multiply by the quantity
var weight as number
weight = field[gearNet].value
if (stackable <> 0) then
  if (field[stackQty].value <> 1) then
    weight *= field[stackQty].value
    endif
  endif

~accrue the net weight of this piece of gear into the encumbrance resource
perform #resspent[resEncumb,+,weight,field[name].text]

Verify Trappings Are Changed

Each arcane power begins with trappings that list assorted possibilities from the rulebook. The user is expected to edit the trappings and specify the actual behaviors for each power. At present, there is no check in place for verifying it has been changed.

While we can't verify that the trappings are valid (it's arbitrary text), we can verify that the user has at least modified the field from its original contents. This is achieved via use of the "ischanged" target reference on the field.

The best way to handle this is by defining a new Eval Rule. If the rule is not satisfied, then a suitable error message can be output to the validation report. To ensure that the rule is applied equally to all arcane powers, we'll add the rule to the "Power" component. This results in the new Eval Rule below being defined.

<evalrule index="1" phase="Validate" priority="5000" 
    message="Trappings must be specified for the power"><![CDATA[
  if (field[powTraps].ischanged <> 0) then
    @valid = 1
    endif
  ]]></evalrule>

Names Within Summary Panels

There are a number of places within the summary panels where the names shown extend past the edge of the panel. It would be much better if the names were to fit fully within the available space. One option would be to widen the summary panels, but that would reduce the number of visible panels to one, so that's not a good idea. Many of the items shown within the summary panels have been converted to support "short names". As such, a better solution would be to make use of the shorter name within the summary panels whenever one is available.

Switching to the "shortname" field is quick and easy within the summary panels. There are four templates that can be switched. These templates show abilities, traits, weapons, and defensive equipment. Since all of these different items support "short names", we can safely change each of the templates to reference the "shortname" field instead of the "name" field. After that, the shorter name will be shown whenever one exists.

Knowledge Skills in Summary Panels

Within the "Abilities" summary panel, skills are shown with the appropriate name. However, skills that require a domain, such as "Knowledge", do not include the actual domain. It's important that the domain be included.

The first consideration we face is that the name of the "Knowledge" skill virtually fills the width of the summary panel when at its narrowest. Even if we appended the domain, it would be of no practical value to the user. We need to shorten the name that is displayed. Fortunately, this can be achieved if we utilize the "short names" mechanism that is already specified for traits. If we specify a "shortname" of "Knw", then we can readily include the domain within the name displayed.

The second issue is actually handling the domain properly. The "smTrait" template is used for attributes, derived traits, and skills. So we can't just assume that a domain may be present. We have to test for both the presence of a skill and the use of a domain. Only if both criteria are satisfied should we append the domain to the name. We need to use a Label script to synthesize the proper results, so we'll change the "name" portal within the template to the following.

<portal
  id="name"
  style="lblSummary">
  <label>
    <labeltext><![CDATA[
      @text &= field[shortname].text
      if (tagis[component.Skill] + tagis[User.NeedDomain] >= 2) then
        @text &= ": " & field[domDomain].text
        endif
      ]]></labeltext>
    </label>
  <mouseinfo/>
  </portal>

Arcane Power Names

Within Savage Worlds, players are encouraged to come up with their own names for the arcane powers possessed by their characters. The current data files don't allow for this. So we need to allow users to name the arcane powers for a character. Given that an arcane power could have a name that is too long to fit in the available space on the "Arcane" tab, we need shrink the name if necessary. In addition, it would ideal to show the original name of the power in parentheses beneath the user name and with a softer presentation so that it is clearly secondary to the user-assigned name.

Designating arcane powers as namable by the user is the first step. This is accomplished by assigning the "usernamable" attribute for the "Power" component. As soon as this attribute is set to "yes", all powers can thereafter be named by the user via the right-click menu.

The next step entails adding the original name in parentheses beneath the user-assigned name. The "name" portal within the "apPower" template displays the name for an arcane power. One option is to change the portal to be multi-line and use a Label script to synthesize the output. This script can start with the current name and append the original name only if the user has named the power. The problem with that approach is that we can't use the "sizetofit" mechanism on such a portal. So we're better off creating a second portal and managing the two.

In order to add the second portal, we'll need to define a new style. We need to use a font that is left-aligned, of moderate size, not bold, and in a less prominent color. We can define just such a style as shown below.

<style
  id="lblOldName">
  <style_label
    textcolor="808080"
    font="fntsummary"
    alignment="left">
    </style_label>
  </style>

Now that the style is in place, we can define the new portal. It needs to utilize a Label script so that the original name can be placed within parentheses. Other than that, it's extremely simple and looks like the portal shown below.

<portal
  id="original"
  style="lblOldName">
  <label>
    <labeltext><![CDATA[
      @text = "(" & field[thingname].text & ")"
      ]]></labeltext>
    </label>
  </portal>

The final task is to revised the Position script to properly position the two portals. With the script, we also need to utilize the "sizetofit" target reference on both portals. This will ensure that long names, whether the original power name or the user-assigned name, will fit appropriately into the space available. Since the majority of power names are shorter than the space we've currently allocated, we'll also shrink that space a little bit (from 200 to 175). The updated script is presented below.

~set up our height based on our tallest portal
~Note: Since the portal is a script-based label, it will not have any contents
~when the height of the template is initially determined for general sizing.
~Consequently, we can't use "textheight" here and must use "fontheight" instead.
height = portal[details].fontheight * 3

~if this is a "sizing" calculation, we're done
if (issizing <> 0) then
  done
  endif

~position our tallest portal at the top
portal[details].top = 0

~determine whether the original name needs to be shown (only if renamed)
portal[original].visible = !field[username].isempty

~center the info and delete portals within the upper half of the template
var half as number
half = height / 2
portal[info].top = (half - portal[info].height) / 2
portal[delete].top = (half - portal[delete].height) / 2

~center the trappings portal within the lower half of the template
~Note: The info portal is big, so shift it down a little extra for better spacing.
portal[trappings].top = half + (half - portal[trappings].height) / 2 + 1

~center the name vertically if we don't need the original name; otherwise,
~position the two names above and below each other
if (portal[original].visible = 0) then
  perform portal[name].centervert
else
  portal[name].top = (half - portal[name].height) / 2 + 3
  perform portal[original].alignrel[ttob,name,2]
  endif

~position the delete portal on the far right
perform portal[delete].alignedge[right,0]

~position the info portal to the left of the delete button
perform portal[info].alignrel[rtol,delete,-6]

~center the trappings portal between the info and delete buttons
var span as number
span = portal[delete].right - portal[info].left
portal[trappings].left = portal[info].left + (span - portal[trappings].width) / 2

~position the name portals on the left and use a suitable amount of space
portal[name].left = 0
portal[name].width = 175
portal[original].left = 15
portal[original].width = portal[name].width - portal[original].left

~position the details next to the name and use the remaining space
perform portal[details].alignrel[ltor,name,10]
portal[details].width = portal[info].left - portal[details].left - 10

~shrink the name portals if they don't fit, then re-align after the shrink
perform portal[name].sizetofit[32]
if (portal[original].visible = 0) then
  perform portal[name].centervert
else
  perform portal[original].sizetofit[32]
  portal[name].top = (half - portal[name].height) / 2 + 3
  perform portal[original].alignrel[ttob,name,2]
  endif

Revise Arcane Power Display

The trappings for an arcane power are noticeably missing from the display for each power on the "Arcane" tab. The original reason for this was that we didn't have the space, which was true at the time. However, we've now added the smarts to shrink the name when necessary, plus we've reduced the space for the name a little bit. If needed, we can reduce that space a bit further. We can also reduce the font size slightly for the various facets of each power, which should give us adequate room to fit both the point cost and range on the top line. This will free up the space to add a third line at the bottom that shows the trappings for the power.

The most obvious solution would be to revise the Label script for the "details" portal to put the range on the same line as the cost. This works great, except that we have no way of handling the situation where the range extends past the right edge. Since we are using a multi-line label portal, if the text extends past the edge on one line, that line is simply wrapped to the new one. What we need is for the right edge to simply be clipped if it is too long. The only way to handle that is by using a single-line label.

This means that we need to carve up our current "details" portal into two separate portals - one for the cost and range, plus another for the duration. We're also going to need to add a third portal for the trappings. Once we add another portal for the trappings, we'll have two portals conveying the same information, so we really should distinguish them clearly. It makes more sense to rename the current "trappings" portal to "trapedit" to indicate its use for editing the trappings, so we'll do that first.

After completing the rename operation, we can then add our two new portals. We can clone the current "details" portal to create a "duration" portal and a "trappings" portal. The Label script for each can be modified easily to output the appropriate information. We also need to modify the "details" portal to eliminate the duration and move the range up to the same line, leaving a horizontal gap between the two. Don't forget that all of these portals must be changed to be single-line portals, so remove the "ismultiline" attribute from each. This results in the three portals shown below.

<portal
  id="details"
  style="lblSmlLeft">
  <label>
    <labeltext><![CDATA[
      @text = "{/b}Points:{b} " & field[powPoints].text
      @text &= "{horz 20}{/b}Range:{b} " & field[powRange].text
      ]]></labeltext>
    </label>
  </portal>

<portal
  id="duration"
  style="lblSmlLeft">
  <label>
    <labeltext><![CDATA[
      @text = "{/b}Duration:{b} " & field[powLength].text
      if (field[powMaint].isempty = 0) then
        @text &= " (" & field[powMaint].text & ")"
        endif
      ]]></labeltext>
    </label>
  </portal>

<portal
  id="trappings"
  style="lblSmlLeft">
  <label>
    <labeltext><![CDATA[
      @text = "{/b}Trappings:{b} " & field[powTraps].text
      ]]></labeltext>
    </label>
  </portal>

We now need to integrate these portals into the positioning logic for the template. Our total height of the template is now the combined height of the three portals. We need to position these portals beneath each other along the vertical axis, and they need to span the same region horizontally. Making these changes yields a revised Position script that looks like the following.

~set up our height based on our tallest vertical span, which is the three
~portals for the power details
height = portal[details].height + portal[duration].height + portal[trappings].height

~if this is a "sizing" calculation, we're done
if (issizing <> 0) then
  done
  endif

~determine whether the original name needs to be shown (only if renamed)
portal[original].visible = !field[username].isempty

~center the info and delete portals within the upper half of the template
var half as number
half = height / 2
portal[info].top = (half - portal[info].height) / 2
portal[delete].top = (half - portal[delete].height) / 2

~center the trappings edit portal within the lower half of the template
~Note: The info portal is big, so shift it down a little extra for better spacing.
portal[trapedit].top = half + (half - portal[trapedit].height) / 2 + 1

~center the name vertically if we don't need the original name; otherwise,
~position the two names above and below each other
if (portal[original].visible = 0) then
  perform portal[name].centervert
else
  portal[name].top = (half - portal[name].height) / 2 + 3
  perform portal[original].alignrel[ttob,name,2]
  endif

~position the power details portals vertically
portal[details].top = 0
perform portal[duration].alignrel[ttob,details,0]
perform portal[trappings].alignrel[ttob,duration,0]

~position the delete portal on the far right
perform portal[delete].alignedge[right,0]

~position the info portal to the left of the delete button
perform portal[info].alignrel[rtol,delete,-6]

~center the trappings edit portal between the info and delete buttons
var span as number
span = portal[delete].right - portal[info].left
portal[trapedit].left = portal[info].left + (span - portal[trapedit].width) / 2

~position the name portals on the left and use a suitable amount of space
portal[name].left = 0
portal[name].width = 175
portal[original].left = 15
portal[original].width = portal[name].width - portal[original].left

~position the details portals next to the name and use the remaining space
perform portal[details].alignrel[ltor,name,10]
portal[details].width = portal[info].left - portal[details].left - 10
portal[duration].left = portal[details].left
portal[duration].width = portal[details].width
portal[trappings].left = portal[details].left
portal[trappings].width = portal[details].width

~shrink the name portals if they don't fit, then re-align after the shrink
perform portal[name].sizetofit[32]
if (portal[original].visible = 0) then
  perform portal[name].centervert
else
  perform portal[original].sizetofit[32]
  portal[name].top = (half - portal[name].height) / 2 + 3
  perform portal[original].alignrel[ttob,name,2]
  endif

This is looking pretty good, but the various details portals look a bit too large and cramped. It would look better if we shrank the font size a little bit. This will require that we define a new style. Looking at the style and font that are currently used, we can create a new style that has similar characteristics but with a smaller font. The new style should look like the one below.

<style
  id="lblSmPower">
  <style_label
    textcolor="f0f0f0"
    font="fntsmpower"
    alignment="left">
    </style_label>
  <resource
    id="fntsmpower">
    <font
      face="Arial"
      size="34">
      </font>
    </resource>
  </style>

After we switch our three portals over to using this new style, the display looks significantly better. The one thing that looks poor is that the default trappings almost always run off the end. Since those trappings are just to provide suggestions to a player, we really shouldn't even be showing them here. What we should ideally be showing is a message that tells the user to specify the appropriate trappings by clicking on the button at the right. We already treat unchanged trappings as a validation error, so we would be consistent if we displayed the message in red. This change can be easily made within the Label script for the "trappings" portal, which will end up looking like the code below.

@text = "{/b}Trappings:{b} "
if (field[powTraps].ischanged = 0) then
  @text &= "{text ff0000}Use button at right to specify"
else
  @text &= field[powTraps].text
  endif

That's all there is to it. We've now got arcane powers working very smoothly.

Show Gear in Two Columns

The table on gear on the "Gear" tab uses a single column. The vast majority of gear have names that are relatively short, which results in a large gaps of empty space for each item in the table. This requires users to scroll through the list to see and access all their gear. Switching to a two-column table would work much better.

The first step is to change the "grGear" table portal to show only two columns. Now we need to revise the "grGrPick" template to get everything to fit better with the new design. We'll start by shrinking the horizontal margin down to two. Next, we'll shrink the gap between the buttons on the right from eight to two pixels. This is a big help, but some names are still too long. So the final step is to add code to shrink the name if it is too big, after which we must re-center the name vertically. The various effected lines of the Position script are shown below.

~position the info portal to the left of the delete button
perform portal[info].alignrel[rtol,delete,-2]

~position the gear portal to the left of the info button
perform portal[gearmanage].alignrel[rtol,info,-2]

....

~shrink the name to fit the available space if it's too big, then re-center
perform portal[name].sizetofit[30]
perform portal[name].centervert

Showing Power for Weird Science

The "static" form at the top shows the power points for a character with an arcane background. This includes both the total points and the current points. However, if the character possesses the "Weird Science" background, each gizmo has its own points and the character itself has only a total number of points. So we must change the display to only show the total points for such as character.

The information shown within the form is pulled from the "acPPSumm" field on the actor. This field synthesizes the displayed text via a Finalize script. We can change that script to detect a character with "Weird Science" and adjust the result accordingly. The revised script should look like the one below.

~if the character uses weird science, only show total power points
@text = ""
if (hero.tagis[Arcane.WeirdSci] = 0) then
  @text = hero.child[trkPower].field[trkLeft].value & " / "
  endif
@text &= hero.child[trkPower].field[trkMax].value