User-Configurable Options (Savage)
Context: HL Kit … Authoring Examples … Savage Worlds Walk-Through
Overview
In most game systems, there are a number of optional game mechanics that may be employed. These range from methods for character creation to rules for combat resolution. There are also situations where you'll want to provide users with the ability to customize the contents of character sheets. All of these have one thing in common: the ability for users to enable options that influence how the data files behave.
All user-configurable options are presented to the user within the "Configure Hero" form. These options can be organized into logical groups to make access easier for users. The sections below introduce various ways to utilize user-configurable options within the Savage Worlds data files.
How Sources Work
The term "source" is used because most user-configurable settings will be tied to specific rule supplements for a particular game system. Each of those supplements is a different source of game material, with its own unique characteristics. To keep things organized for the user, the various options are organized into a hierarchy. To keep things simple for the author, the same "source" elements are used for each node in the hierarchy, with different attributes being assigned to dictate the unique behaviors of each node.
Source elements can be designated as children of other sources via the "parent" attribute. This is how you construct the overall hierarchy of sources. If the source "chlid" needs to be shown beneath the source "grouping" in the hierarchy, you would specify the "parent" attribute on the "child" source as "grouping". This approach makes it possible to add new sources to an existing grouping without having to modify the parent. When other authors want to extend your data files by adding support for other source books (or their own game world), this technique allows them to do so independently of your files.
Sources that are intended for user-selection must be designated as such via the "selectable" attribute. Only sources that possess no children are allowed to be selectable. If you want to force the selection of a source, then you can make it non-selectable and mark is as automatically selected.
That brings us to one last configurable facet of sources that we'll cover here. Sources can be marked as selected by default via use of the "default" attribute. By default, sources are not selected until the user selects them. However, you can reverse that behavior so that the source is selected unless the user deselects it. This is useful in situations where you believe most users will normally want to utilize an option. They can turn it off if they want, but the option is enabled if they don't specifically do so.
The question that remains is how sources can be utilized within the data files. Every source has a tag automatically defined by the Kit. These tags always belong to the group with the unique id "source" and possess a tag id matching the id of the source. Therefore, if you define the source "pickme", the tag id "source.pickme" will be automatically defined.
Whenever the user enables the source, the tag is automatically assigned to the hero, and the tag is not assigned then the source is deselected. This makes it possible to easily check the hero for the various source tags to determine if an option has been selected or not. You can reference source tags anywhere within your data files, such as testing them within scripts. You can even use them within ContainerReq and/or Live tag expressions, allowing you to customize the interface or control whether things exist within the data files.
Dark Campaign Settings
The original Savage Worlds rulebook provides some suggestions regarding different campaign settings. For example, there are two "Dark Settings" presented. In a "Flawed Heroes" setting, heroes are allowed to take an additional Minor Hindrance at character creation, while a "Tragic Heroes" setting allow heroes to take an extra Major Hindrance.
Our current data files will report any attempt to do take extra hindrances as a validation error. What we need is the ability for users to toggle these options on if they wish, with our validation rule making the appropriate adjustments. We can model these options easily via the Sources mechanism.
The first thing we need to do is setup a source that will serve as a logical grouping for the user. Each of these options reflects the nature of the campaign setting being played, so that's what we call the grouping. Since this is intended as a grouping, we need to make it non-selectable. This results in the new source shown below, which we can add to the file "control.1st".
<source id="CampType" name="Campaign Settings" selectable="no" description="Options associated with the style of campaign setting being played"> </source>
The next thing we need to do is setup our individual options as sources. We need one option for flawed heroes and another for tragic heroes. Both sources need to reference the "Settings" source we defined above as their parent. This yields the following two new sources.
<source id="Flawed" name="Flawed Heroes" parent="CampType" description="Heroes may select one additional Minor Hindrance, earning one extra Hindrance point"> </source> <source id="Tragic" name="Tragic Heroes" parent="CampType" description="Heroes may select one additional Major Hindrance, earning two extra Hindrance points"> </source>
Our sources are defined and should now appear for the user to select. However, we still need to do something useful with them. The validation test that checks on the limit of hindrances is controlled via the "valHinders" thing, which is defined in the file "thing_validation.dat". We'll need to modify the validation logic to incorporate the handling of these two sources.
Within the Eval Rule script, we assume that there is a maximum of one major and two minor hindrances allowed. We need to change this so that we start with those numbers by default and add in an extra one if the appropriate source tag is defined. We also need to synthesize the validation message via the script so that we can show the proper maximum limits based on whether the options are selected by the user. This yields a revised Eval Rule script that looks like the one below.
<evalrule index="1" phase="Validate" priority="8000" message="Maximum limits exceeded" summary="Limit exceeded"><![CDATA[ ~iterate through all hindrances and tally up the number of majors and minors var major as number var minor as number foreach pick in hero where "component.Hindrance" if (eachpick.field[hinMajor].value = 0) then minor += 1 else major += 1 endif nexteach ~determine our maximum number of major and minor hindrances var max_major as number var max_minor as number max_major = 1 + hero.tagis[source.Tragic] max_minor = 2 + hero.tagis[source.Flawed] ~if we have no more than our maximum major and minor hindrances, we're good if (major <= max_major) then if (minor <= max_minor) then @valid = 1 done endif endif ~synthesize our validation message appropriately @message="Maximum of " & max_major & " major and " & max_minor & " minor hindrances allowed" ~mark associated tabs as invalid container.panelvalid[edges] = 0 ~assign a tag to the hero to indicate the invalid state ~Note: This is used to color highlight the title above hindrances on the tab. perform hero.assign[Hero.BadHinders] ]]></evalrule>
Reload the data files and give our changes a try. Add an extra major hindrance to the character and the validation error should appear. Then selects the "Tragic Heroes" option and the validation error should disappear. Everything is now working as it should.
Heroic Campaign Settings
The original rulebook also a few other campaign setting changes that apply in a heroic or cinematic setting. Two of these adjustments impact character creation and should be incorporated into our data files. The first is for a "Heroic" setting, wherein characters can ignore Rank requirements for edges taken during character creation. The second is an "Epic Heroism" setting, in which all Rank requirements can be ignored.
The first step in implementing these options is to set them up as sources. We'll do this just like we did in the previous example, adding one source for each of the two settings. Both sources must reference the "CampType" source we defined previously, resulting in the two new sources shown below.
<source id="Heroism" name="Heroism" parent="CampType" description="Heroes ignore the Rank requirements for Edges during character creation"> </source> <source id="EpicHero" name="Epic Heroism" parent="CampType" description="Heroes ignore Rank requirements for Edges at all times"> </source>
With our sources defined, we can now shift our focus to properly handling their selection. The pre-requisite handling for the rank is handled within a PreReq test that is defined on the "MinRank" component. We'll find this component in the file "components.core", where we can modify it to support these new sources.
Within the Validate script of the pre-requisite test, the character's rank is tested against the requirements for the item. If the rank test fails, we then need to perform additional tests. This component is used within multiple different component sets, but our sources only apply to Edges. As such, all of our extra tests must only be performed for edges.
If the "Epic Heroism" source is selected, then an edge is always considered to be valid, so we can handle that quickly and easily. However, the "Heroism" source is only applied to edges selected during character creation, so we must determine whether our item satisfies that requirement. Each pick tracks whether it was added during character creation, with the state being accessible via the "creation" target reference. Things don't possess such a state, though, so we instead need to check the global state to determine whether we're in "creation" mode.
Putting all of this logic together results in a revised Validate script that looks like the one below.
<validate><![CDATA[ var rankvalue as number var ranktext as string ~get the minimum rank required for the thing to be valid rankvalue = altthing.field[rnkMinRank].value ~if the minimum rank is satisfied, we're good to go if (herofield[acRank].value >= rankvalue) then @valid = 1 done endif ~if this is an edge, check for special configuration options if (altthing.tagis[component.Edge] <> 0) then ~if this is an epic heroism setting, we're valid if (hero.tagis[source.EpicHero] <> 0) then @valid = 1 done endif ~determine whether this edge is added during creation var iscreate as number if (@ispick = 0) then iscreate = state.iscreate else iscreate = altpick.creation endif ~if this is an heroic setting and this is an edge at creation, we're valid if (hero.tagis[source.Heroism] + iscreate >= 2) then @valid = 1 done endif endif ~mark the panel as invalid altthing.linkvalid = 0 ~synthesize an appropriate validation error message call RankName @message = ranktext & " rank required." ]]></validate>
Original Rulebook Options
The previous two sections outlined options that were described in the original rulebook for the game. As such, if a user sees them in the list and doesn't know about the original rulebook, he'll wonder where these options come from. So it's best if we add a new source that identifies itself as containing options from the original rulebook. We can then change the sources above to be children of this new source. The new source should look something like the one shown below.
<source id="Original" name="Original Rulebook Options" selectable="no" reportable="no" description="Assorted options that were outlined in the original core rulebook"> </source>
Once the new source is added, we can modify the "CampType" source to specify our new source as its parent, as shown below.
<source id="CampType" name="Campaign Settings" parent="Original" selectable="no" description="Options associated with the style of campaign setting being played"> </source>
Things from Original Rulebook
Throughout this walk-through, we've included a number of objects that were pulled from the original rulebook and don't exist in the Explorers Edition. For example, all of the races we added (other than human) and the various complex military vehicles. We need to add appropriate sources for these objects and then associate the corresponding "thing" elements to those sources.
We'll first define a source for races. This source will govern the visibility of the various races from the core rulebook, and it should look like the following.
<source id="OrigRace" name="Show Races" parent="Original" description="Enables selection of races from the original core rulebook"> </source>
We can now associate each of the races other than "Human" to this source. All we need to do is add the "usesource" reference shown below to each thing. Note that we do not need to assign the source dependency to the racial abilities. Since those abilities are only accessible via the race, they are implicitly omitted along with the race.
<usesource source="OrigRace"/>
We'll now do the same thing for vehicles. We'll define a source that controls their availability and then associate each vehicle with the source. The new source is shown below.
<source id="OrigVeh" name="Show Vehicles" parent="Original" description="Enables selection of vehicles from the original core rulebook"> </source>
We can now associate the source with each vehicle via the "usesource" element. Unfortunately, this doesn't seem to work. The problem is that the vehicles have multiple sources - both the new source and a time period. The way the source mechanism works is that only one of the defined sources must be selected for the source to be active. Some of the major game systems have rules that are duplicated in multiple supplements, so the rules should be enabled if any of those supplements is enabled.
In our case, we want to require that both sources be selected for the vehicle to be shown. To accomplish that, we need to leverage a ContainerReq test instead. This test simply checks for both sources within the tag expression, as shown below. We should delete the "usesource" elements when we add the ContainerReq test to avoid any confusion.
<containerreq phase="Initialize" priority="2000"><![CDATA[ source.OrigVeh & source.TimeModern ]]></containerreq>
Character Sheet Output
One of our original objectives with the character sheet was to fit everything on a single page. All of the core character information achieves this now, unless the character is quite complex. The problem is that we've added the ally and vehicle output only to the second page of the character sheet, which forces the printing of a second page if either are present. It's quite possible that a user will want to print allies as entirely separate characters and will only use vehicles as a means of transportation, in which case a single-page character sheet is desired and neither will be wanted on the second page.
We accommodate this very easily through the use of a couple of new user-configurable options. We'll keep the printing of allies and vehicles separate, just in case a user wants to include one but not the other. So we'll need to define two new sources. There is already an "Output Options" source grouping with one source beneath it. All we need to do is add our new sources and designate the appropriate parent. Since we want users to see the extra output instead of assuming it doesn't exist, we also want to set the default state of both sources to selected. This yields the two new sources below.
<source id="ShowAlly" name="Include Ally Summaries on Sheet" parent="Output" default="yes" description="If the hero has any allies, separate summaries for each ally will be included on secondary pages of the character sheet"> </source> <source id="ShowVeh" name="Include Vehicles Summaries on Sheet" parent="Output" default="yes" description="If the hero has any vehicles, separate summaries for each vehicle will be included on secondary pages of the character sheet"> </source>
If we select these options for a new character, we'll notice something that needs to be tweaked. On the "Configure Hero" form, a summary of the selected options is shown. Each of our new options runs off the right edge of the available space. We can either shorten the overall name or we can make use of the "abbrev" attribute. This attribute allows us to specify a shorter name for a source that will be shown in places like the summary list. We can add the following two lines to the source definitions, which will give us much better results.
abbrev="Show Ally Summaries" abbrev="Show Vehicle Summaries"
With the sources defined, we can now put them to use within the second page of the character sheet. Open the file "sheet_standard2.dat" and locate the layout. We need to add special handling for both the "oAlly" and "oVehicle" table portals. If the source tag is defined, we'll "autoplace" the portals normally. However, if the source tag is not defined, we'll set the portal to be non-visible. That's all there is to it. This results in a revised Position script that looks like the one below.
~position the various tables in the desired sequence perform portal[oPower].autoplace perform portal[oArmor].autoplace perform portal[oWeapon].autoplace perform portal[oGear].autoplace ~only include allies if the user has enabled them if (hero.tagis[source.ShowAlly] = 0) then portal[oAlly].visible = 0 else perform portal[oAlly].autoplace endif ~only include vehicles if the user has enabled them if (hero.tagis[source.ShowVeh] = 0) then portal[oVehicle].visible = 0 else perform portal[oVehicle].autoplace endif ~the height of the layout is the bottommost extent of the elements within height = autotop
Sequencing the Sources
Our list of sources has become rather long. Since the list is sorted alphabetically, that puts the "Original Rulebook" source at the top of the list, but that group of options is the least likely to be used. Our list of time periods has a similar problem, in that the sequence is not chronological, which is confusing. We should dictate the order in which the various sources are shown. Fortunately, the Kit provides the ability to do this.
Each source has an optional "sortorder" attribute. If this attribute is specified, all of the sources within a given grouping (i.e. sharing the same parent) are first sorted on the assigned order, from lowest to highest. If the order value is the same, the sources are sorted alphabetically. Any source with no assigned order goes after all sources that do have an order.
We're only going to worry about the sources that need sequencing, since most of them can be left to their default behavior. For the various time period sources, we can simply assign the values one through four, as appropriate. However, for the parent sources, we should number them with values that leave gaps. That way, we can easily insert new groupings between existing groupings if we want. We'll use the values listed below.
- Time Period: 20
- Output Options: 50
- Original Rulebook: 100
Once we assign the appropriate "sortorder" attributes to the various sources, we can reload and see the list in a much more intuitive order.