User-Configurable Options (Savage)

From HLKitWiki
Jump to navigationJump to search

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>

Filtering Equipment

The various lists of equipment (gear, weapons, etc.) are quite long. They also cover a wide range of time periods. If a user is creating a character for a game set in the 1700s, it's probably going to be annoying to wade through all the options for computers, surveillance gear, etc. What we need is a way to break the equipment up into a few smaller groupings, and sources provide exactly the solution we need.

The rulebook defines four general classifications for time period. These are: medieval, black powder, modern, and futuristic. We'll define new sources corresponding to each of these periods, plus an additional source to use as their parent. This results in the following new definitions.

<source
  id="TimePeriod"
  name="Time Period"
  selectable="no"
  description="Time periods and Savage Settings that govern the availability of equipment">
  </source>

<source
  id="TimeMedi"
  name="Medieval"
  parent="TimePeriod"
  default="yes"
  description="Enables the availability of equipment from the Medieval time period">
  </source>

<source
  id="TimePowder"
  name="Black Powder"
  parent="TimePeriod"
  default="yes"
  description="Enables the availability of equipment from the Black Powder time period">
  </source>

<source
  id="TimeModern"
  name="Modern"
  parent="TimePeriod"
  default="yes"
  description="Enables the availability of equipment from the Modern time period">
  </source>

<source
  id="TimeFuture"
  name="Futuristic"
  parent="TimePeriod"
  default="yes"
  description="Enables the availability of equipment from the Futuristic time period">
  </source>

Once all the sources are defined, we can now make each piece of equipment dependent on the appropriate sources. This is accomplished via the "usesource" element. For example, associating a piece of equipment with the "Modern" time period entails adding the following reference to the thing definition.

<usesource source="TimeModern"/>

Any equipment that should exist within all time periods does not require any "usesource" assignment. If a thing has no source restrictions, then it always appears everywhere. We only need to assign the source restriction to equipment that is only available in a subset of the time periods (e.g. computers only exist in modern and future periods). It is perfectly valid to assign multiple time periods to a particular piece of equipment. If any of the specified sources is selected by the user, the equipment will appear.

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.