Character Sheet Additions (Savage)
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