Character Sheet Additions (Savage): Difference between revisions
Line 187: | Line 187: | ||
@text &= hero.child[resXP].field[resMax].text & " XP)" | @text &= hero.child[resXP].field[resMax].text & " XP)" | ||
@text &= "{br}" | @text &= "{br}" | ||
~use a hanging indent from here on out | |||
@text &= "{indent -100}" | |||
~output attributes | ~output attributes |
Revision as of 11:04, 30 January 2009
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}" ~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 ~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.