Character Sheet Phase 5 (Savage)
Context: HL Kit … Authoring Examples … Savage Worlds Walk-Through
Overview
Armor
The next section for us to deal with on the character sheet is armor. In Savage Worlds, armor is relatively simple, but there are still some important details we should include for each piece. Obviously, the armor rating is needed. For shields, we need to include the parry rating if it is non-zero. For armor, we need to include the areas covered by the armor.
One option is to do this with columns of data, just like we did for arcane powers. However, shields and armor need to show different details, and the number of details is small, so we'll just list the details in text appended after the name. The Skeleton files provide handling for armor that uses this same technique, so we'll adapt the provided "oArmorPick" template for our needs.
Before we do that, though, we need to consider how we're going to display the various areas covered by the armor. If we use the names "Head", "Torso", and such, we'll never be able to fit the details text without switching to an incredibly small font or running onto a second line. Since space is at a premium, we need something to fit on one line. The easiest solution is to only show the first letter of each area, so "Head" would be shown as "H", etc. This will be immediately obvious to someone reviewing the character sheet and be very compact.
The question now becomes how best can be get the first letter of every area. Within the script, we'll be able to retrieve the names of each area, so we can then muck with the string we get back to strip out only the first characters. However, that will be a real pain to do. A much easier solution is to define an abbreviation for each of the tags for the different areas that is the first character. Once we do that, we can use the "tagabbrevs" target reference instead of "tagnames" and retrieve the abbreviations for display.
To add the abbreviations, we need to modify the various armor location tags. Open the file "tags.1st" and locate the tag group "ArmorLoc". Then add appropriate abbreviations to each tag. The result should look like the following.
<group id="ArmorLoc"> <value id="Torso" abbrev="T"/> <value id="Head" abbrev="H"/> <value id="Arms" abbrev="A"/> <value id="Legs" abbrev="L"/> </group>
Now that the tags have been modified, we can shift our focus to the template showing each piece of defensive equipment. First of all, we don't need the "badstr" portal, so can rip that out. Next we can revise the "name" portal to synthesize the information we identified above for output. Since we're changing the font for the details text, sizing the "name" field to fit is inappropriate, so we must also eliminate that behavior. This yields the following revised template.
<template id="oArmorPick" name="Output Armor Table" compset="Equipment" marginvert="1"> <portal id="equipped" style="outNameMed"> <output_label> <labeltext><![CDATA[ @text = "{bmpscale 3 output_armor}" ]]></labeltext> </output_label> </portal> <portal id="name" style="outNameMed"> <output_label> <labeltext><![CDATA[ ~start with the name and switch to a smaller, non-bold font for details var temp as string @text = field[name].text @text &= "{size 36}{/b} (" ~add the defense rating @text &= field[defDefense].text ~if this is a shield, include the parry rating (if non-zero) if (tagis[component.Shield] <> 0) then if (field[defParry].value <> 0) then @text &= ", Parry: " & signed(field[defParry].value) endif endif ~if this is armor, include the areas covered by the equipment if (tagis[component.Armor] <> 0) then temp = tagabbrevs[ArmorLoc.?,","] if (empty(temp) = 0) then @text &= ", Covers: " & temp endif endif ~wrapup the details @text &= ")" ]]></labeltext> </output_label> </portal> <position><![CDATA[ ~our height is the height of the tallest portal height = maximum(portal[name].height,portal[equipped].height) if (issizing <> 0) then done endif ~if the armor is not equipped, hide the bitmap if (tagis[Equipped.Equipped] = 0) then portal[equipped].visible = 0 endif ~center all portals vertically perform portal[equipped].centervert perform portal[name].centervert ~shift the "equipped" bitmap downward a little bit; this is because it is a ~lone bitmap drawn via encoded text, and bitmaps are never drawn within the ~descender portion of the text, which causes it to appear higher than we want it portal[equipped].top += 4 ~align everything horizontally perform portal[name].alignrel[ltor,equipped,5] ]]></position> </template>
Weapons
Weapons are next on our list. We need to decide whether we want to use columns of data for weapons or stream the info as details like we just did for armor. There are four primary pieces of info for weapons, aside from the name. These are the attack roll, damage, range (if applicable), and armor piercing rating (if any). We should also include a notation if there are special rules for the weapon that need to be remembered by the player, as well as indicating if the minimum strength requirement for the weapon is not satisfied.
We'll make use of columnar data, since that will probably look better. We'll note a failed strength requirement via a bitmap indicator the prefixes the name. We'll indicate is there are special rules with a bitmap after the name. Since we'll have limited space, we'll display the "shortname" field for each weapon. In terms of columns, we'll have one for the attack, another for damage, a third for armor piercing, and a fourth for range.
We'll use the same implementation approach as we employed for arcane powers. We first create the table without the header, and then we add the header information.
Table Portals
For the table itself, we'll first figure out what indicators to use for insufficient strength and special notes. Within the interface, we use the universal "no" symbol and a star symbol, respectively. We should strive to be consistent, so we'll use the same symbols on the character sheet. The star symbol in the interface is a bitmap that is designed for the on-screen use, so it will look poor on the character sheet. If we bring up the "Character Map" tool and scan through the various symbol fonts we discussed earlier, we can quickly find a suitable character in the "Wingdings 2" font.
Now that we've identified all the pieces we need, we can define the additional portals. We'll add a "special" portal to indicate special details for the weapon. We'll also add an "ap" portal to display the armor piercing rating for the weapon. For the "range" portal, we want to show something other than just blank when there is no range, so we'll revise the Label script accordingly. Lastly, we need to realize that the current portals mostly use the "outNameMed" style, which utilizes a rather large, bold font. This simply won't fit in the space we've got to work with, so we need to switch to an alternate style that will fit. We can create our own or use something that already exists. Probably the best choice is to try the "outPlain" style and see if that works, after which we can change it if there are problems. We'll use this style for all portals except for the name, which we'll leave using the "outNameMed" style.
Based on the above considerations, we'll define the portals. This yields a collection of portals that looks like the set below.
<portal id="badstr" style="outPlain"> <output_label> <labeltext><![CDATA[ @text = "{font Webdings}" & chr(120) ]]></labeltext> </output_label> </portal> <portal id="name" style="outNameMed"> <output_label field="shortname"> </output_label> </portal> <portal id="special" style="outPlain"> <output_label> <labeltext><![CDATA[ @text = "{font Wingdings 2}" & chr(234) ]]></labeltext> </output_label> </portal> <portal id="attack" style="outPlain"> <output_label field="wpNetAtk"> </output_label> </portal> <portal id="damage" style="outPlain"> <output_label field="wpDamage"> </output_label> </portal> <portal id="ap" style="outPlain"> <output_label> <labeltext><![CDATA[ if (field[wpPiercing].value <> 0) then @text = field[wpPiercing].text else @text = "-" endif ]]></labeltext> </output_label> </portal> <portal id="range" style="outPlain"> <output_label> <labeltext><![CDATA[ if (tagis[component.WeapRange] <> 0) then @text = field[wpRange].text else @text = "-" endif ]]></labeltext> </output_label> </portal> <portal id="dots" style="outDots"> <output_dots> </output_dots> </portal>
Positioning the Portals
Now that we have all the portals, we need to position them appropriately. We'll start by centering the name. We'll also center our indicator symbols vertically, since they will be flanking the name on either side. All other portals use a smaller font than the name, so we need to align them along the same baseline as the name. Since we're using columns, we need to specify appropriate widths for each of the four columns other than the name. The name will then inherit whatever space remains. If we don't pick the correct widths for the columns, we can easily refine the values with a little bit of experimentation.
Once we have the space for the name identified, we can position the portals for each column. Now we are left with positioning the name appropriately. The two indicator bitmaps will be displayed as part of the name column. Consequently, we must determine which of the indicators is applicable and set our visibility accordingly. If the strength requirement is shown, then the name itself must be positioned to the right of the indicator.
Since the indicators are part of the space for the name, we need to subtract them from the width allocated to the name portal. Once that's done, we can automatically shrink the name to fit within the available space if it's too large. When we do this, we have to remember that shrinking the name keeps its "top" position the same, which means that smaller text will creep upwards. We want it to retain the same baseline position, so we have to re-position the portal after the resize to ensure the baseline doesn't change.
At this point, the name portal extends to the rightmost edge of the space we reserved for the name. However, we want the "special" portal to appear immediately adjacent to the name. We also want to include a sequence of dots between the name and the attack portal for better clarity. To accomplish this, we must now force the "name" portal to re-calculate its width based on the new font. Assuming the "sizetofit" operation above shrunk the name sufficiently, this will work perfectly. However, if the name is extremely long, we specified a minimum font size, so the operation may have stopped when the font size reached the minimum. If that's the case, re-calculating the size will end up making the portal wider, which will extend it into other columns. We can't allow that, so we must first get the current width, then re-calculate the width, and finally limit the new width to the original width.
Everything has been figured out now. So we can position the "name" portal properly and then position the "special" portal to its right, if necessary. The final step is showing the "dots" portal between the right edge of the used name region and the left edge of the attack column.
All of this logic can be seen in the Position script shown below.
~our height is based on the tallest portal within height = portal[name].height if (issizing <> 0) then done endif ~center the name and indicators vertically perform portal[name].centervert perform portal[badstr].centervert perform portal[special].centervert ~position the other portals with the same baseline as the name; since they ~use a smaller font, they will have a smaller height, so centering them will ~make them appear to float up relative to the name perform portal[attack].alignrel[btob,name,0] perform portal[damage].alignrel[btob,name,0] perform portal[ap].alignrel[btob,name,0] perform portal[range].alignrel[btob,name,0] perform portal[dots].alignrel[btob,name,0] ~establish suitable fixed widths for the various columns of data portal[attack].width = 135 portal[damage].width = 135 portal[ap].width = 50 portal[range].width = 225 ~assign the name the remaining horizontal space ~Note: We must also account for a gap of 5 between portals. portal[name].width = width - 4 * 5 portal[name].width -= portal[attack].width + portal[damage].width portal[name].width -= portal[ap].width + portal[range].width ~position our columns now, except for the name portal[attack].left = portal[name].right + 5 perform portal[damage].alignrel[ltor,attack,5] perform portal[ap].alignrel[ltor,damage,5] perform portal[range].alignrel[ltor,ap,5] ~setup to track our position when positioning portals along the x-axis var x as number x = 0 ~if the weapon satisfies the minimum strength requirement, hide the bitmap, ~else position it on the left if (tagis[Helper.BadStrReq] = 0) then portal[badstr].visible = 0 else portal[badstr].left = 0 x = portal[badstr].right endif ~if there are no special details for the weapon, hide the bitmap if (field[wpNotes].isempty = 1) then portal[special].visible = 0 endif ~shrink the space for the name based on the presence of the two bitmaps portal[name].width -= portal[badstr].width * portal[badstr].visible portal[name].width -= portal[special].width * portal[special].visible ~size the name to fit the available space, then reposition it at the baseline ~Note: This is needed since smaller text will have the same top position. perform portal[name].sizetofit[36] perform portal[name].alignrel[btob,attack,0] ~recalculate the width of the name based on the sized font ~Note: This is needed so we can determine the span for the row of dots. ~Note: We must also cap the name portal to its previous width, just in case ~ the "sizetofit" didn't shrink the name far enough to fully fit. var limit as number limit = portal[name].width perform portal[name].autowidth if (portal[name].width > limit) then portal[name].width = limit endif ~position the name and special details indicator portals horizontally now portal[name].left = x x = portal[name].right if (portal[special].visible <> 0) then portal[special].left = x x = portal[special].right endif ~extend the dots from the right of the name across to the attack portal if (x > portal[attack].left - 10) then portal[dots].visible = 0 else portal[dots].left = x + 5 portal[dots].width = portal[attack].left - 5 - portal[dots].left endif
Adding the Header
Our table is looking good, so we can now deal get the header working correctly. We already have three header portals that we'll want to retain. These portals are being positioned relative to the portals in the main table, so they are working perfectly. All we need to do is add another header portal for the armor piercing rating. This can be added and then positioned just like the other header portals in the Header script.
Looking at it all, though, the header labels appear to be a little bit larger than would be optimal. Since we're using a smaller font than the provided Skeleton files, we need to ensure that the header is shrunk a bit, too. We can accomplish this by either switching to a new style or simply changing the font size used for font in the existing style. We'll do the latter and shrink the font a full point (from 36 to 32). Once we shrink the font size, we can also change the column headers to show the proper name instead of an abbreviation.
Putting it all together, this yields the following portals and Header script for the template.
<portal id="hdrtitle" style="outTitle" isheader="yes"> <output_label text="Weapons"> </output_label> </portal> <portal id="hdrattack" style="outHeader" isheader="yes"> <output_label text="Attack"> </output_label> </portal> <portal id="hdrdamage" style="outHeader" isheader="yes"> <output_label text="Damage"> </output_label> </portal> <portal id="hdrap" style="outHeader" isheader="yes"> <output_label text="AP"> </output_label> </portal> <portal id="hdrrange" style="outHeader" isheader="yes"> <output_label text="Range"> </output_label> </portal>
<header><![CDATA[ ~our header height is the title plus a gap plus the header text height = portal[hdrtitle].height + 2 + portal[hdrattack].height if (issizing <> 0) then done endif ~our title spans the entire width of the template portal[hdrtitle].width = width ~each of our header labels has the same width as the corresponding data beneath portal[hdrattack].width = portal[attack].width portal[hdrdamage].width = portal[damage].width portal[hdrap].width = portal[ap].width portal[hdrrange].width = portal[range].width ~center each header label on the corresponding data beneath perform portal[hdrattack].centeron[horz,attack] perform portal[hdrdamage].centeron[horz,damage] perform portal[hdrap].centeron[horz,ap] perform portal[hdrrange].centeron[horz,range] ~align all header labels at the bottom of the header region perform portal[hdrattack].alignedge[bottom,0] perform portal[hdrdamage].alignedge[bottom,0] perform portal[hdrap].alignedge[bottom,0] perform portal[hdrrange].alignedge[bottom,0] ]]></header>
Gear
The final piece of the character sheet is handling the gear. The Skeleton files provide us with a gear table that lists all the gear in a single column. Since a single column is used, the font is large and bold, plus there is plenty of unused space. We need something compact, using a smaller, non-bold font, and employing at least two columns. In fact, if we can switch to a significantly smaller font, we can probably even squeeze three columns in, which would be ideal. So we'll start by modifying the table portal to use three columns.
Looking at the existing template, we don't want to keep the quantity in a separate column from the actual gear name. So we'll eliminate the separate "value" portal, but we'll move the logic for generating the quantity into the "name" portal. The template uses a large horizontal margin, which we need to shrink to something that will be just enough to separate our three columns. A value of 5 ought to be about right.
We need to use a small font to squeeze the gear into three columns, so we'll simply define a style specifically for use with gear. In the file "styles_output.aug", we'll create the "outGear" style and a corresponding font for use with the style. We'll use a small point size for the font and not use bold. This results in the new definition shown below.
<style id="outGear"> <style_output textcolor="000000" font="ofntgear" alignment="left"> </style_output> <resource id="ofntgear"> <font face="Arial" size="36"> </font> </resource> </style>
We can now implement the portal appropriately. We'll use the new style and prepend the quantity onto the name, which yields the following portal.
<portal id="name" style="outGear"> <output_label> <labeltext><![CDATA[ if (stackable = 0) then @text = "" elseif (field[stackQty].value = 1) then @text = "" else @text = field[stackQty].text & "x " endif @text &= field[name].text ]]></labeltext> </output_label> </portal>
With the portal in place, we need to position it. The name portal is sized based on the text within it, so our first action is the limit the width of the portal to the width of the template. Once we do that, we can try shrinking the font size to better fit longer names into the available space. Some names may still be too long, so we now limit the height to a single line. Lastly, we align the portal at the bottom of the template, which ensures that all gear entries across a row share the same baseline. This yields the following Position script.
~our height is the height of the tallest portal height = portal[name].height if (issizing <> 0) then done endif ~limit the width of the name to the width of the template if (portal[name].width > width) then portal[name].width = width endif ~size the name to fit the available space, if needed perform portal[name].sizetofit[30] ~limit the name to a single line based on the updated font size portal[name].lineheight = 1 ~align the name at the bottom of the template perform portal[name].alignedge[bottom,0]
This looks pretty good, except that there is a variety of gear that has names longer than will fit in the space we've got. The solution is to do for all gear what we've already done for weapons. We can designate the "Gear" component as supporting short names, allowing a shorter name to be defined for every piece of equipment. Then we can change the Label script for the "name" portal to reference the "shortname" field instead of the "name" field. Now we should be in great shape for handling gear in a very compact fashion.