Synthesizing Descriptions for Display (Savage)
Context: HL Kit Authoring Examples Savage Worlds Walk-Through
The description text that is shown to the user often includes more than the actual description text entered for each thing. If the thing has fields, the description text viewed by users will typically be improved by integrating the values of some or all of those fields. This synthesized description text is used from all sorts of places within HL. Fortunately, the Skeleton data files integrate a centralized mechanism for synthesizing description text for virtually all types of things. This makes it relatively easy to extend the mechanism for use with your own custom components.
At the hub of the description logic is a re-usable procedure with the id "Descript". This procedure is found in the file "procedures.dat" and orchestrates the synthesis of appropriate description text for just about any type of thing. Whenever you want to display the description text of a thing, your script can call this procedure to do the work. Then you can use the synthesized result for display to the user. The key advantage of using a centralized mechanism like this is that you only have to write the logic for synthesizing the description once, after which you can re-use it from anywhere. In addition, since the one mechanism intelligently supports all different things, you don't have to remember to invoke the correct logic from the correct places - just use the same procedure from everywhere.
The "Descript" procedure utilizes a standard sequence in which it pieces together a description for output. This is good for users, because all the information appears in a consistent fashion for each type of thing. The procedure keys on facets of the thing to determine the nature of what it's processing and selectively includes/excludes different types of information. For example, if the thing is gear, appropriate gear details are included. Similarly, failed pre-requisite details are only output if a thing is being processed (instead of a pick).
In the middle of the procedure, the nature of the thing is keyed upon to call an appropriate helper procedure. This allows the specifics of each particular type of thing to be encapsulated into a separate procedure. It accomplishes this by keying on an appropriate "component" tag and calling the associated procedure. Suitable helper procedures for a handful of different components are provided within the Skeleton files for use by the "Descript" procedure. As part of all this, an "InfoWeapon" procedure is defined that handles all generic details for all types of weapons. Both the "InfoMelee" and "InfoRange" procedures call "InfoWeapon" to output their common aspects. The net result is a well-organized structure that is easily maintained and easily extended.
You'll also see a "MouseInfo" procedure within the file. This procedure is designed for use by MouseInfo scripts to synthesize the output for display. It is designed for a slightly different output display, but it calls the "Descript" procedure to do the vast majority of output synthesis.
Integrating the "Descript" Procedure
Anywhere that you want to display the description text for a thing or pick, you can call the "Descript" procedure and use its results. The procedure is designed for use from any "info" script context as opposed to only Description scripts, so it is unable to use any special symbols. Instead, the synthesized results are placed into a "descript" string variable. If the caller wants to access the results, it must also define the same variable prior to calling the procedure. So the typical use of the "Descript" procedure amounts to the following script code.
var descript as string call Descript @text = descript
The "Descript" procedure has one optional parameter that you can specify. Normally, the list of failed pre-requisites is only included when synthesizing the description for a thing. However, there may be times where you want to include failed pre-requisites for a pick. In this situation, you can force the procedure to include the extra information via the "isprereq" numeric variable. Since variables are inherited by called procedures, you can define the "isprereq" variable, set it to a non-zero value, and then call the script. An example of this usage is shown below.
var descript as string var isprereq as number isprereq = 1 call Descript @text = descript
NOTE! Since numeric variables always default to zero, omitting the variable is the same as defining it to zero. So we normally just omit it, unless there is an important reason to make the default behavior explicitly clear.
Integrating the "MouseInfo" Procedure
The "MouseInfo" procedure is used just like the "Descript" procedure, except that it is designed for use from within MouseInfo scripts. The synthesized results from this procedure are placed in a string variable named "mouseinfo", but it otherwise works no differently. The "isprereq" variable is also supported. Consequently, the typical usage of the "MouseInfo" procedure looks like the script code shown below.
var mouseinfo as string call MouseInfo @text = mouseinfo
Putting "Descript" and "MouseInfo" to Use
Now that we have our "Arcane" tab in place, we're going to want to show more than just the name and description text for the various arcane powers. We're also going to want to see the power point cost, range, duration, etc. All that information is specified via fields, which won't get displayed by default. So we need to utilize our own Description script for the table portal and that means we'll put the "Descript" and "MouseInfo" procedures to use.
But wait. HL is already using the "Descript" and "MouseInfo" procedures. By default, the Kit automatically hooks up these two procedures as the default behaviors used whenever a Description script or MouseInfo script is required. If we don't specify anything specific for one of these scripts, the "Descript" or "MouseInfo" procedure, as appropriate, will be invoked. So we don't have to do anything special to integrate our special handling logic.
Extending the "Descript" Procedure
In keeping with the conventions used in the Skeleton files, we're going to extend the "Descript" procedure to include detailed output for arcane powers. To do this, we'll need to add a new procedure that synthesizes the custom details appropriately. We also need to call the procedure properly from within the "Descript" procedure. We'll name our new procedure "InfoPower" to match the component and start by splicing it into the "Descript" procedure. Looking at the "Descript" procedure, you'll see a section of script code that checks for various component tags and calls a different procedure for each. That's where we want to add our new code. At the end of the "if/then/else" block, we'll add a test for the "Power" component and call our new procedure. The resulting code block should look like the following.
iteminfo = "" if (tagis[component.WeapRange] <> 0) then call InfoRange elseif (tagis[component.WeapMelee] <> 0) then call InfoMelee elseif (tagis[component.Defense] <> 0) then call InfoDef elseif (tagis[component.Power] <> 0) then call InfoPower endif
Now we need to add our new procedure. We'll place it immediately beneath the "Descript" procedure to keep all the info-related mechanisms together. Our new procedure needs to output the various fields that are unique to arcane powers on separate lines. We'll use a format similar to the one used in the rulebook so that everything looks familiar to the user. That means blending any maintenance cost into the duration. Since the trappings are designed to be edited by the user once the power is added, we need to gracefully handle a situation where the user has left the trappings empty. The net result is a procedure that looks like the one below.
<procedure id="InfoPower" context="info"><![CDATA[ ~declare variables that are used to communicate with our caller var iteminfo as string iteminfo = "" ~report the power point cost iteminfo &= "Power Points: " & field[powPoints].text & "\n" ~report the range iteminfo &= "Range: " & field[powRange].text & "\n" ~report the duration and any maintenance cost (omitting if there is none) iteminfo &= "Duration: " & field[powLength].text if (field[powMaint].isempty = 0) then iteminfo &= " (" & field[powMaint].text & ")" endif iteminfo &= "\n" ~report the trappings for the power iteminfo &= "Trappings: " if (field[powTraps].isempty <> 0) then iteminfo &= "n/a" else iteminfo &= field[powTraps].text endif iteminfo &= "\n" ]]></procedure>
Once our new procedure is in place, re-load the data files and click on the table to add a new arcane power. In the description window on the right, you'll see all of the fields properly listed for each arcane power.