User-Configurable Options (Savage)

From HLKitWiki
Revision as of 00:41, 30 January 2009 by Rob (Talk | contribs) (Character Sheet Output)

Jump to: navigation, search

Context: HL KitAuthoring 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="Settings"
  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="Settings"
  description="Heroes may select one additional Minor Hindrance, earning one extra Hindrance point">
  </source>

<source
  id="Tragic"
  name="Tragic Heroes"
  parent="Settings"
  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 "Settings" source we defined previously, resulting in the two new sources shown below.

<source
  id="Heroism"
  name="Heroism"
  parent="Settings"
  description="Heroes ignore the Rank requirements for Edges during character creation">
  </source>

<source
  id="EpicHero"
  name="Epic Heroism"
  parent="Settings"
  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>

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