Allies (Savage)

From HLKitWiki
Revision as of 01:55, 28 January 2009 by Rob (Talk | contribs) (Status Tracking)

Jump to: navigation, search

Context: HL KitAuthoring Examples … Savage Worlds Walk-Through 

Overview

The Savage Worlds game system emphasizes the use of allies. As such, our data files should provide for the creation and management of allies associated with the characters.

Setting Up Ally Support

Allies are essentially independent characters that are children of the main characters. The Kit provides a framework for handling such characters very easily via the "minion" mechanism. Minions work very similarly to gizmos. They are attached to a character via a thing, and the minion is automatically added to the character whenever a pick based on that thing is added. Deleting the pick also deletes the minion. The parent character of a minion is referred to as the "master".

Just about any "thing" can have a minion attached. However, we want to be able to readily identify the picks that add our allies from other picks. We'll define a new component and component set to accomplish this. We'll assign the component a unique id of "Ally" and have it automatically define a component set with the same id, since we don't need to re-use any other special behaviors.

It would also be useful to let the user enter some arbitrary notes about each ally that can be used to readily identify them. For this, we'll include a field on the component where we can store those details. This results in a component set that looks like below, which we can add to the file "miscellaneous.str".

<component
  id="Ally"
  name="Ally">

  <!-- Brief summary of ally for display in the list of allies -->
  <field
    id="alySummary"
    name="Summary"
    type="user"
    maxlength="100">
    </field>

  </component>

Defining the Minion

We can now define a thing based on our new component set. We can add the thing to the file "thing_miscellaneous.dat". The only item of note about this thing is that the the minion will be attached by it.

Associating a minion with a thing is accomplished via the "minion" child element. Each minion needs to be assigned a unique id, which makes it possible to identify different minions when multiple types of minions are added to a character. We'll only have one type of minion, but we need to assign a unique id anyway.

The other important facet of our minion is that we want it to inherit all of the settings associated with the master character. For example, if the master has only the "Futuristic" time period selected, then we want to assume that the minion has the same behaviors. This is achieved via the "isinherit" attribute within the "minion" element.

Putting this all together, we end up with a thing definition that looks like the following.

<thing
  id="mscAlly"
  name="Ally"
  compset="Ally">

  <minion
    id="ally"
    isinherit="yes">
    </minion>

  </thing>

Manipulating Allies

We now need to figure out how to let users add and manage allies. We could add allies to an existing tab, but none of them really seem appropriate. There also is a space consideration, as many of our tabs are already quite packed with information. Since we only have a rather small number of tabs, it would be quite reasonable to add another tab for tracking allies.

When adding our tab, we'll want something very simple. We'll have a single table on the tab where the user can add and access allies. The "Skills" tab is very similar, so we'll copy the file "tab_skills.dat" as "tab_allies.dat" and then adapt the file to our needs.

The first thing we need to do is revise the table portal at the top. All allies will be attached to the character via the same "mscAlly" thing that we defined above. Consequently, we need to utilize an "auto" table that automatically adds a new pick based on a specific thing instead of prompting the user to select a thing. This requires that we specify the thing id to be used. We also need to utilize a custom template for showing the contents of each ally. The resulting portal should look like the one shown below.

<portal
  id="alAllies"
  style="tblNormal">
  <table_auto
    component="Ally"
    showtemplate="alPick"
    autothing="mscAlly">
    <headertitle><![CDATA[
      @text = "Allies Associated with Character"
      ]]></headertitle>
    <additem><![CDATA[
      @text = "Add a New Ally to the Character"
      ]]></additem>
    </table_auto>
  </portal>

For the moment, we'll keep the template very simple. We'll start with just the name of the pick, plus the standard info and delete portals. We'll come back in a moment to refine the template and make it more useful. This yields a template like the one below.

<template
  id="alPick"
  name="Ally Pick"
  compset="Ally"
  marginhorz="3"
  marginvert="2">

  <portal
    id="name"
    style="lblNormal"
    showinvalid="yes">
    <label
      field="name">
      </label>
    </portal>

  <portal
    id="info"
    style="actInfo">
    <action
      action="info">
      </action>
    <mouseinfo/>
    </portal>

  <portal
    id="delete"
    style="actDelete"
    tiptext="Click to delete this item">
    <action
      action="delete">
      </action>
    </portal>

  <position><![CDATA[
    ~set up our height based on the tallest portal
    height = portal[info].height

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

    ~position our tallest portal at the top and center other portals vertically
    portal[info].top = 0
    perform portal[name].centervert
    perform portal[delete].centervert

    ~position the delete and info portals on the far right
    perform portal[delete].alignedge[right,0]
    perform portal[info].alignrel[rtol,delete,-8]

    ~position the name on the left
    portal[name].left = 0
    ]]></position>

The next step is to revise the layout to show allies. All that entails is a switch to the new ids, which looks like below.

<layout
  id="allies">
  <portalref portal="alAllies" taborder="10"/>

  <position><![CDATA[
    ~position and size the table to span the full layout; it will only use the
    ~vertical space that it actually needs
    perform portal[alAllies].autoplace
    ]]></position>

  </layout>

The final step is to modify the panel. We'll position the new tab between the "Personal" and "Journal" tabs, which means we need to assign it an order of 315. This yields the following panel.

<panel
  id="allies"
  name="Allies"
  marginhorz="5"
  marginvert="5"
  order="315">
  <layoutref layout="allies"/>
  <position><![CDATA[
    ]]></position>
  </panel>

We can now give things try. Reload the data files and you should see the new "Allies" tab. On the tab is a table, and clicking on the "add item" of the table automatically adds a new ally pick to the character. When this happens, you should also see a new character appear on the Dashboard. This is our new ally.

If you switch to ally via the Dashboard, you can see that the ally is a standard character. You can also verify that the various settings associated with the master character are properly inherited into the minion. At the top left of the minion, next to the name, a button should appear. This button allows you to quickly return to the master of the minion by clicking on it. Click the button and you should again be looking at the master character. Now delete the ally pick from the table, at which point our minion disappears. The basics of allies are now operational.

Revising the Template

We should now do something more useful that just show the name of our allies. One thing that would be extremely useful is to add a button that lets the user go directly to a particular ally. We can always rely on the Dashboard for this, but a button next to each ally would be much nicer.

We want to accomplish the exact same behavior as the Dashboard, and we should probably use the exact same button for consistency. So take a look at how the Dashboard accomplishes this. It uses a special action portal that handles all the mechanics automatically. We'll copy the portal into our template and re-use all that same logic.

When we defined the "Ally" component, we included a field where the user can specify details about the character. We should show an edit portal next to the name that allows the user to edit those details. We'll size the edit portal based on whatever space exists between the ally name and the "info" portal on the right.

This results in a revised template that looks like the one below.

<template
  id="alPick"
  name="Ally Pick"
  compset="Ally"
  marginhorz="3"
  marginvert="2">

  <portal
    id="load"
    style="actLoad"
    tiptext="Click here to make this the active character.">
    <action
      action="minion">
      </action>
    </portal>

  <portal
    id="name"
    style="lblNormal"
    showinvalid="yes">
    <label
      field="name">
      </label>
    </portal>

  <portal
    id="summary"
    style="editNormal">
    <edit
      field="alySummary">
      </edit>
    </portal>

  <portal
    id="info"
    style="actInfo">
    <action
      action="info">
      </action>
    <mouseinfo/>
    </portal>

  <portal
    id="delete"
    style="actDelete"
    tiptext="Click to delete this item">
    <action
      action="delete">
      </action>
    </portal>

  <position><![CDATA[
    ~set up our height based on the tallest portal
    height = portal[info].height

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

    ~position our tallest portal at the top and center other portals on it
    portal[info].top = 0
    perform portal[name].centeron[vert,info]
    perform portal[delete].centeron[vert,info]
    perform portal[load].centeron[vert,info]

    perform portal[summary].alignrel[btob,name,2]

    ~position the delete and info portals on the far right
    perform portal[delete].alignedge[right,0]
    perform portal[info].alignrel[rtol,delete,-8]

    ~position the load portal on the left, with the name and summary adjacent
    portal[load].left = 0
    perform portal[name].alignrel[ltor,load,8]
    perform portal[summary].alignrel[ltor,name,10]
    portal[summary].width = portal[info].left - 10 - portal[summary].left
    ]]></position>

  </template>

If we reload the files, we can use the button next to the name to go directly to a given ally, plus we can enter notes about the ally for easy access and viewing.

Recap Summary

Showing the name of each ally and a few summary notes is of limited use. What would be ideal is if we could actually show a detailed summary of each ally, much like the contents of a statblock. We could easily show this summary beneath the current information for each ally. However, we need to have the summary available.

We could generate the summary on-the-fly via a Label script. However, we may also want to show the recap else where. For example, within the mouse-info for the ally. In order to make sure we can have access to the recap from various places, we need to synthesize the results into a field. This field can be added to the "Actor" component and generated via an Eval script.

The contents of the recap summary should be as compact as possible. Consequently, we'll minimize spacing and punctuation to the minimum necessary. We'll also use abbreviations and short names wherever possible. The resulting field and script are demonstrated below.

<field
  id="acRecap"
  name="Recap Summary"
  type="derived"
  maxlength="2000">
  </field>

<eval index="5" phase="Render" priority="10000"><![CDATA[
  var txt as string
  var recap as string

  ~output any race
  txt = hero.firstchild["Race.?"].field[name].text
  if (empty(txt) = 0) then
    recap &= txt & ", "
    endif

  ~output the XP and rank
  var rankvalue as number
  var ranktext as string
  rankvalue = herofield[acRank].value
  call RankName
  recap &= ranktext & "  (" & #resmax[resXP] & " XP)"

  ~output attributes
  foreach pick in hero where "component.Attribute & !Hide.Attribute"
    recap &= ", " & eachpick.field[trtAbbrev].text & " " & eachpick.field[trtDisplay].text
    nexteach

  ~output derived traits
  foreach pick in hero where "component.Derived & !Hide.Trait" sortas explicit
    recap &= ", " & eachpick.field[trtAbbrev].text & " " & eachpick.field[trtDisplay].text
    nexteach

  ~output special abilities
  foreach pick in hero where "component.Ability" sortas SpecialTab
    recap &= ", " & eachpick.field[shortname].text
    nexteach

  ~output arcane powers
  foreach pick in hero where "component.Power"
    recap &= ", " & eachpick.field[name].text
    nexteach

  ~output skills
  foreach pick in hero where "component.Skill & !Hide.Skill"
    recap &= ", " & eachpick.field[trtAbbrev].text
    if (eachpick.tagis[User.NeedDomain] <> 0) then
      recap &= " (" & eachpick.field[domDomain].text & ")"
      endif
    recap &= " " & eachpick.field[trtDisplay].text
    nexteach

  ~save the final contents
  field[acRecap].text = recap
  ]]></eval>

Now that we've got the field being synthesized, we can put it to use. We need to add a new portal to the template for displaying the field. We'll allocate three lines of text to the recap for each ally, which should allow us to show a handful of allies at a time when the main window is at its smallest height. In the interest of keeping things as tight as possible, we'll also decrease the spacing between lines by a one pixel. This results in the following portal.

<portal
  id="recap"
  style="lblSmlLeft">
  <label
    ismultiline="yes">
    <labeltext><![CDATA[
      ~output the recap field, but squeeze the line spacing a little bit
      @text = "{leading -1}" & minion.herofield[acRecap].text
      ]]></labeltext>
    </label>
  </portal>

We need to factor the height of the new portal into our overall height for the template. Once that's done, we can then place the portal beneath the current line of portals, leaving a margin on each side to set it off better and clearly break up individual allies in the table. The pertinent changes and additions to the Position script are shown below.

~set up our height based on our full extent
height = portal[summary].height + 5 + portal[recap].fontheight * 3
~position the recap portal beneath the top line and limit it to 3 lines
perform portal[recap].alignrel[ttob,summary,3]
portal[recap].lineheight = 3

~position and size the recap horizontally
perform portal[recap].alignrel[ltol,name,0]
portal[recap].width = portal[delete].left - 10 - portal[recap].left

~resize the contents of the recap portal if needed and ensure a 3-line height
perform portal[recap].sizetofit[30]
portal[recap].lineheight = 3

After reloading the data files, the recap text is quite helpful. Unfortunately, it's also still a bit too big and bold. It's competing with the primary information for each ally instead of being clearly supporting information. We need to switch to a different style that uses a smaller font and a less intense color. We could use a soft grey, but that's a little too subtle, so we'll choose a soft cyan instead. This yields the new style shown below, which can be swapped into use by the portal.

<style
  id="lblNotes">
  <style_label
    textcolor="99efed"
    font="fntnotes"
    alignment="left">
    </style_label>
  <resource
    id="fntnotes">
    <font
      face="Arial"
      size="34">
      </font>
    </resource>
  </style>

Refinements

Allies are basically working, but there are still a few things we should clean up. First of all, the mouse-info text shown for each ally is just the standard name and description text. We should really show the full recap information for the ally, since the three lines of space we've allocated may not be enough in some cases. This requires that we change the MouseInfo script from using the default behavior to more appropriate custom behavior.

We don't want to replace the standard behavior entirely, though. What we want is to append additional information at the end of the standard material. To accomplish this, we'll call the "MouseInfo" procedure to get the standard information and then append our own data at the end. This yields a new MouseInfo script that looks like the following.

<mouseinfo><![CDATA[
  var mouseinfo as string
  call MouseInfo
  @text = mouseinfo & "{br}{br}{b}Ally Summary:{/b}{br}" & minion.herofield[acRecap].text
  ]]></mouseinfo>

Another issue with our implementation can be seen when we switch to an ally. The "Allies" tab is visible for our allies, which means that we can theoretically add allies to our allies, and those allies can have their own allies as well. While this is technically valid, it doesn't make sense within the context of a Savage Worlds game. So we need to hide the "Allies" tab for allies.

Hiding the "Allies" tab in general is easy, and we've done it before. First, we define a new "HideTab.allies" tag. Then we add a Live tag expression to the panel that verifies the tag doesn't exist on the character. But how do we get the tag onto the character properly?

Remember that minions work very much like gizmos. We can assign tags to a child entity within the definition by use of the "tag" element on the entity. We can do the same within our "minion" element. All we need to do is an additional line to our thing, which ends up looking like the following.

<thing
  id="mscAlly"
  name="Ally"
  compset="Ally">

  <minion
    id="ally"
    isinherit="yes">
    <tag group="HideTab" tag="allies"/>
    </minion>

  </thing>

If we reload the files now, we'll see the "Allies" tab appear normally for our main characters, but it disappears when we're manipulating an ally.

Printing Allies

Allies are now working nicely. Each ally can be printed out on a separate character sheet, just like a normal character. However, it would be much more convenient to print a compact representation of each ally with the main character. We don't have room for allies on the first page of the character sheet, but we should be able to add them without any problem on the second page.

We'll add allies after everything else is output for the character. We can define a new table of allies and integrate it easily into the layout for the second sheet. In addition to printing all the basic details for each ally, we also need to include a separate space to track damage, power points, and ammo for each ally.

The Table Portal

Our table portal is a little bit different from the ones we normally define. We need our table to list all minions of the character. This can be done by operating on all picks in the "Ally" component, just like we did in the table on the tab. However, there is a better solution. We can instead specify that we want to list all minions via the "showpicks" attribute. This establishes the "actor" pick for the actual minion as the context for each item in the table, and that means that we can use the "hero" context readily within Label scripts in the template's portals.

For the sort set, we'll use the pre-defined "_ActorSeq_" sequence, which outputs the minions in an appropriate order for the character. Since our minions will not all be the same height, we need to specify that our height varies. Lastly, we don't want a header above the table. Instead, we'll draw our own header above each item in the table so that each minion looks like its own entry to the user. Consequently, we want to insert a gap between each item in the table, so we specify the "showgapy" attribute.

The net result of all of this is a table portal that looks like the following.

<portal
  id="oAlly"
  style="outNormal">
  <output_table
    component="Ally"
    showtemplate="oAllyPick"
    showpicks="minion"
    showsortset="_ActorSeq_"
    varyheight="yes"
    showgapy="25">
    </output_table>
  </portal>

Layout Integration

Integrating our new portal into the layout is trivial. We simply add a new "portalref" for the portal and then add a suitable "autoplace" of the portal within the Position script. The revised layout looks like the following.

<layout
  id="oStandard2">
  <portalref portal="oPower"/>
  <portalref portal="oArmor"/>
  <portalref portal="oWeapon"/>
  <portalref portal="oGear"/>
  <portalref portal="oAlly"/>
  <position><![CDATA[
    ~position the various tables in the desired sequence
    perform portal[oPower].autoplace
    perform portal[oArmor].autoplace
    perform portal[oWeapon].autoplace
    perform portal[oGear].autoplace
    perform portal[oAlly].autoplace

    ~the height of the layout is the bottommost extent of the elements within
    height = autotop
    ]]></position>
  </layout>

The Ally Template

The template itself will consist of four pieces that will each be handled by a separate portal. At the top of each ally, we'll display a title that looks like a standard title above a table. Beneath the title, we'll output all of the details for the character, including traits, abilities, and gear. At the bottom, we'll present a convenient place for tracking health and other status for the character. Between the details and tracking sections, we'll insert a solid line as a separator.

The overall template will look like the one shown below. This template omits the contents of the Label scripts that will be employed for three of the portals. Those contents will be discussed in the following sections.

<template
  id="oAllyPick"
  name="Output Allies Table"
  compset="Actor">

  <portal
    id="name"
    style="outTitle">
    <output_label>
      <labeltext><![CDATA[
        ~insert script here
        ]]></labeltext>
      </output_label>
    </portal>

  <portal
    id="details"
    style="outAlly">
    <output_label>
      <labeltext><![CDATA[
        ~insert script here
        ]]></labeltext>
      </output_label>
    </portal>

  <portal
    id="line"
    style="outGreyBox">
    <output_label
      text=" ">
      </output_label>
    </portal>

  <portal
    id="tracking"
    style="outAlly">
    <output_label>
      <labeltext><![CDATA[
        ~insert script here
        ]]></labeltext>
      </output_label>
    </portal>

  <position><![CDATA[
    ~our name spans the entire width and is limited to a single line in height
    portal[name].width = width
    portal[name].lineheight = 1

    ~position the details beneath the name, spanning the full width
    perform portal[details].alignrel[ttob,name,10]
    portal[details].width = width
    perform portal[details].autoheight

    ~position the line beneath the details, spanning the full width; we set the
    ~height to double the border size so that no contents are visible and we
    ~end up with simply a solid horizontal line
    perform portal[line].alignrel[ttob,details,8]
    portal[line].width = width
    portal[line].height = portal[line].bordersize * 2

    ~position the tracking beneath the line, spanning the full width
    perform portal[tracking].alignrel[ttob,line,5]
    portal[tracking].width = width
    perform portal[tracking].autoheight

    ~our final height is the bottom of the template contents
    height = portal[tracking].bottom
    ]]></position>
  </template>

Showing the Title

The "title" portal must show a suitable name for ally. Each minion has a name, and that name is initialized to the name of the anchor pick for the minion. Consequently, if the user has not changed the actor's name, the two will match. If the name has been changed, we want to designate the nature of the minion in parentheses after that name. If the name has not been changed, we need to omit the nature, else we'll end up duplicating the default name twice (e.g. "Ally (Ally)").

We'll start with the name of the actor. If the name differs from the default name (accessed via the "miniontext" target reference), then we'll append the nature. This results in the Label script shown below.

@text = hero.actorname
if (empty(hero.miniontext) = 0) then
  if (compare(hero.miniontext,@text) <> 0) then
    @text &= " (" & hero.miniontext & ")"
    endif
  endif

Character Details

The core details for the character are rather extensive. We need to show the basics like the name and rank, as well as the attributes, derived traits, abilities, and skills. To ensure that the ally is highly usable during play, we also need to include arcane powers, weapon details, armor, and gear.

The output format is very simple and compact. Each category of information is identified by an indicator in bold. The actual information follows in plain text. Each new category starts on a new line so that everything is cleanly organized and easy for the user to access whatever information he wants.

At the very top of the details section, we include any notes that the user assigned to the ally. Since our script starts with the "actor" pick of the ally as its initial context, we need to access the anchor pick that attaches the minion. Once we have the anchor pick, we can then access the appropriate field to include in our output.

The complete script to synthesize all the character details is shown below.

var txt as string
var ismore as number

~output any notes for the ally
if (anchor.field[alySummary].isempty = 0) then
  @text &= "{b}Notes:{/b} " & anchor.field[alySummary].text & "{br}"
  endif

~output any race
txt = hero.firstchild["Race.?"].field[name].text
if (empty(txt) <> 0) then
  txt = "-none-"
  endif
@text &= "{b}Race:{/b} " & txt

~output the rank and XP
var rankvalue as number
var ranktext as string
rankvalue = herofield[acRank].value
call RankName
@text &= "; {b}" & ranktext & "{/b} ("
@text &= hero.child[resXP].field[resMax].text & " XP)"
@text &= "{br}"

~output attributes
@text &= "{b}Attributes:{/b} "
ismore = 0
foreach pick in hero where "component.Attribute & !Hide.Attribute"
  if (ismore <> 0) then
    @text &= ", "
    endif
  @text &= eachpick.field[trtAbbrev].text & " " & eachpick.field[trtDisplay].text
  ismore = 1
  nexteach
@text &= "{br}"

~output derived traits
@text &= "{b}Traits:{/b} "
ismore = 0
foreach pick in hero where "component.Derived & !Hide.Trait" sortas explicit
  if (ismore <> 0) then
    @text &= ", "
    endif
  @text &= eachpick.field[trtAbbrev].text & " " & eachpick.field[trtDisplay].text
  ismore = 1
  nexteach
@text &= "{br}"

~output special abilities (if any)
if (hero.haschild["component.Ability"] <> 0) then
  @text &= "{b}Abilities:{/b} "
  ismore = 0
  foreach pick in hero where "component.Ability" sortas SpecialTab
    if (ismore <> 0) then
      @text &= ", "
      endif
    @text &= eachpick.field[shortname].text
    ismore = 1
    nexteach
  @text &= "{br}"
  endif

~output arcane powers (if any)
if (hero.haschild["component.Power"] <> 0) then
  @text &= "{b}Arcane Powers (" & #trkmax[trkPower] & "):{/b} "
  ismore = 0
  foreach pick in hero where "component.Power"
    if (ismore <> 0) then
      @text &= ", "
      endif
    @text &= eachpick.field[name].text
    ismore = 1
    nexteach
  @text &= "{br}"
  endif

~output skills
@text &= "{b}Skills:{/b} "
ismore = 0
foreach pick in hero where "component.Skill & !Hide.Skill"
  if (ismore <> 0) then
    @text &= ", "
    endif
  @text &= eachpick.field[trtAbbrev].text
  if (eachpick.tagis[User.NeedDomain] <> 0) then
    @text &= " (" & eachpick.field[domDomain].text & ")"
    endif
  @text &= " " & eachpick.field[trtDisplay].text
  ismore = 1
  nexteach
@text &= "{br}"

~output weapons (if any)
if (hero.haschild["component.WeaponBase"] <> 0) then
  @text &= "{b}Weapons:{/b} "
  ismore = 0
  foreach pick in hero where "component.WeaponBase" sortas Armory
    if (ismore <> 0) then
      @text &= ", "
      endif
    @text &= eachpick.field[name].text
    @text &= " " & eachpick.field[wpNetAtk].text
    @text &= " (" & eachpick.field[wpDamage].text
    if (eachpick.field[wpPiercing].value <> 0) then
      @text &= ", AP" & eachpick.field[wpPiercing].text
      endif
    if (eachpick.tagis[component.WeapRange] <> 0) then
      @text &= ", " & eachpick.field[wpRange].text
      endif
    @text &= ")"
    ismore = 1
    nexteach
  @text &= "{br}"
  endif

~output armor and gear together (if any)
if (hero.haschild["component.Defense | component.Equipment"] <> 0) then

  ~output armor
  ismore = 0
  foreach pick in hero where "component.Defense" sortas Armory
    if (ismore <> 0) then
      @text &= ", "
      endif
    @text &= eachpick.field[name].text
    @text &= " (" & signed(eachpick.field[defDefense].text)
    if (eachpick.tagis[component.Shield] <> 0) then
      if (eachpick.field[defParry].value <> 0) then
        @text &= ", Parry" & signed(eachpick.field[defParry].text)
        endif
      endif
    @text &= ")"
    ismore = 1
    nexteach

  ~output gear
  foreach pick in hero where "component.Equipment"
    if (ismore <> 0) then
      @text &= ", "
      endif
    @text &= eachpick.field[grStkName].text
    ismore = 1
    nexteach
  @text &= "{br}"
  endif

Status Tracking

At the very bottom of each ally, we're going to allows users to easily track the status of the ally during play. We'll include tracking for the ally's health, power points (if applicable), and ammunition (if applicable). The health tracking will depend on whether the character is a wildcard or not. For power points, we'll simply show a series of boxes, up to the total number of power points possessed by the character. The ammo will use the simplified mechanism outlined in the rulebook, which presents four levels.

The resulting Label script for synthesizing all the tracking information is shown below.

var box as string
var gap as string

~output mechanism for tracking health
gap = "{horz 50}"
@text &= "{b}Wounds:" & gap
if (herofield[acIsWild].value <> 0) then
  @text &= "-1" & gap & "-2" & gap & "-3" & gap
  endif
@text &= "INC"
@text &= "{horz 100}Fatigue:" & gap
@text &= "-1" & gap & "-2" & gap & "INC"
@text &= "{/b}{br}"

~output boxes for tracking power points (if applicable)
if (hero.haschild["component.Power"] <> 0) then
  @text &= "{vert 5}"
  @text &= "{b}Power:{/b}{horz 40}"
  var i as number
  var j as number
  var limit as number
  limit = #trkmax[trkPower] / 5
  @text &= "{font wingdings 2}"
  for i = 1 to limit
    for j = 1 to 4
      @text &= chr(163)
      next
    @text &= "{size 44}" & chr(163) & "{size 36}"
    next
  @text &= "{revert}{br}"
  endif

~output boxes for tracking the ammo supply (if any ranged weapons)
if (hero.haschild["component.WeapRange"] <> 0) then
  @text &= "{vert 8}"
  box = "{font Wingdings 2}{size 40}" & chr(163) & "{revert}"
  gap = "{horz 65}"
  @text &= "{b}Ammo:{/b}" & gap & "Full " & box & gap & "High " & box & gap & "Low " & box & gap & "Out " & box
  endif