Character Sheet Additions (Savage)

From HLKitWiki
Revision as of 20:51, 28 January 2009 by Rob (talk | contribs)
Jump to navigationJump to search

Context: HL Kit … Authoring 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="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

Printing Vehicles