Specialized Edges (Savage)

From HLKitWiki
Revision as of 02:03, 2 February 2009 by Rob (Talk | contribs) (The "Scholar" Edge)

Jump to: navigation, search

Context: HL KitAuthoring Examples … Savage Worlds Walk-Through 

Overview

Early in the development of our data files, we determined that we would need to do some special handling for a few of the edges. For example, the "Professional" edge requires the user to select a trait that with a rating of d12. Similarly, the "Scholar" edge requires the user to select two skills with a rating of d8 or higher. We need to allow the user to select the appropriate choices via menus, plus we need to properly validate that everything has been done correctly.

Built-In Support

Situations where the player must select a trait or two to associate with an ability are reasonably common across game systems. There are also many situations where a pick's state needs to be toggled or choices need to be selected from a dynamic list. Consequently, the Skeleton files provide a substantial amount of built-in support to handle these cases. This support takes the form of a component to handle the internal mechanics and a template for displaying picks with menu selections.

The "UserSelect" component provides support for all the mechanics. In general, this component can be used to handle the vast majority of the customizations that you'll need. The "UserSelect" component supports a variety of behaviors, including a toggle state that can be selected by the user (generally via a checkbox), a menu of choices that are driven dyanmically at run-time, and up to two different menus for selecting other things or picks within the data files. Between these four mechanisms, you should be able to use the "UserSelect" component almost all the time.

The Skeleton files also provide a "UserSelect" template within the file "visual.dat". This template is similar to the "SimpleItem" template, except that it is designed to orchestrate the display and handling of picks derived from the "UserSelect" component. Fields within the component are defined to tailor how the visuals behave, while the template interprets these fields to appropriately display information and allow the user to modify it.

You will almost certainly find situations where the "UserSelect" component will come in handy. When integrating the visual behaviors, you can either use the corresponding template or adapt aspects of it for use within your own templates. Either approach is perfectly reasonable and up to you as the author.

Integrating User-Selection

As mentioned above, the "UserSelect" component encapsulates most of the behaviors you'll need. We'll now review how those various behaviors work and how to tailor them appropriately. There are a total of three different behaviors.

The first behavior is a checkbox, which we've already used on a few occasions (e.g. equipping weapons and armor). The component provides two fields for managing the checkbox state. The "usrIsCheck" field tracks whether the checkbox is actually checked, and the "usrChkText" field dictates the text to be displayed with the checkbox. If this field is empty, then pick is assumed to not utilize the checkbox and it is not shown.

The second behavior is a thing-based menu, which populates the menu with a list of things or picks that can be chosen. The specific list of items shown is dictated by a tag expression that must be placed into the "usrCandid1" field. If this field is empty, it indicates that no thing-based menu is to be shown. You can control whether list to choose from consists of things, picks that have been added to the hero, or picks that have been added to the current container. This is accomplished by specifying one of the tags from the "ChooseSrc1" tag group, with no tag resulting the "hero" behavior.

Once the user selects an item, it is stored in the field "usrChosen1". If the user does not select an item, a validation error is automatically triggered. If you want to display a label next to the menu, you can specify the text to be shown in the "usrLabel1" field.

It is also possible to specify two separate thing-based menus at the same time. Two sets of the various fields are provided. In most cases, only one menu will be needed, but there will be times that two are required. When that occurs, you can use the second set of fields. They work exactly as the first set, although the first set of fields must be specified before you use the second set. This means that the field "usrCandid1" must be defined if you want to also use "usrCandid2".

The third behavior is an array-based menu, which pulls the menu choices from an array of strings. The array can contain any strings that are appropriate to the game system, and you can even populate the array via scripts at run-time, affording you with complete control over the options presented to the user. The array of options must be specified within the "usrArray" field, and the selected item is stored in the "usrSelect" field. If you want to display a label next to the menu, you can specify it within the "usrLabelAr" field. This behavior is only enabled when the 0th element of the "usrArray" field is non-empty.

If any of these behaviors are utilized, the selected choices are automatically integrated into the name of the pick. If you don't want the name to be changed, or if you want to synthesize your own name, you can assign the "User.NoAutoName" tag to the thing. This disables the automatic name synthesis for all picks derived from that thing.

NOTE! It is invalid to utilize more than one of the three behaviors on the same pick. It is perfectly reasonable to have one pick use a checkbox, another use a thing-based menu, a third use two thing-based menus, and a fourth use an array-based menu. However, you cannot have the same pick use a combination of behaviors, so a pick using a checkbox and a thing-based menu is not supported. If you attempt this, the results are undefined, so we recommend you not bother trying it.

Revising Our Code

We need to utilize the thing-based menus for our specialized edges. To do that, our first task is to add the "UserSelect" component to the "Edge" component set. By doing that, we'll automatically add all the mechanisms for managing user-selection behaviors into every edge we define. The revised component set looks like the following.

<compset
  id="Edge">
  <compref component="Edge"/>
  <compref component="MinRank"/>
  <compref component="Ability"/>
  <compref component="UserSelect"/>
  <compref component="SpecialTab"/>
  <compref component="CanAdvance"/>
  </compset>

The next thing we need to do is add support for properly displaying our edges. We could easily utilize the built-in "UserSelect" template by swapping it into the "edEdges" table portal as the "showtemplate" in place of the "edEdge" template. However, the default display behaviors aren't exactly what we need. The defaults would work fine for the "Professional" edge, since we simply want to show a menu to select an attribute or skill. The same would also work for the similar "Expert" and "Master" edges.

The problem arises with the "Scholar" edge. The various "Knowledge" skills will often have very long names once we append the domain. A limitation of thing-based menus is that we can only show the full name of the picks within the menu. This means that we need to show two separate menus, and each needs to contain a long name (e.g. "Knowledge: Area Knowledge, Battle"). The "UserSelect" template uses normal sized menus, which use a rather large font, so it's going to be very tight trying to show two long skill names in two separate menus. Instead of having the skill names cut off, we'll switch to using a smaller menu style. This will give us plenty of space to show everything, but it requires that we not use the "UserSelect" template and implement the menus ourselves.

We don't need to display any labels next to our menus, so all we need is two menu portals. We can copy the two thing-based menu portals from the "UserSelect" template into the "edEdge" template. Once we do that, we can change the style for the portals to "menuSmall" so that we get small menus. This gives us the following two new portals.

<portal
  id="menu1"
  style="menuSmall">
  <menu_things
    field="usrChosen1"
    component="none"
    maxvisible="10"
    usepicksfield="usrSource1"
    candidatefield="usrCandid1">
    </menu_things>
  </portal>

<portal
  id="menu2"
  style="menuSmall">
  <menu_things
    field="usrChosen2"
    component="none"
    maxvisible="10"
    usepicksfield="usrSource2"
    candidatefield="usrCandid2">
    </menu_things>
  </portal>

Since the names of our picks are going to be modified to incorporate the selected options, we face the same problem we had with domains for skills. Using the "name" field for showing the name of a pick will show the modified name. This means we'll be showing the modified name and showing the menus with the same contents. That's silly, so we need to only show the base name for each pick. We accomplish that by changing the "name" portal to reference the "thingname" field.

We can now look at the contents of the Position script for the "UserSelect" template. We're going to need to determine whether our menus are visible, so we can steal that block of code. We're also going to want to highlight the menus in red if nothing is selected within them, so we can steal that code as well. We'll need to center the menus vertically, but that's nothing special. The general logic for positioning everything in the "UserSelect" template is much too complicated for our needs - we only have two menu portals - so we'll figure out how to integrate them ourselves.

When we determine the width of the "name" portal, we need to reserve some space for the menus, just in case the name is extremely long. If the name is shorter than the reserved space, we'll happily use whatever space is available. Once the name is sized properly, we can easily insert one menu into the space or split the space between two menus. Putting all this together yields the revised Position script below.

~set up our height based on our tallest portal
height = portal[info].height

~if this is a "sizing" calculation, we're done
if (issizing <> 0) then
  done
  endif

~determine whether our menus are visible
~Note: Remember that a non-empty tagexpr field indicates menu selection is used.
if (field[usrCandid1].isempty <> 0) then
  portal[menu1].visible = 0
  endif
if (field[usrCandid2].isempty <> 0) then
  portal[menu2].visible = 0
  endif

~position our tallest portal at the top
portal[info].top = 0

~position the other portals vertically
perform portal[name].centervert
perform portal[delete].centervert
perform portal[menu1].centervert
perform portal[menu2].centervert

~position the delete portal on the far right
perform portal[delete].alignedge[right,0]

~position the info portal to the left of the delete button
perform portal[info].alignrel[rtol,delete,-8]

~position the name on the left and use availble space, with a gap for menus
portal[name].left = 0
portal[name].width = minimum(portal[name].width,portal[info].left - portal[name].left - 150)

~position the menus to the right of the name in the available space
perform portal[menu1].alignrel[ltor,name,10]
portal[menu1].width = (portal[info].left - portal[menu1].left - 20) / 2
portal[menu2].width = portal[menu1].width
perform portal[menu2].alignrel[ltor,menu1,10]

~if a menu is visible, make sure it has a selection
if (portal[menu1].visible <> 0) then
  if (field[usrChosen1].ischosen = 0) then
    perform portal[menu1].setstyle[menuErrSm]
    endif
  endif
if (portal[menu2].visible <> 0) then
  if (field[usrChosen2].ischosen = 0) then
    perform portal[menu2].setstyle[menuErrSm]
    endif
  endif

The "Scholar" Edge

It's time for us to actually define one of our special edges. We'll start with the "Scholar" edge.

Hooking Up Fields

Revising the Pre-Requisites

Assigning Bonuses to Selected Skills

Custom Name

Safeguarding Against Duplicate Skills

The "Professional" Edge

The "Expert" and "Master" Edges