Statblock Output (Savage): Difference between revisions
No edit summary |
|||
(6 intermediate revisions by the same user not shown) | |||
Line 47: | Line 47: | ||
</pre> | </pre> | ||
Now we need to revise the procedure contents. The current procedure | Now we need to determine whether to revise the procedure contents. The current procedure appears to work perfectly for attributes and skills, so there is are no changes required. | ||
Attributes and skills are now handled, so that leaves derived traits. Savage Worlds uses a different format for derived traits, so we need to implement them separately. Since the format is only used for derived traits, we can either write a procedure that does it or put the code directly into the Synthesize script. If the Synthesize script was large and complex, it would probably we worth using a separate procedure, but the script is relatively simple, so we'll just insert the code directly. | Attributes and skills are now handled, so that leaves derived traits. Savage Worlds uses a different format for derived traits, so we need to implement them separately. Since the format is only used for derived traits, we can either write a procedure that does it or put the code directly into the Synthesize script. If the Synthesize script was large and complex, it would probably we worth using a separate procedure, but the script is relatively simple, so we'll just insert the code directly. | ||
Line 121: | Line 103: | ||
endif | endif | ||
details &= eachpick.field[name].text | details &= eachpick.field[name].text | ||
details &= " (" & eachpick.field[ | details &= " " & eachpick.field[wpNetAtk].text | ||
details &= " (" & eachpick.field[wpShowDmg].text | |||
if (eachpick.tagis[component.WeapRange] <> 0) then | if (eachpick.tagis[component.WeapRange] <> 0) then | ||
details &= ", " & eachpick.field[ | details &= ", " & eachpick.field[wpRange].text | ||
endif | endif | ||
details &= ")" | details &= ")" | ||
Line 167: | Line 150: | ||
===Special Abilities=== | ===Special Abilities=== | ||
The | The next component of statblock output is the various special abilities for the character. All of the abilities are lumped together within the statblock, including edges, hindrances, and racial abilities. We could use a similar approach to gear for abilities, but all abilities are simply output with a name and summary, so can use a single mechanism for all abilities. | ||
We only want to output special abilities if we have at least one. Fortunately, we can easily detect this. Since all abilities derived from the shared "Ability" component, we can check to see if the hero contains any picks that possess the "component.Ability" tag. If so, then we know that we have at least one ability to output and can do so. We'll output the various abilities via a called procedure, although we could just as easily include the code directly here. This results in the following code for orchestrating the output of abilities. | We only want to output special abilities if we have at least one. Fortunately, we can easily detect this. Since all abilities derived from the shared "Ability" component, we can check to see if the hero contains any picks that possess the "component.Ability" tag. If so, then we know that we have at least one ability to output and can do so. We'll output the various abilities via a called procedure, although we could just as easily include the code directly here. This results in the following code for orchestrating the output of abilities. | ||
Line 189: | Line 172: | ||
~output a list of all abilities | ~output a list of all abilities | ||
foreach pick in hero where "component.Ability" sortas SpecialTab | foreach pick in hero where "component.Ability" sortas SpecialTab | ||
append chr(149) & eachpick.field[shortname].text & ": " | |||
append eachpick.field[summary].text & @newline | |||
nexteach | |||
]]></procedure> | |||
</pre> | |||
That's incredibly simple. Because of it's simplicity, we're probably better off eliminating the extra procedure and just putting the logic directly into the Synthesize script. So we'll move the logic into the script, after which we can delete the procedure. The revised code within the Synthesize script should now look like below. | |||
<pre> | |||
~output special abilities | |||
if (hero.haschild["component.Ability"] <> 0) then | |||
append @boldon & "Special Abilities:" & @boldoff & @newline | |||
foreach pick in hero where "component.Ability" sortas SpecialTab | |||
append chr(149) & eachpick.field[shortname].text & ": " | |||
append eachpick.field[summary].text & @newline | |||
nexteach | |||
endif | |||
</pre> | |||
===Arcane Powers=== | |||
The final element of statblock output that we're missing is arcane powers. We can handle the arcane powers in the exact same way as special abilities. The key differences are the tag expression, we don't have to worry about sorting different types of powers, and there is no "shortname" field for powers. We make those changes and end up with the following code for outputting arcane powers. | |||
<pre> | |||
~output arcane powers | |||
if (hero.haschild["component.Power"] <> 0) then | |||
append @boldon & "Arcane Powers:" & @boldoff & @newline | |||
foreach pick in hero where "component.Power" | |||
append chr(149) & eachpick.field[name].text & ": " | append chr(149) & eachpick.field[name].text & ": " | ||
append eachpick.field[summary].text & @newline | append eachpick.field[summary].text & @newline | ||
nexteach | nexteach | ||
endif | |||
</pre> | </pre> |
Latest revision as of 03:32, 10 February 2009
Context: HL Kit … Authoring Examples … Savage Worlds Walk-Through
Overview
Most game systems have a standard format for presenting a character in a relatively compact, text-only format. The more commonly used term for this format is a "statblock". You'll find NPCs and monsters generally presented using this format within the various rulebooks. Savage Worlds has such a format, so we'll now make sure that we generate the appropriate output.
The Game Plan
The statblock format we'll use for Savage Worlds can be found in the Bestiary section of the core rulebook. It's a simple text format that makes use of bold text for important names. While most creatures in the Bestiary don't possess any gear, there are a few that do. We'll be using the way those entries are formatted as a guideline for how we should be synthesizing our statblock output.
All statblock output is generated via a Synthesize script within a "dossier" element. We're going to start with the statblock that is provided by the Skeleton data files. This starting point can be readily adapted for our purposes with Savage Worlds. It can be found within the file "out_statblock.dat", which also includes a number of procedures that are called directly from the Synthesize script.
How Output Works
As mentioned above, text output is generated via a Synthesize script. Since text output is really just a single big stream of text, it's very clunky to require the author to keep remembering to append data to a string variable. It's also very inefficient from a scripting standpoint. Consequently, the Kit provides a special mechanism that is is specific to text output. The text being synthesized is managed internally by HL and the "append" statement allows the author to easily tack more material onto the end of the output. This results in a simplified process for authors, although it also requires the output be generated in a serialized fashion.
The general mechanism is designed to support various different types of output, including plain text, HTML, and BBCode. In order to make this easy for authors, there are a number of different special symbols that are pre-defined by the Kit. Based on the output format chosen by the user, these symbols map to the appropriate codes for that format. For example, the "@boldon" symbol is used to enable bold text within the output. When the user selects HTML output, this symbol maps to "{b}", while it is empty for plain text output. This allows authors to synthesize the output once and let HL do the work of tailoring it to the appropriate format.
The following sequence of script code will output a series of attributes as text, where each is listed on a new line, the name of the field is in bold, and the description is not.
foreach pick in hero where "component.Attribute" append @boldon & eachpick.field[name].text & @boldoff append ": " & eachpick.field[description] append @newline nexteach
Outputting the Basics
The mechanism provided by the Skeleton files generates all of the basic details for a generic statblock. We're going to adapt it to the specifics of Savage Worlds, and we'll start with the attributes, skills, and derived traits.
The sample script includes the age as an example of inserting personal information about the character. The Savage Worlds format does not include such data, so we need to omit it. That means deleting the code that outputs the age.
For attributes and skills, a procedure is used that is passed in the tag expression to select the proper picks. In both uses, we need to modify the tag expression to omit the attributes and skills that are hidden from output. This results in the revised code below.
~output attributes append @boldon & "Attributes: " & @boldoff tagexpr = "component.Attribute & !Hide.Attribute" call sbtraits ~output skills append @boldon & "Skills: " & @boldoff tagexpr = "component.Skill & !Hide.Skill" call sbtraits
Now we need to determine whether to revise the procedure contents. The current procedure appears to work perfectly for attributes and skills, so there is are no changes required.
Attributes and skills are now handled, so that leaves derived traits. Savage Worlds uses a different format for derived traits, so we need to implement them separately. Since the format is only used for derived traits, we can either write a procedure that does it or put the code directly into the Synthesize script. If the Synthesize script was large and complex, it would probably we worth using a separate procedure, but the script is relatively simple, so we'll just insert the code directly.
The Skeleton files provide logic for outputting derived traits after special abilities. We'll start by moving that code up to just below the output of skills. Once that's done, we'll adapt the code. Since Savage Worlds uses a comma-separated list for derived traits, we need to track when we need to insert a comma. We'll use the same technique as the various procedures, which have an "ismore" variable that starts at zero and gets changed to one after something is output. If the variable is non-zero, we need to insert a comma before the next item. This yields the following block of code.
~output derived traits var ismore as number ismore = 0 foreach pick in hero where "component.Derived & !Hide.Trait" sortas explicit if (ismore <> 0) then append ", " endif append @boldon & eachpick.field[name].text & ": " & @boldoff append eachpick.field[trtDisplay].text ismore = 1 nexteach append @newline
Gear Output
After all of the traits are output, the Savage Worlds format includes whatever gear the character possesses. All weapons, armor, and miscellaneous gear are lumped into this one block of material. In the case of weapons, the damage and range are shown in parentheses. In the case of armor, we'll include the defense rating and any parry bonus in parentheses. Simple equipment will be listed by name, along with any quantity possessed.
The first thing we need to do is handle how all the gear will be assembled for output. We need a single, comma-separate list, but we also need to synthesize each type of gear differently using separate procedures. To deal with this, we need to build up our gear list as a string. Once the final string is constructed, we can then output it. This results in the logic below.
~synthesize all gear var details as string details = "" call sbweapons call sbarmor call sbgear if (empty(details) = 0) then append @boldon & "Gear: " & @boldoff & details & @newline endif
Now we need to go into each of the procedures we rely upon and make them work the way they should. Within each of the procedures, we first need to setup the "details" variable for use as a parameter. The Synthesize script defines "details" and initializes it to empty. Each procedure will then append its items to the end of the "details" variable. This way, each procedure will build on the results of the preceding procedures.
Each procedure must also determine whether to start with a comma before the first item. Each procedure maintains it's own notion of whether a comma is needed, and its state must be initialized based on whether we already have anything within the "details" variable. If "details" is non-empty, we assume we'll need a comma before the first item we add in the procedure.
We can now adapt the existing procedures for weapons and armor to generate the text as we outline above. We can also add a new procedure for basic equipment, which will use the "grStkName" field to incorporate the quantity information. This results in the three procedures below.
<procedure id="sbweapons" scripttype="synthesize"><![CDATA[ var details as string var ismore as number ismore = !empty(details) ~output a list of all weapons foreach pick in hero where "component.WeaponBase" sortas Armory if (ismore <> 0) then details &= ", " endif details &= eachpick.field[name].text details &= " " & eachpick.field[wpNetAtk].text details &= " (" & eachpick.field[wpShowDmg].text if (eachpick.tagis[component.WeapRange] <> 0) then details &= ", " & eachpick.field[wpRange].text endif details &= ")" ismore = 1 nexteach ]]></procedure> <procedure id="sbarmor" scripttype="synthesize"><![CDATA[ var details as string var ismore as number ismore = !empty(details) ~output the details of all armor foreach pick in hero where "component.Defense" sortas Armory if (ismore <> 0) then details &= ", " endif details &= eachpick.field[name].text details &= " (" & signed(eachpick.field[defDefense].text) if (eachpick.tagis[component.Shield] <> 0) then if (eachpick.field[defParry].value <> 0) then details &= ", Parry" & signed(eachpick.field[defParry].text) endif endif details &= ")" ismore = 1 nexteach ]]></procedure> <procedure id="sbgear" scripttype="synthesize"><![CDATA[ var details as string var ismore as number ismore = !empty(details) ~output the list of all gear foreach pick in hero where "component.Equipment" if (ismore <> 0) then details &= ", " endif details &= eachpick.field[grStkName].text nexteach ]]></procedure>
Special Abilities
The next component of statblock output is the various special abilities for the character. All of the abilities are lumped together within the statblock, including edges, hindrances, and racial abilities. We could use a similar approach to gear for abilities, but all abilities are simply output with a name and summary, so can use a single mechanism for all abilities.
We only want to output special abilities if we have at least one. Fortunately, we can easily detect this. Since all abilities derived from the shared "Ability" component, we can check to see if the hero contains any picks that possess the "component.Ability" tag. If so, then we know that we have at least one ability to output and can do so. We'll output the various abilities via a called procedure, although we could just as easily include the code directly here. This results in the following code for orchestrating the output of abilities.
~output special abilities if (hero.haschild["component.Ability"] <> 0) then append @boldon & "Special Abilities:" & @boldoff & @newline call sbability endif
Lastly, we need to implement the procedure that outputs the actual abilities. Since we have all of the abilities lumped into one list, we should organize it appropriately to show racial abilities, edges, and hindrances together. The most obvious way to do this is to use three different "foreach" loops, but there is an easier way. If we use a single "foreach" loop on all abilities and sort the sequence appropriately, we can get the desired order. We already have a sort set that will work perfectly for us, and that's the "SpecialTab" sort set. This sort set organizes everything by type, and since we only have abilities in our list, those abilities will be sorted just the way we want them.
When outputting the individual special abilities, the Savage Worlds format indents each ability. Unfortunately, there is no reliable way to indent with the various different text formats we need to support. So the easiest solution is to simply ignore the indentation and otherwise output the abilities using the same presentation style.
Putting this all together yields a procedure that looks the one below.
<procedure id="sbability" scripttype="synthesize"><![CDATA[ ~output a list of all abilities foreach pick in hero where "component.Ability" sortas SpecialTab append chr(149) & eachpick.field[shortname].text & ": " append eachpick.field[summary].text & @newline nexteach ]]></procedure>
That's incredibly simple. Because of it's simplicity, we're probably better off eliminating the extra procedure and just putting the logic directly into the Synthesize script. So we'll move the logic into the script, after which we can delete the procedure. The revised code within the Synthesize script should now look like below.
~output special abilities if (hero.haschild["component.Ability"] <> 0) then append @boldon & "Special Abilities:" & @boldoff & @newline foreach pick in hero where "component.Ability" sortas SpecialTab append chr(149) & eachpick.field[shortname].text & ": " append eachpick.field[summary].text & @newline nexteach endif
Arcane Powers
The final element of statblock output that we're missing is arcane powers. We can handle the arcane powers in the exact same way as special abilities. The key differences are the tag expression, we don't have to worry about sorting different types of powers, and there is no "shortname" field for powers. We make those changes and end up with the following code for outputting arcane powers.
~output arcane powers if (hero.haschild["component.Power"] <> 0) then append @boldon & "Arcane Powers:" & @boldoff & @newline foreach pick in hero where "component.Power" append chr(149) & eachpick.field[name].text & ": " append eachpick.field[summary].text & @newline nexteach endif