Character Sheet Phase 3 (Savage)
In this phase of developing the character sheet, we'll begin work on the right column of the sheet. This side is a bit more complex than the left side, containing both detailed sections (e.g. powers, weapons, and armor) and some interesting relationships between the different sections.
At the top of the right column of the character are the logos for both HL and the game system. These are handled automatically in the Skeleton files and there is nothing we need to do. If we wanted to stack the logos instead of placing them side-by-side, we could do that by simply configuring the orientation within the Position script for the sheet. However, we're trying to keep things compact in this character sheet, so we'll stick with the default behavior.
Eliminate Portrait Handling
The first thing we need to do for the right column is eliminate the character portrait. The Skeleton files include the output of a character portrait at the top if a portrait is specified for the character. In the interest of squeezing all the important information into the one-page sheet, we'll remove it from the sheet.
Unlike the way we handled the "Health and Power" material previously, we won't completely delete the logic for handling character portraits. We'll likely want to include the portrait in a different character layout in the future, at which point we'll want to use the logic that's provided by the Skeleton files. For this sheet, we'll simply eliminate the reference to the portrait, thereby removing it from this particular sheet without actually deleting it from the data files.
All of the logic for the portrait display resides within the "oPortrait" template. In order to remove the portrait from the character sheet, we simply need to eliminate the reference to this template from the "oRightSide" layout. This consists of deleting the "templateref" and the line of code from the Position script that places the template. Once we do that, the portrait is no longer included in the character sheet.
The next thing we need to worry about on the right side is edges. At this point, you're probably wondering why we're concerned with edges on the right side, since we already output them on the left side. The reason is that a highly complex character might not be able to fit all the edges on the left side. If there are more than will actually fit in the space at the bottom of the left column, only those that fit will be output. If that occurs, we still need to output the rest. One option for handling the extra edges is to use the second "spill-over" page for that purpose. However, edges are very important and should ideally be shown on the main character sheet, so we should output any extras at the top of the right column.
Outputting any remaining edges is actually quite easy. When generating the character sheet, HL automatically keeps track of which picks are actually output to the sheet. Each pick is output only once, which eliminates the need for you to worry about which edges have been output and which ones still need to be output. By simply re-using the existing edges table on the right side, any edges that have not yet been output will be included.
If all of the edges are properly output on the left side, then the table of edges on the right will be empty. By using the auto-place mechanism, an empty table is completely omitted (automatically) when rendering the character sheet. So the behavior we want is handled for us automatically.
This means that all we need to do is reference the existing edges table from within the "oRightSide" layout. Doing that requires adding a "portalref" element for the table and placing the portal properly. The revised layout should look like the following.
<layout id="oRightSide"> <portalref portal="oEdge2"/> <portalref portal="oGear"/> <position><![CDATA[ ~position the various tables appropriately perform portal[oEdge2].autoplace perform portal[oGear].autoplace ~our layout height is the extent of the elements within height = autotop ]]></position> </layout>
Add a bunch of edges to the character so that it will run out of room and need to spill over to the next column, then preview the character again. Everything works as intended for edges, except we've uncovered a new problem. The table of edges on the left extends down to the bottom of the page instead of being limited to the top of the validation region. By default, the "oLeftSide" layout will extend to the bottom of the page. In order to impose the necessary limit, we need to set the bottom of the layout before we render it. This consists of adding a single line of code immediately prior to the "render" statement within the Position script of the sheet, so the block of code that configures and renders the left side layout should look like below.
~position the leftside layout in the upper left corner layout[oLeftSide].width = colwidth layout[oLeftSide].height = extent - layout[oLeftSide].top perform layout[oLeftSide].render
If we wanted to be truly paranoid, we could add spill-over handling for hindrances. Theoretically, if a character has enough skills and hindrances, the list of hindrances could overrun the available space on the left side. However, the feasibility of such a character in practice is unrealistic, so we won't bother.
Moving downward on the right side, we have arcane powers next. Powers need to be handled a lot like weapons are handled in the provided Skeleton files. There are a number of fields that are best placed in columns within the table. As such, we'll want to use the same approach as with the weapons, wherein we use the same template as both a header and the contents of each item. This makes it possible to easily align the header elements with the contents of the table.
For the moment, though, we're just going to start with a normal table that does not possess a special header. The reason is that trying to setup everything for both the header and contents at the same time results in a more complex operation. When both are done at the same time, you have to get everything into place before you can begin testing and refining. By implementing everything in stages, we'll simplify the process for ourselves and make it possible to get the contents in good shape before we worry about the header.
There is a great deal of information that should ideally be output for arcane powers. In fact, there's so much information that it would be ridiculous to try and squeeze it all into a single line. We need to show the name, point cost, range, duration, maintenance, trappings, and description for each power. However, we're striving to keep everything on a single sheet, so we have to keep things as compact as possible. In order to achieve that goal, we're going to need our starting information to be more compact. The contents of each field that we've entered for display to the user within the UI are too large to fit in a compact space.
Fields for Output
The first thing we need to do is define a new set of fields for powers that will contain shortened versions for display on the character sheet. If these fields are non-empty, we'll use the contents, else we'll use the standard field, assuming that it's already short enough for our purposes. We'll need new fields for the point cost, range, duration, and maintenance. Open the file "miscellaneous.str" and locate the "Power" component. Copy the four existing fields for this information and give them new ids to indicate they are for use with printing. When you're done, the new fields should look similar to those below.
<field id="powPrtPts" name="Power Point Cost" type="static" maxlength="25"> </field> <field id="powPrtRng" name="Range" type="static" maxlength="25"> </field> <field id="powPrtLen" name="Duration" type="static" maxlength="25"> </field> <field id="powPrtMain" name="Maintenance" type="static" maxlength="25"> </field>
Now open the file "thing_arcane.dat" and go through all of the powers. Any time you see one of the four fields with lengthy contents, define the corresponding new field with a suitably shortened version. Keep it all as brief as possible so that we can fit it all into the table for display on the character sheet.
Design the Table
Returning to the character sheet, we need to determine how the contents are going to look for each power. We already decided that we won't be able to squeeze everything into a single line. Now the question is whether we can limit ourselves to two lines or if we need more. So we should probably grab a piece of paper and sketch out how everything will look.
After sketching out various ideas, we'll settle on one that is compact and also includes all the most important details for each power. We'll put the name on the left in a moderate size font. We'll have a line of information that provides the point cost, range, duration, maintenance cost, and trappings. Beneath that line, we'll provide the summary information about how the power works. If a character has a dozen powers, this approach will consume a good amount of space. However, most characters with powers only have a handful of them, so this should generally work out quite well.
With the layout mapped out, the next step is to create a new table portal for our powers. This portal should look like many of the ones we've already defined. The key distinction is that the summary may end up being more than one line in length for some powers. As such, we need to designate the table as containing variable height items. The resulting portal should look like the following.
<portal id="oPower" style="outNormal"> <output_table component="Power" showtemplate="oPowerPick" varyheight="yes"> <headertitle><![CDATA[ @text = "Arcane Powers" ]]></headertitle> </output_table> </portal>
Once defined, we need to integrate the new table portal into the appropriate layout. As we've done before, this entails adding a "portalref" element for the portal and then placing the portal via the Position script. The revised "oRightSide" layout should look like below.
<layout id="oRightSide"> <portalref portal="oEdge"/> <portalref portal="oPower"/> <portalref portal="oGear"/> <position><![CDATA[ ~position the various tables appropriately perform portal[oEdge].autoplace perform portal[oPower].autoplace perform portal[oGear].autoplace ~our layout height is the extent of the elements within height = autotop ]]></position> </layout>
New Styles Required
We now shift our focus to the template used to display each individual power. In order to fit all the information we plan, we're going to need to use a rather small font. Looking through the various styles provided in the file "styles_output.aug", there aren't any that will serve our needs. So we'll need to create a new style that uses a small font. We'll actually need two new styles, with one centering the text and the other aligning the text to the left. This means we need to define the font separately for use by both styles. These new definition should look like the ones below.
<resource id="ofnttiny"> <font face="Arial" size="32"> </font> </resource> <style id="outTiny"> <style_output textcolor="000000" font="ofnttiny" alignment="center"> </style_output> </style> <style id="outTinyLt"> <style_output textcolor="000000" font="ofnttiny" alignment="left"> </style_output> </style>
We need a total of six portals in order to present all the information we selected for inclusion. The portals for showing the point cost and range will use a script that first checks the print-specific field and otherwise defaults to the normal field. The portal for duration will work the same way, with added handling for including any maintenance cost. The remaining portals will simply show the contents of an appropriate field.
We'll put the name on the left and then align the four fields across the top line within columns. The summary text will be placed beneath the line of fields, spanning the same width as the fields, and it can be more than one line in height. If the name is too large to fit, we'll allow it span two lines, since we already need at least two lines of output. Our total height will be determined after we position everything. If the name still doesn't fit, will use the built-in "sizetofit" target reference to have HL incrementally scale the font size downward until the name actually fits. We can then center the name vertically. Putting all this together yields the template shown below.
<template id="oPowerPick" name="Output Powers Table" compset="Power" marginvert="2"> <portal id="name" style="outMedLt"> <output_label field="name"> </output_label> </portal> <portal id="points" style="outTiny"> <output_label> <labeltext><![CDATA[ if (field[powPrtPts].isempty = 0) then @text = field[powPrtPts].text else @text = field[powPoints].text endif ]]></labeltext> </output_label> </portal> <portal id="range" style="outTiny"> <output_label> <labeltext><![CDATA[ if (field[powPrtRng].isempty = 0) then @text = field[powPrtRng].text else @text = field[powRange].text endif ]]></labeltext> </output_label> </portal> <portal id="duration" style="outTiny"> <output_label> <labeltext><![CDATA[ var maint as string if (field[powPrtMain].isempty = 0) then maint = field[powPrtMain].text else maint = field[powMaint].text endif if (field[powPrtLen].isempty = 0) then @text = field[powPrtLen].text else @text = field[powLength].text endif if (empty(maint) = 0) then @text &= " (" & maint & ")" endif ]]></labeltext> </output_label> </portal> <portal id="trapping" style="outTinyLt"> <output_label field="powTraps"> </output_label> </portal> <portal id="summary" style="outPower"> <output_label field="summary" ismultiline="yes"> </output_label> </portal> <position><![CDATA[ ~position all portals with the same baseline as the name; since they use a ~smaller font, they will have a smaller height, so centering them will have ~them appear to float up relative to the name perform portal[points].alignrel[btob,name,0] perform portal[range].alignrel[btob,name,0] perform portal[duration].alignrel[btob,name,0] perform portal[trapping].alignrel[btob,name,0] ~establish suitable fixed widths for the various columns of data portal[name].width = 300 portal[points].width = 100 portal[range].width = 150 portal[duration].width = 200 ~position everything horizontally perform portal[points].alignrel[ltor,name,5] perform portal[range].alignrel[ltor,points,5] perform portal[duration].alignrel[ltor,range,5] perform portal[trapping].alignrel[ltor,duration,5] ~size the trappings portal to whatever space is left portal[trapping].width = width - portal[trapping].left ~size the summary portal to span the area excluding the name portal[summary].left = portal[points].left portal[summary].width = width - portal[summary].left perform portal[summary].autoheight ~position the summary portal beneath the other line of fields perform portal[summary].alignrel[ttob,points,2] ~our height is the base of the summary portal height = portal[summary].bottom ~size the name to fit the available space, then re-center after shrink perform portal[name].sizetofit perform portal[name].autoheight perform portal[name].centervert ]]></position> </template>
Reloading the data files and previewing the character gives us a chance to review what we've got. The name and columns of data look pretty good, but the summary text beneath results in a rather jumbled look. If we output the summary text in a slightly fainter stroke and set it off against a grey background, everything would look significantly better. So we'll define a new style like the one below and use it with the summary text.
<style id="outPower"> <style_output textcolor="404040" backcolor="e8e8e8" font="ofnttiny" alignment="left"> </style_output> </style>
This looks very clear and readable. The amount of space available for showing the trappings of a power are small, but it's enough if the user is succinct with his descriptions of the trappings. So we're good to go for showing the powers.
Adding a Custom Header
It's now time to add the custom header to the table portal. Our header needs to label each column of information within the table. In order to easily synchronize the positions of the column headers with the actual data being output, we'll make use of a dual-purpose template. A dual-purpose template is when the same template is used for both the contents of the table and the header, and there are a number of details involved in making it all work.
The first step is to revise our table portal properly. We need to add a new "headertemplate" attribute that references the same template used as a the show template. We also need to eliminate the "headertitle" element, since we'll be defining our own header. This yields the following new portal.
<portal id="oPower" style="outNormal"> <output_table component="Power" showtemplate="oPowerPick" headertemplate="oPowerPick" varyheight="yes"> </output_table> </portal>
Next up is adding the appropriate portals to the template that will be used for the header. We need one portal for the banner that will span the full width of the table portal. We also need four additional portals for the various labels at the top of each column. For the column headers, the Skeleton files provide the "outHeader" style that is generally an excellent choice. However, we're using a much smaller font for the various fields themselves, so we should use a similar font for the headers. This results in the following new portal definitions.
<portal id="hdrtitle" style="outTitle" isheader="yes"> <output_label text="Arcane Powers"> </output_label> </portal> <portal id="hdrpoints style="outTiny" isheader="yes"> <output_label text="Cost"> </output_label> </portal> <portal id="hdrrange" style="outTiny" isheader="yes"> <output_label text="Range"> </output_label> </portal> <portal id="hdrlength" style="outTiny" isheader="yes"> <output_label text="Dur/Maint"> </output_label> </portal> <portal id="hdrtrap" style="outTiny" isheader="yes"> <output_label text="Trappings"> </output_label> </portal>
The final step is to define a Header script via the "header" element. This script is similar to the Position script, except that it's intended for positioning the various portals within the header. It is invoked after the normal Position script, which allows it to retrieve the positions of the various normal portals for the template and easily position the header portals in relation to them. Consequently, we can position each of the labels directly where they belong without any special hoop jumping. This yields the Header script below.
<header><![CDATA[ ~our header height is the title plus a gap plus the header text height = portal[hdrtitle].height + 2 + portal[hdrpoints].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[hdrpoints].width = portal[points].width portal[hdrrange].width = portal[range].width portal[hdrlength].width = portal[duration].width portal[hdrtrap].width = portal[trapping].width ~center each header label on the corresponding data beneath perform portal[hdrpoints].centeron[horz,points] perform portal[hdrrange].centeron[horz,range] perform portal[hdrlength].centeron[horz,duration] perform portal[hdrtrap].centeron[horz,trapping] ~align all header labels at the bottom of the header region perform portal[hdrpoints].alignedge[bottom,0] perform portal[hdrrange].alignedge[bottom,0] perform portal[hdrlength].alignedge[bottom,0] perform portal[hdrtrap].alignedge[bottom,0] ]]></header>
We can now reload the files and see how our header looks. It looks great, although we should probably include the total number of arcane power points possessed by the character within the title. That way, the total power points is conveniently grouped next to all the powers for the users. We can accomplish this by changing the "hdrtitle" portal to use a script and synthesize the appropriate information via the script. The revised portal should look similar to the one below.
<portal id="hdrtitle" style="outTitle" isheader="yes"> <output_label> <labeltext><![CDATA[ @text = "Arcane Powers (" & #trkmax[trkPower] & " Points)" ]]></labeltext> </output_label> </portal>