Character Sheet Additions (Savage)

From HLKitWiki
Revision as of 23:13, 7 February 2009 by Rob (Talk | contribs) (The Ally Template)

Jump to: navigation, search

Context: HL KitAuthoring Examples … Savage Worlds Walk-Through 

Overview

Now that we've got allies working, we need to add them to the character sheet output. We should also provide details on vehicles within the character sheet. This wiki entry outlines how to accomplish this.

Printing Allies

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 along with the main character for which they are minions. 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="oSeparator">
    <output_separator
      isvertical="no"/>
    </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}"

~use a hanging indent from here on out
@text &= "{indent -100}"

~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

Printing Vehicles

We currently just identify the existence of vehicles within the gear list. This is adequate when the vehicle is used as nothing more than a form of transportation. However, Savage Worlds includes full rules for vehicles in combat, and many of the vehicles are specifically intended for use in combat. Consequently, there will often be times when a player wants to see full details for his vehicles.

We can incorporate vehicles into the character sheet in the exact same way as we handle allies. We'll use the exact same approach, showing the vehicle name as a title at the top, with the vehicle details and damage tracking beneath. Since we're using the same approach, we can copy what we have for allies and adapt it for use with vehicles.

The Table Portal

We'll start with the table portal. For vehicles, our table portal will be operating on picks within the character instead of separate minions. This means that we can use the default behaviors for both what to show in the table and how to sequence them. We'll still want our items to vary in height, and we'll provide our own title at the top of each, so we'll also need the gap between items. This results in the table portal below.

<portal
  id="oVehicle"
  style="outNormal">
  <output_table
    component="Vehicle"
    showtemplate="oVehPick"
    varyheight="yes"
    showgapy="25">
    </output_table>
  </portal>

Layout Integration

Integrating this new table portal into the layout will work the exact same way as allies. We need to add the "portalref" element and include an "autoplace" statement to position the new table. We can add our vehicles either before or after allies, so we'll choose to add them afterwards. This results in the following revised layout.

<layout
  id="oStandard2">
  <portalref portal="oPower"/>
  <portalref portal="oArmor"/>
  <portalref portal="oWeapon"/>
  <portalref portal="oGear"/>
  <portalref portal="oAlly"/>
  <portalref portal="oVehicle"/>
  <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
    perform portal[oVehicle].autoplace

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

The Template

Our template will behave the exact same was as for allies. We'll have the same four sections that serve the same roles. As such, our Position script requires zero changes. The only things we need to change are the unique id, name, and the contents of three of the portals.

The first portal outputs the name of the vehicle. Since we can assume that the names of vehicles will make each entry readily identifiable as a vehicle, we don't have to do anything special with the name. So we'll change the portal to a simple field-based portal, as shown below.

<portal
  id="name"
  style="outTitle">
  <output_label
    field="name">
    </output_label>
  </portal>

The vehicle details are completely different from allies. However, we're going to use the identical approach in synthesizing the output. We can replace the Label script for the portal with the appropriate information for vehicles. If a vehicle has weapons, we need to include full details for each weapon as well. This results in the following revised portal for vehicles.

<portal
  id="details"
  style="outAlly">
  <output_label>
    <labeltext><![CDATA[
      ~output the crew size
      @text &= "{b}Crew:{/b} " & field[vhCrew].text & "{br}"

      ~output the acceleration, speed, and climb for aircraft
      @text &= "{b}Acceleration{/b}: " & field[vhAccel].text
      @text &= "{horz 75}{b}Top Speed:{/b} " & field[vhTopSpeed].text
      if (tagis[VehType.Aircraft] <> 0) then
        @text &= "{horz 75}{b}Climb:{/b} " & field[vhClimb].text
        endif
      @text &= "{br}"

      ~output the toughness and armor
      @text &= "{b}Toughness{/b}: " & field[vhTough].text
      @text &= "{horz 75}{b}Armor{/b}: " & field[vhArmor].text & "{br}"

      ~if there is no child entity/gizmo, then there's nothing more to do
      if (isentity = 0) then
        done
        endif

      ~use a hanging indent from here on out
      @text &= "{indent -150}"

      ~there is a child entity/gizmo, so output the load-out
      @text &= "{b}Weapons/Equipment:{/b}{br}"
      foreach pick in gizmo
        @text &= "{horz 10}" & chr(149) & " " & eachpick.field[name].text & ": "
        @text &= eachpick.field[wpDamage].text & ", " & eachpick.field[wpRange].text
        if (eachpick.field[wpNotes].isempty = 0) then
          @text &= ", " & eachpick.field[wpNotes].text
          endif
        @text &= "{br}"
        nexteach
      ]]></labeltext>
    </output_label>
  </portal>

The portal for the solid line requires no changes, so our final portal to deal with is for status tracking during play. Vehicles only need a damage track, so that's what we'll include. Tracking ammunition for weapons with hundreds or thousands of rounds is not practical on the character sheet, so we won't even try. This yields the revised portal below.

<pre>
<portal
  id="tracking"
  style="outAlly">
  <output_label>
    <labeltext><![CDATA[
      var gap as string

      ~output mechanism for tracking damage
      gap = "{horz 50}"
      @text &= "{b}Damage:" & gap
      @text &= "-1" & gap & "-2" & gap & "-3" & gap & "Wrecked"
      @text &= "{/b}"
      ]]></labeltext>
    </output_label>
  </portal>

Vehicle output within character sheets is now fully operational.