Character Sheet Phase 4 (Savage): Difference between revisions
No edit summary |
|||
(23 intermediate revisions by the same user not shown) | |||
Line 13: | Line 13: | ||
In order to ensure that we honor these priorities, we need to carve the right side of the page up into pieces that can be placed in an appropriate sequence. So we need create a new layout that encompasses the material we'll place in the lower right corner, then we need to position it after the powers are handled. Within the remaining space, we can position a separate layout that includes weapons and armor. If we still have space left, then we can squeeze our gear into that region via a third layout. | In order to ensure that we honor these priorities, we need to carve the right side of the page up into pieces that can be placed in an appropriate sequence. So we need create a new layout that encompasses the material we'll place in the lower right corner, then we need to position it after the powers are handled. Within the remaining space, we can position a separate layout that includes weapons and armor. If we still have space left, then we can squeeze our gear into that region via a third layout. | ||
So our next order of business is to setup these three layouts. We already have a layout for the weapons and armor that we can use. For the gear, we have the table portal and simply need a layout to contain it. That layout can be defined easily as shown below, and we must remove the portal reference from the "oRightSide" layout. | So our next order of business is to setup these three layouts. We already have a layout for the weapons and armor that we can use. This layout is designed to split its space between the weapons and armor tables, so we don't have to do anything special to get weapons and armor to work smoothly. | ||
For the gear, we have the table portal and simply need a layout to contain it. That layout can be defined easily as shown below, and we must remove the portal reference from the "oRightSide" layout. | |||
<pre> | <pre> | ||
Line 45: | Line 47: | ||
<layoutref layout="oValidate"/> | <layoutref layout="oValidate"/> | ||
<position><![CDATA[ | <position><![CDATA[ | ||
~set this | ~set this scene variable to 1 if you want the logos to be stacked; a value | ||
~of zero places them side-by-side | ~of zero places them side-by-side | ||
scenevalue[stacklogos] = 0 | |||
~setup the gap to be used between the various sections of the character sheet | ~setup the gap to be used between the various sections of the character sheet | ||
autogap = 40 | autogap = 40 | ||
scenevalue[sectiongap] = autogap | |||
~calculate the width of the two columns of the character sheet, leaving a | ~calculate the width of the two columns of the character sheet, leaving a | ||
Line 69: | Line 71: | ||
perform layout[oValidate].render | perform layout[oValidate].render | ||
layout[oValidate].top = height - layout[oValidate].height | layout[oValidate].top = height - layout[oValidate].height | ||
extent = layout[oValidate].top - | extent = layout[oValidate].top - scenevalue[sectiongap] | ||
endif | endif | ||
Line 81: | Line 83: | ||
perform layout[oLogos].render | perform layout[oLogos].render | ||
layout[oLogos].left = width - colwidth | layout[oLogos].left = width - colwidth | ||
~position the activated adjustments at the bottom on the right; this will | ~position the activated adjustments at the bottom on the right; this will | ||
Line 95: | Line 90: | ||
perform layout[oAdjust].render | perform layout[oAdjust].render | ||
layout[oAdjust].top = extent - layout[oAdjust].height | layout[oAdjust].top = extent - layout[oAdjust].height | ||
~position the rightside layout in the remaining space on the right | |||
layout[oRightSide].width = colwidth | |||
layout[oRightSide].top = layout[oLogos].bottom + scenevalue[sectiongap] | |||
layout[oRightSide].left = layout[oLogos].left | |||
layout[oRightSide].height = layout[oAdjust].top - scenevalue[sectiongap] - layout[oRightSide].top | |||
perform layout[oRightSide].render | |||
~position the armory layout within the remaining space on the right | ~position the armory layout within the remaining space on the right | ||
layout[oArmory].width = colwidth | layout[oArmory].width = colwidth | ||
layout[oArmory].top = layout[oRightSide].bottom + | layout[oArmory].top = layout[oRightSide].bottom + scenevalue[sectiongap] | ||
layout[oArmory].left = layout[oLogos].left | layout[oArmory].left = layout[oLogos].left | ||
layout[oArmory].height = layout[oAdjust].top - | |||
~if the top of the armory layout is below the adjustments layout, there is no | |||
~room for it, so hide it; otherwise, set height and render the layout properly | |||
if (layout[oArmory].top >= layout[oAdjust].top) then | |||
layout[oArmory].visible = 0 | |||
else | |||
layout[oArmory].height = layout[oAdjust].top - scenevalue[sectiongap] - layout[oArmory].top | |||
perform layout[oArmory].render | |||
endif | |||
~position the gear layout in whatever space is left on the right | ~position the gear layout in whatever space is left on the right | ||
layout[oGear].width = colwidth | layout[oGear].width = colwidth | ||
layout[oGear].top = layout[oArmory].bottom + | layout[oGear].top = layout[oArmory].bottom + scenevalue[sectiongap] | ||
layout[oGear].left = layout[oLogos].left | layout[oGear].left = layout[oLogos].left | ||
layout[oGear].height = layout[oAdjust].top - | |||
~if the top of the gear layout is below the adjustments layout, there is no | |||
~room for it, so hide it; otherwise, set height and render the layout properly | |||
if (layout[oGear].top >= layout[oAdjust].top) then | |||
layout[oGear].visible = 0 | |||
else | |||
layout[oGear].height = layout[oAdjust].top - scenevalue[sectiongap] - layout[oGear].top | |||
perform layout[oGear].render | |||
endif | |||
]]></position> | ]]></position> | ||
</sheet> | </sheet> | ||
Line 114: | Line 130: | ||
===Adjustments=== | ===Adjustments=== | ||
Since the adjustments layout is always going to be given priority in the lower right corner, we should make sure it's all working now. The only time the adjustments table appears is when one or more in-play or permanent adjustments are enabled for the character when the sheet is printed. For testing, we can add a few of each and preview the character sheet. They appear nicely in the lower right corner, so there's nothing further we need to do to handle them now. | |||
===Injuries and Health=== | ===Injuries and Health=== | ||
=== | The plan is to always show any injuries in the lower right corner, along with a place to track health and power points. The injuries can be managed as a simple table portal, while the condition tracking requires that we implement something that is a little more complex. We could keep things easy by just inserting a graphic, or we could actually construct something that will work. We'll try the latter, without going overboard. | ||
====Injuries Table==== | |||
Before we do that, though, we'll first get the injuries table into place. The table simply lists all injuries, and we'll need to provide a template that displays each individual injury appropriately. For the template, we just need something simple that will show the name of the injury. We can clone and adapt the template used for adjustments. This results in the table portal and template shown below. | |||
<pre> | |||
<portal | |||
id="oInjury" | |||
style="outNormal"> | |||
<output_table | |||
component="Injury" | |||
showtemplate="oInjury"> | |||
<headertitle><![CDATA[ | |||
@text = "Injuries" | |||
]]></headertitle> | |||
</output_table> | |||
</portal> | |||
<template | |||
id="oInjury" | |||
name="Output Injuries Table" | |||
compset="Injury" | |||
marginhorz="10"> | |||
<portal | |||
id="name" | |||
style="outNormLt"> | |||
<output_label | |||
field="name"> | |||
</output_label> | |||
</portal> | |||
<position><![CDATA[ | |||
~our height is the vertical extent of our portals | |||
height = portal[name].height | |||
if (issizing <> 0) then | |||
done | |||
endif | |||
~size the name to fit the available space | |||
portal[name].width = width | |||
perform portal[name].sizetofit[36] | |||
perform portal[name].autoheight | |||
perform portal[name].centervert | |||
]]></position> | |||
</template> | |||
</pre> | |||
Once the table is in place, we need to add it to the layout. We'll make it 40% of the total width of the layout, leaving the rest for condition tracking. Unlike our usual approach, we cannot use "autoplace" for this table. If the table is empty, automatic placement will hide the table, which will leave an empty gap in the character sheet that will look odd. So we need to position and size the table ourselves. Since the table will use no more space than it requires, we can set the height to something very large and HL will truncate it to the appropriate height. This results in the revised layout below. | |||
<pre> | |||
<layout | |||
id="oAdjust"> | |||
<portalref portal="oAdjust"/> | |||
<portalref portal="oInjury"/> | |||
<position><![CDATA[ | |||
~position the adjustments table at the top | |||
perform portal[oAdjust].autoplace | |||
~position the injuries table in the left 40% beneath the adjustments | |||
~Note: We don't use autoplace, since we never want the table hidden | |||
portal[oInjury].left = 0 | |||
portal[oInjury].top = autotop + scenevalue[sectiongap] | |||
portal[oInjury].width = width * .4 | |||
portal[oInjury].height = 5000 | |||
~our layout height is the bottom extent of the elements within | |||
height = portal[oInjury].bottom | |||
]]></position> | |||
</layout> | |||
</pre> | |||
If our character has no injuries, though, this looks strange. All we see is the header above the table and nothing within it. What we need is a portal that simply says "None" that we can show just below the header if the table is empty. So we'll define a new label portal and show it appropriately via the layout. The new portal and updated layout are shown below. | |||
<pre> | |||
<portal | |||
id="oNoInjury" | |||
style="outNormal"> | |||
<output_label | |||
text="-None-"> | |||
</output_label> | |||
</portal> | |||
<layout | |||
id="oAdjust"> | |||
<portalref portal="oAdjust"/> | |||
<portalref portal="oInjury"/> | |||
<portalref portal="oNoInjury"/> | |||
<position><![CDATA[ | |||
~position the adjustments table at the top | |||
perform portal[oAdjust].autoplace | |||
~position the injuries table in the left 40% beneath the adjustments | |||
~Note: We don't use autoplace, since we never want the table hidden | |||
portal[oInjury].left = 0 | |||
portal[oInjury].top = autotop + scenevalue[sectiongap] | |||
portal[oInjury].width = width * .4 | |||
portal[oInjury].height = 5000 | |||
~if there are no injuries, indicate that fact | |||
if (portal[oInjury].itemsshown <> 0) then | |||
portal[oNoInjury].visible = 0 | |||
else | |||
perform portal[oNoInjury].centeron[horz,oInjury] | |||
perform portal[oNoInjury].alignrel[ttob,oInjury,10] | |||
endif | |||
~our layout height is the bottom extent of the elements within | |||
height = maximum(portal[oInjury].bottom,portal[oNoInjury].bottom) | |||
]]></position> | |||
</layout> | |||
</pre> | |||
====Health in Template==== | |||
For tracking health, the standard representation in Savage Worlds is a Wounds progression from one end and a Fatigue progression from the other end. This can probably be best implemented via a handful of portals. Since the contents of those portals have no relationship to any aspects of the character, no template is truly necessary. However, it's probably easiest to manage this via a template, so that's the approach we'll use. | |||
We need a total of eight portals within our template. We need one to indicate "Wounds" on the left and another to indicate "Fatigue" on the right. We then need the "Incapacitated" indicator as a third portal, along with three progressive increments for wounds and two for fatigue. We'll place the two labels in the upper left and upper right corners of the span within which the condition tracking is shown. The six health levels will then be arrayed across the full width of the span. This results in a template that should look like the following. | |||
<pre> | |||
<template | |||
id="oCondition" | |||
name="Output Condition" | |||
compset="Actor"> | |||
<portal | |||
id="wounds" | |||
style="outNameLg"> | |||
<output_label | |||
text="Wounds"> | |||
</output_label> | |||
</portal> | |||
<portal | |||
id="fatigue" | |||
style="outNameLg"> | |||
<output_label | |||
text="Fatigue"> | |||
</output_label> | |||
</portal> | |||
<portal | |||
id="wound1" | |||
style="outNameLg"> | |||
<output_label | |||
text="-1"> | |||
</output_label> | |||
</portal> | |||
<portal | |||
id="wound2" | |||
style="outNameLg"> | |||
<output_label | |||
text="-2"> | |||
</output_label> | |||
</portal> | |||
<portal | |||
id="wound3" | |||
style="outNameLg"> | |||
<output_label | |||
text="-3"> | |||
</output_label> | |||
</portal> | |||
<portal | |||
id="fatigue1" | |||
style="outNameLg"> | |||
<output_label | |||
text="-1"> | |||
</output_label> | |||
</portal> | |||
<portal | |||
id="fatigue2" | |||
style="outNameLg"> | |||
<output_label | |||
text="-2"> | |||
</output_label> | |||
</portal> | |||
<portal | |||
id="incapac" | |||
style="outNameLg"> | |||
<output_label | |||
text="INC"> | |||
</output_label> | |||
</portal> | |||
<position><![CDATA[ | |||
~position the labels at the top | |||
portal[wounds].top = 0 | |||
portal[fatigue].top = 0 | |||
~position all health levels beneath the labels with a common baseline | |||
perform portal[wound1].alignrel[ttob,wounds,10] | |||
perform portal[wound2].alignrel[btob,wound1,0] | |||
perform portal[wound3].alignrel[btob,wound1,0] | |||
perform portal[fatigue1].alignrel[btob,wound1,0] | |||
perform portal[fatigue2].alignrel[btob,wound1,0] | |||
perform portal[incapac].alignrel[btob,wound1,0] | |||
~position the labels at the left and right edges | |||
portal[wounds].left = 0 | |||
perform portal[fatigue].alignedge[right,0] | |||
~put the -1 levels at each end | |||
portal[wound1].left = 0 | |||
perform portal[fatigue1].alignedge[right,0] | |||
~determine the remaining span over which the portals extend | |||
var span as number | |||
span = portal[fatigue1].left - portal[wound1].right | |||
~tally the total width of the remaining health level portals | |||
var used as number | |||
used = portal[wound2].width + portal[wound3].width | |||
used += portal[fatigue2].width + portal[incapac].width | |||
~divvy up the remaining horizontal space into equal pieces and use as spacing | |||
~for positioning the various health level horizontally | |||
var each as number | |||
each = span - used | |||
each = round(each / 5,0,0) | |||
perform portal[wound2].alignrel[ltor,wound1,each] | |||
perform portal[wound3].alignrel[ltor,wound2,each] | |||
perform portal[fatigue2].alignrel[rtol,fatigue1,-each] | |||
~size and center the incapacitated cell in the remaining space | |||
each = (portal[fatigue2].left - portal[wound3].right - portal[incapac].width) / 2 | |||
perform portal[incapac].alignrel[ltor,wound3,each] | |||
~our height is the bottom extent of the portals | |||
height = portal[wound1].bottom | |||
]]></position> | |||
</template> | |||
</pre> | |||
====Integration Into Layout==== | |||
Now that we've got our template created, we need to integrate it into the layout. We first add a "templateref" element. Since the template doesn't actually reference any live data within the portfolio, we can hook the template up to any pick or thing we choose. In general, the best tactic in situations like this is to utilize the "actor" pick, so that's what we'll do. | |||
Once the template is part of the layout, we then need to position it properly. It gets placed to the right of the injuries table, leaving a little bit of a gap. The top of the template should be the same as the top of the injury table. The template will calculate its own height when it is rendered. Once rendered, it's conceivable (albeit unlikely) that the template will be taller than the table, so we need to set our height to the tallest of the two visual elements. This results in the updated layout shown below. | |||
<pre> | |||
<layout | |||
id="oAdjust"> | |||
<portalref portal="oAdjust"/> | |||
<portalref portal="oInjury"/> | |||
<portalref portal="oNoInjury"/> | |||
<templateref template="oCondition" thing="actor" ispick="yes"/> | |||
<position><![CDATA[ | |||
~position the adjustments table at the top | |||
perform portal[oAdjust].autoplace | |||
~position the injuries table in the left 40% beneath the adjustments | |||
~Note: We don't use autoplace, since we never want the table hidden | |||
portal[oInjury].left = 0 | |||
portal[oInjury].top = autotop + scenevalue[sectiongap] | |||
portal[oInjury].width = width * .4 | |||
portal[oInjury].height = 5000 | |||
~if there are no injuries, indicate that fact | |||
if (portal[oInjury].itemsshown <> 0) then | |||
portal[oNoInjury].visible = 0 | |||
else | |||
perform portal[oNoInjury].centeron[horz,oInjury] | |||
perform portal[oNoInjury].alignrel[ttob,oInjury,10] | |||
endif | |||
~position the condition template to the right of the injuries | |||
template[oCondition].left = portal[oInjury].right + 50 | |||
template[oCondition].top = portal[oInjury].top | |||
template[oCondition].width = width - template[oCondition].left | |||
perform template[oCondition].render | |||
=== | ~our layout height is the bottom extent of the elements within | ||
height = maximum(portal[oInjury].bottom,template[oCondition].bottom) | |||
]]></position> | |||
</layout> | |||
</pre> | |||
====Refining the Health Display==== | |||
After previewing our updated character sheet, the health region looks passable, but it would really benefit by having a soft grey border around the region to set it off nicely. Unlike portals, we cannot simply put a border around a template. So we'll need to add the border via a portal within the template. This entails adding a new portal whose sole purpose is to provide the border. We then setup a margin around the edges by positioning the portals with a gap all the way around. Once that's done, we can size the border portal to encompass the full region of the other portals, resulting in a border being drawn around a collection of portals. The new portal and the revised Position script are shown below. | |||
<pre> | |||
<portal | |||
id="border1" | |||
style="outGreyBox"> | |||
<output_label | |||
text=" "> | |||
</output_label> | |||
</portal> | |||
<position><![CDATA[ | |||
~leave a margin around all edges to draw our border | |||
var margin as number | |||
margin = 10 | |||
~position the labels at the top | |||
portal[wounds].top = margin | |||
portal[fatigue].top = margin | |||
~position all health levels beneath the labels with a common baseline | |||
perform portal[wound1].alignrel[ttob,wounds,10] | |||
perform portal[wound2].alignrel[btob,wound1,0] | |||
perform portal[wound3].alignrel[btob,wound1,0] | |||
perform portal[fatigue1].alignrel[btob,wound1,0] | |||
perform portal[fatigue2].alignrel[btob,wound1,0] | |||
perform portal[incapac].alignrel[btob,wound1,0] | |||
~position the labels at the left and right edges | |||
portal[wounds].left = margin | |||
perform portal[fatigue].alignedge[right,-margin] | |||
~put the -1 levels at each end | |||
portal[wound1].left = margin | |||
perform portal[fatigue1].alignedge[right,-margin] | |||
~determine the remaining span over which the portals extend | |||
var span as number | |||
span = portal[fatigue1].left - portal[wound1].right | |||
~tally the total width of the remaining health level portals | |||
var used as number | |||
used = portal[wound2].width + portal[wound3].width | |||
used += portal[fatigue2].width + portal[incapac].width | |||
~divvy up the remaining horizontal space into equal pieces and use as spacing | |||
~for positioning the various health level horizontally | |||
var each as number | |||
each = span - used | |||
each = round(each / 5,0,0) | |||
perform portal[wound2].alignrel[ltor,wound1,each] | |||
perform portal[wound3].alignrel[ltor,wound2,each] | |||
perform portal[fatigue2].alignrel[rtol,fatigue1,-each] | |||
~size and center the incapacitated cell in the remaining space | |||
each = (portal[fatigue2].left - portal[wound3].right - portal[incapac].width) / 2 | |||
perform portal[incapac].alignrel[ltor,wound3,each] | |||
~set the first border to span the full dimensions of the health tracker | |||
portal[border1].width = width | |||
portal[border1].height = portal[wound1].bottom + margin | |||
~our height is the bottom extent of the border | |||
height = portal[border1].bottom | |||
]]></position> | |||
</pre> | |||
====Power Points Tracking==== | |||
The final thing we need to add to the condition tracking section is a place to mark off power points that are spent. All the various character sheets tend to show 30 boxes, so we'll do the same. We'll place a sequence of boxes beneath the health tracking region and allow the user to check boxes as points are spent. Given our limited space, we won't be able to fit 30 boxes, so we'll instead output two rows of 15 boxes each. To make tracking easier for the user, we'll ensure that every fifth box is larger and stands out from the others. | |||
Synthesizing output like we need is most easily handled via a script. By using some nested "for/next" loops, we can construct the text for output. However, we need to figure out how we're going to output boxes without having to use bitmaps. The answer is the "Wingdings" font that is built into Windows. We can utilize the "Character Map" tool provided by Windows to view the various characters supported by each font. Only a small number of fonts are built into every installation of Windows, and these include the "Webdings" font and the "Wingdings" font. Scanning through the character map for "Wingdings", we spot a suitable box character that we can use (character code "A8" in hex). | |||
Let's think through our logic now. We start by switching to the "Wingdings" font via the "{font Wingdings}" encoded text sequence. Between each character, we'll need to insert a tiny extra bit of spacing, which can be accomplished via the "{horz 1}" specification. For every fifth character, we need to increase the font size and then shrink it back down, which entails using "{size 52}". After every 15 characters, a line break needs to be inserted. | |||
Since the border looks good around the health tracking, we'll use a separate border around the power points tracking region. So we'll need two new portals, with one being generating the text for output and the other providing a border. This yields the two new portals below and the additional code for the Position script given below. | |||
<pre> | |||
<portal | |||
id="power" | |||
style="outPlain"> | |||
<output_label> | |||
<labeltext><![CDATA[ | |||
var i as number | |||
var j as number | |||
var k as number | |||
@text = "{font Wingdings}" | |||
for i = 1 to 2 | |||
for j = 1 to 3 | |||
for k = 1 to 4 | |||
@text &= chr(168) & "{horz 1}" | |||
next | |||
@text &= "{size 52}" & chr(168) & "{size 40}" | |||
next | |||
@text &= "{br}" | |||
next | |||
]]></labeltext> | |||
</output_label> | |||
</portal> | |||
<portal | |||
id="border2" | |||
style="outGreyBox"> | |||
<output_label | |||
text=" "> | |||
</output_label> | |||
</portal> | |||
</pre> | |||
<pre> | |||
~position and size the power level condition | |||
perform portal[power].alignrel[ttob,border1,10 + margin] | |||
portal[power].left = margin | |||
portal[power].width = width - margin | |||
~set the first border to span the full dimensions of the power tracker | |||
portal[border2].top = portal[power].top - margin | |||
portal[border2].width = width | |||
portal[border2].height = portal[power].height + margin * 2 | |||
~our height is the bottom extent of the second border | |||
height = portal[border2].bottom | |||
</pre> | |||
This doesn't provide anything impressive, but it offers a clean and effective solution in a very compact space. This will do quite nicely. |
Latest revision as of 06:13, 5 May 2009
Context: HL Kit … Authoring Examples … Savage Worlds Walk-Through
Overview
In the previous stage of creating the character sheet, we got started on the right side and solved the most complex piece of the output in arcane powers. The remainder of the right side of the sheet introduce a few more wrinkles, but everything should proceed smoothly from this point forward.
Breaking the Right Side into Sections
The biggest remaining issue with the right side of the sheet is managing our vertical space appropriately. If a character has arcane powers, a bunch of weapons, and lots of gear, there might not be enough space on the right side in which to fit it all. It's perfectly reasonable for that to happen, and we can safely spill the remaining items onto a second page. However, we should give thought to which information is given priority for being shown on the first page and what we're happy to let fall to the second page.
It's very important that we show the region for tracking health on the first page, since that will come into play regularly for many games. Since we're pairing the health and injuries up side-by-side, we need to ensure those get included on the first page. We should also be sure to include a reminder of any actively enabled adjustments on the first page. Weapons and armor are a secondary priority, with gear being the lowest priority for the first page.
In order to ensure that we honor these priorities, we need to carve the right side of the page up into pieces that can be placed in an appropriate sequence. So we need create a new layout that encompasses the material we'll place in the lower right corner, then we need to position it after the powers are handled. Within the remaining space, we can position a separate layout that includes weapons and armor. If we still have space left, then we can squeeze our gear into that region via a third layout.
So our next order of business is to setup these three layouts. We already have a layout for the weapons and armor that we can use. This layout is designed to split its space between the weapons and armor tables, so we don't have to do anything special to get weapons and armor to work smoothly.
For the gear, we have the table portal and simply need a layout to contain it. That layout can be defined easily as shown below, and we must remove the portal reference from the "oRightSide" layout.
<layout id="oGear"> <portalref portal="oGear"/> <position><![CDATA[ ~position the various tables appropriately perform portal[oGear].autoplace ~our layout height is the extent of the elements within height = autotop ]]></position> </layout>
The Skeleton files provide us with a table of adjustments and a layout that contains them. For simplicity, we'll just use this layout as our third layout. When we need to add injuries and such, we'll integrate them into this layout. So we don't need to do anything further with this layout.
The final thing we need to do is properly revise the sheet to incorporate these three layouts. We also need to handle these layouts within the Position script for the sheet. This entails positioning the current "oRightSide" layout, then placing the "oAdjust" layout anchored to the bottom of the sheet. Once that's done, we can position the "oArmory" layout beneath the "oRightSide" layout, using up whatever space is available. We'll place the "oGear" layout last, using the final remnants of space on the character sheet. The updated contents of the "standard1" sheet should look like the one shown below.
<sheet id="standard1" name="Standard character sheet, page #1"> <layoutref layout="oLogos"/> <layoutref layout="oLeftSide"/> <layoutref layout="oRightSide"/> <layoutref layout="oAdjust"/> <layoutref layout="oArmory"/> <layoutref layout="oGear"/> <layoutref layout="oValidate"/> <position><![CDATA[ ~set this scene variable to 1 if you want the logos to be stacked; a value ~of zero places them side-by-side scenevalue[stacklogos] = 0 ~setup the gap to be used between the various sections of the character sheet autogap = 40 scenevalue[sectiongap] = autogap ~calculate the width of the two columns of the character sheet, leaving a ~suitable center gap between them var colwidth as number colwidth = (width - 50) / 2 ~if the user wants to omit the validation report, the hide it and allow the ~rest of the sheet to fill that space; otherwise, output the layout and the ~top of the validation report establishes the bottom for all other output var extent as number if (hero.tagis[source.Validation] = 0) then layout[oValidate].visible = 0 extent = height else layout[oValidate].width = width perform layout[oValidate].render layout[oValidate].top = height - layout[oValidate].height extent = layout[oValidate].top - scenevalue[sectiongap] endif ~position the leftside layout in the upper left corner layout[oLeftSide].width = colwidth layout[oLeftSide].height = extent - layout[oLeftSide].top perform layout[oLeftSide].render ~position the logos layout in the upper right corner layout[oLogos].width = colwidth perform layout[oLogos].render layout[oLogos].left = width - colwidth ~position the activated adjustments at the bottom on the right; this will ~establish the remaining space available on the right for other layouts layout[oAdjust].width = colwidth layout[oAdjust].left = layout[oLogos].left perform layout[oAdjust].render layout[oAdjust].top = extent - layout[oAdjust].height ~position the rightside layout in the remaining space on the right layout[oRightSide].width = colwidth layout[oRightSide].top = layout[oLogos].bottom + scenevalue[sectiongap] layout[oRightSide].left = layout[oLogos].left layout[oRightSide].height = layout[oAdjust].top - scenevalue[sectiongap] - layout[oRightSide].top perform layout[oRightSide].render ~position the armory layout within the remaining space on the right layout[oArmory].width = colwidth layout[oArmory].top = layout[oRightSide].bottom + scenevalue[sectiongap] layout[oArmory].left = layout[oLogos].left ~if the top of the armory layout is below the adjustments layout, there is no ~room for it, so hide it; otherwise, set height and render the layout properly if (layout[oArmory].top >= layout[oAdjust].top) then layout[oArmory].visible = 0 else layout[oArmory].height = layout[oAdjust].top - scenevalue[sectiongap] - layout[oArmory].top perform layout[oArmory].render endif ~position the gear layout in whatever space is left on the right layout[oGear].width = colwidth layout[oGear].top = layout[oArmory].bottom + scenevalue[sectiongap] layout[oGear].left = layout[oLogos].left ~if the top of the gear layout is below the adjustments layout, there is no ~room for it, so hide it; otherwise, set height and render the layout properly if (layout[oGear].top >= layout[oAdjust].top) then layout[oGear].visible = 0 else layout[oGear].height = layout[oAdjust].top - scenevalue[sectiongap] - layout[oGear].top perform layout[oGear].render endif ]]></position> </sheet>
Adjustments
Since the adjustments layout is always going to be given priority in the lower right corner, we should make sure it's all working now. The only time the adjustments table appears is when one or more in-play or permanent adjustments are enabled for the character when the sheet is printed. For testing, we can add a few of each and preview the character sheet. They appear nicely in the lower right corner, so there's nothing further we need to do to handle them now.
Injuries and Health
The plan is to always show any injuries in the lower right corner, along with a place to track health and power points. The injuries can be managed as a simple table portal, while the condition tracking requires that we implement something that is a little more complex. We could keep things easy by just inserting a graphic, or we could actually construct something that will work. We'll try the latter, without going overboard.
Injuries Table
Before we do that, though, we'll first get the injuries table into place. The table simply lists all injuries, and we'll need to provide a template that displays each individual injury appropriately. For the template, we just need something simple that will show the name of the injury. We can clone and adapt the template used for adjustments. This results in the table portal and template shown below.
<portal id="oInjury" style="outNormal"> <output_table component="Injury" showtemplate="oInjury"> <headertitle><![CDATA[ @text = "Injuries" ]]></headertitle> </output_table> </portal> <template id="oInjury" name="Output Injuries Table" compset="Injury" marginhorz="10"> <portal id="name" style="outNormLt"> <output_label field="name"> </output_label> </portal> <position><![CDATA[ ~our height is the vertical extent of our portals height = portal[name].height if (issizing <> 0) then done endif ~size the name to fit the available space portal[name].width = width perform portal[name].sizetofit[36] perform portal[name].autoheight perform portal[name].centervert ]]></position> </template>
Once the table is in place, we need to add it to the layout. We'll make it 40% of the total width of the layout, leaving the rest for condition tracking. Unlike our usual approach, we cannot use "autoplace" for this table. If the table is empty, automatic placement will hide the table, which will leave an empty gap in the character sheet that will look odd. So we need to position and size the table ourselves. Since the table will use no more space than it requires, we can set the height to something very large and HL will truncate it to the appropriate height. This results in the revised layout below.
<layout id="oAdjust"> <portalref portal="oAdjust"/> <portalref portal="oInjury"/> <position><![CDATA[ ~position the adjustments table at the top perform portal[oAdjust].autoplace ~position the injuries table in the left 40% beneath the adjustments ~Note: We don't use autoplace, since we never want the table hidden portal[oInjury].left = 0 portal[oInjury].top = autotop + scenevalue[sectiongap] portal[oInjury].width = width * .4 portal[oInjury].height = 5000 ~our layout height is the bottom extent of the elements within height = portal[oInjury].bottom ]]></position> </layout>
If our character has no injuries, though, this looks strange. All we see is the header above the table and nothing within it. What we need is a portal that simply says "None" that we can show just below the header if the table is empty. So we'll define a new label portal and show it appropriately via the layout. The new portal and updated layout are shown below.
<portal id="oNoInjury" style="outNormal"> <output_label text="-None-"> </output_label> </portal> <layout id="oAdjust"> <portalref portal="oAdjust"/> <portalref portal="oInjury"/> <portalref portal="oNoInjury"/> <position><![CDATA[ ~position the adjustments table at the top perform portal[oAdjust].autoplace ~position the injuries table in the left 40% beneath the adjustments ~Note: We don't use autoplace, since we never want the table hidden portal[oInjury].left = 0 portal[oInjury].top = autotop + scenevalue[sectiongap] portal[oInjury].width = width * .4 portal[oInjury].height = 5000 ~if there are no injuries, indicate that fact if (portal[oInjury].itemsshown <> 0) then portal[oNoInjury].visible = 0 else perform portal[oNoInjury].centeron[horz,oInjury] perform portal[oNoInjury].alignrel[ttob,oInjury,10] endif ~our layout height is the bottom extent of the elements within height = maximum(portal[oInjury].bottom,portal[oNoInjury].bottom) ]]></position> </layout>
Health in Template
For tracking health, the standard representation in Savage Worlds is a Wounds progression from one end and a Fatigue progression from the other end. This can probably be best implemented via a handful of portals. Since the contents of those portals have no relationship to any aspects of the character, no template is truly necessary. However, it's probably easiest to manage this via a template, so that's the approach we'll use.
We need a total of eight portals within our template. We need one to indicate "Wounds" on the left and another to indicate "Fatigue" on the right. We then need the "Incapacitated" indicator as a third portal, along with three progressive increments for wounds and two for fatigue. We'll place the two labels in the upper left and upper right corners of the span within which the condition tracking is shown. The six health levels will then be arrayed across the full width of the span. This results in a template that should look like the following.
<template id="oCondition" name="Output Condition" compset="Actor"> <portal id="wounds" style="outNameLg"> <output_label text="Wounds"> </output_label> </portal> <portal id="fatigue" style="outNameLg"> <output_label text="Fatigue"> </output_label> </portal> <portal id="wound1" style="outNameLg"> <output_label text="-1"> </output_label> </portal> <portal id="wound2" style="outNameLg"> <output_label text="-2"> </output_label> </portal> <portal id="wound3" style="outNameLg"> <output_label text="-3"> </output_label> </portal> <portal id="fatigue1" style="outNameLg"> <output_label text="-1"> </output_label> </portal> <portal id="fatigue2" style="outNameLg"> <output_label text="-2"> </output_label> </portal> <portal id="incapac" style="outNameLg"> <output_label text="INC"> </output_label> </portal> <position><![CDATA[ ~position the labels at the top portal[wounds].top = 0 portal[fatigue].top = 0 ~position all health levels beneath the labels with a common baseline perform portal[wound1].alignrel[ttob,wounds,10] perform portal[wound2].alignrel[btob,wound1,0] perform portal[wound3].alignrel[btob,wound1,0] perform portal[fatigue1].alignrel[btob,wound1,0] perform portal[fatigue2].alignrel[btob,wound1,0] perform portal[incapac].alignrel[btob,wound1,0] ~position the labels at the left and right edges portal[wounds].left = 0 perform portal[fatigue].alignedge[right,0] ~put the -1 levels at each end portal[wound1].left = 0 perform portal[fatigue1].alignedge[right,0] ~determine the remaining span over which the portals extend var span as number span = portal[fatigue1].left - portal[wound1].right ~tally the total width of the remaining health level portals var used as number used = portal[wound2].width + portal[wound3].width used += portal[fatigue2].width + portal[incapac].width ~divvy up the remaining horizontal space into equal pieces and use as spacing ~for positioning the various health level horizontally var each as number each = span - used each = round(each / 5,0,0) perform portal[wound2].alignrel[ltor,wound1,each] perform portal[wound3].alignrel[ltor,wound2,each] perform portal[fatigue2].alignrel[rtol,fatigue1,-each] ~size and center the incapacitated cell in the remaining space each = (portal[fatigue2].left - portal[wound3].right - portal[incapac].width) / 2 perform portal[incapac].alignrel[ltor,wound3,each] ~our height is the bottom extent of the portals height = portal[wound1].bottom ]]></position> </template>
Integration Into Layout
Now that we've got our template created, we need to integrate it into the layout. We first add a "templateref" element. Since the template doesn't actually reference any live data within the portfolio, we can hook the template up to any pick or thing we choose. In general, the best tactic in situations like this is to utilize the "actor" pick, so that's what we'll do.
Once the template is part of the layout, we then need to position it properly. It gets placed to the right of the injuries table, leaving a little bit of a gap. The top of the template should be the same as the top of the injury table. The template will calculate its own height when it is rendered. Once rendered, it's conceivable (albeit unlikely) that the template will be taller than the table, so we need to set our height to the tallest of the two visual elements. This results in the updated layout shown below.
<layout id="oAdjust"> <portalref portal="oAdjust"/> <portalref portal="oInjury"/> <portalref portal="oNoInjury"/> <templateref template="oCondition" thing="actor" ispick="yes"/> <position><![CDATA[ ~position the adjustments table at the top perform portal[oAdjust].autoplace ~position the injuries table in the left 40% beneath the adjustments ~Note: We don't use autoplace, since we never want the table hidden portal[oInjury].left = 0 portal[oInjury].top = autotop + scenevalue[sectiongap] portal[oInjury].width = width * .4 portal[oInjury].height = 5000 ~if there are no injuries, indicate that fact if (portal[oInjury].itemsshown <> 0) then portal[oNoInjury].visible = 0 else perform portal[oNoInjury].centeron[horz,oInjury] perform portal[oNoInjury].alignrel[ttob,oInjury,10] endif ~position the condition template to the right of the injuries template[oCondition].left = portal[oInjury].right + 50 template[oCondition].top = portal[oInjury].top template[oCondition].width = width - template[oCondition].left perform template[oCondition].render ~our layout height is the bottom extent of the elements within height = maximum(portal[oInjury].bottom,template[oCondition].bottom) ]]></position> </layout>
Refining the Health Display
After previewing our updated character sheet, the health region looks passable, but it would really benefit by having a soft grey border around the region to set it off nicely. Unlike portals, we cannot simply put a border around a template. So we'll need to add the border via a portal within the template. This entails adding a new portal whose sole purpose is to provide the border. We then setup a margin around the edges by positioning the portals with a gap all the way around. Once that's done, we can size the border portal to encompass the full region of the other portals, resulting in a border being drawn around a collection of portals. The new portal and the revised Position script are shown below.
<portal id="border1" style="outGreyBox"> <output_label text=" "> </output_label> </portal> <position><![CDATA[ ~leave a margin around all edges to draw our border var margin as number margin = 10 ~position the labels at the top portal[wounds].top = margin portal[fatigue].top = margin ~position all health levels beneath the labels with a common baseline perform portal[wound1].alignrel[ttob,wounds,10] perform portal[wound2].alignrel[btob,wound1,0] perform portal[wound3].alignrel[btob,wound1,0] perform portal[fatigue1].alignrel[btob,wound1,0] perform portal[fatigue2].alignrel[btob,wound1,0] perform portal[incapac].alignrel[btob,wound1,0] ~position the labels at the left and right edges portal[wounds].left = margin perform portal[fatigue].alignedge[right,-margin] ~put the -1 levels at each end portal[wound1].left = margin perform portal[fatigue1].alignedge[right,-margin] ~determine the remaining span over which the portals extend var span as number span = portal[fatigue1].left - portal[wound1].right ~tally the total width of the remaining health level portals var used as number used = portal[wound2].width + portal[wound3].width used += portal[fatigue2].width + portal[incapac].width ~divvy up the remaining horizontal space into equal pieces and use as spacing ~for positioning the various health level horizontally var each as number each = span - used each = round(each / 5,0,0) perform portal[wound2].alignrel[ltor,wound1,each] perform portal[wound3].alignrel[ltor,wound2,each] perform portal[fatigue2].alignrel[rtol,fatigue1,-each] ~size and center the incapacitated cell in the remaining space each = (portal[fatigue2].left - portal[wound3].right - portal[incapac].width) / 2 perform portal[incapac].alignrel[ltor,wound3,each] ~set the first border to span the full dimensions of the health tracker portal[border1].width = width portal[border1].height = portal[wound1].bottom + margin ~our height is the bottom extent of the border height = portal[border1].bottom ]]></position>
Power Points Tracking
The final thing we need to add to the condition tracking section is a place to mark off power points that are spent. All the various character sheets tend to show 30 boxes, so we'll do the same. We'll place a sequence of boxes beneath the health tracking region and allow the user to check boxes as points are spent. Given our limited space, we won't be able to fit 30 boxes, so we'll instead output two rows of 15 boxes each. To make tracking easier for the user, we'll ensure that every fifth box is larger and stands out from the others.
Synthesizing output like we need is most easily handled via a script. By using some nested "for/next" loops, we can construct the text for output. However, we need to figure out how we're going to output boxes without having to use bitmaps. The answer is the "Wingdings" font that is built into Windows. We can utilize the "Character Map" tool provided by Windows to view the various characters supported by each font. Only a small number of fonts are built into every installation of Windows, and these include the "Webdings" font and the "Wingdings" font. Scanning through the character map for "Wingdings", we spot a suitable box character that we can use (character code "A8" in hex).
Let's think through our logic now. We start by switching to the "Wingdings" font via the "{font Wingdings}" encoded text sequence. Between each character, we'll need to insert a tiny extra bit of spacing, which can be accomplished via the "{horz 1}" specification. For every fifth character, we need to increase the font size and then shrink it back down, which entails using "{size 52}". After every 15 characters, a line break needs to be inserted.
Since the border looks good around the health tracking, we'll use a separate border around the power points tracking region. So we'll need two new portals, with one being generating the text for output and the other providing a border. This yields the two new portals below and the additional code for the Position script given below.
<portal id="power" style="outPlain"> <output_label> <labeltext><![CDATA[ var i as number var j as number var k as number @text = "{font Wingdings}" for i = 1 to 2 for j = 1 to 3 for k = 1 to 4 @text &= chr(168) & "{horz 1}" next @text &= "{size 52}" & chr(168) & "{size 40}" next @text &= "{br}" next ]]></labeltext> </output_label> </portal> <portal id="border2" style="outGreyBox"> <output_label text=" "> </output_label> </portal>
~position and size the power level condition perform portal[power].alignrel[ttob,border1,10 + margin] portal[power].left = margin portal[power].width = width - margin ~set the first border to span the full dimensions of the power tracker portal[border2].top = portal[power].top - margin portal[border2].width = width portal[border2].height = portal[power].height + margin * 2 ~our height is the bottom extent of the second border height = portal[border2].bottom
This doesn't provide anything impressive, but it offers a clean and effective solution in a very compact space. This will do quite nicely.