Dashboard (Savage): Difference between revisions

From HLKitWiki
Jump to navigationJump to search
Line 179: Line 179:
~if we have any arcane powers, separate them from the special abilities above
~if we have any arcane powers, separate them from the special abilities above
if (hero.haschild["component.Power"] <> 0) then
if (hero.haschild["component.Power"] <> 0) then
   final &= "{br}{horz 10}{b}{u}Arcane Powers{/u}"
   final &= "{br}{horz 10}{b}{u}Arcane Powers and/or Gizmos{/u}"
   final &= "  (" & herofield[acPPSumm].text & "){/b}{br}{vert 5}"
   final &= "  (" & herofield[acPPSumm].text & "){/b}{br}{vert 5}"
   endif
   endif

Revision as of 04:09, 26 January 2009

Context: HL Kit &#133; Authoring Examples &#133; Savage Worlds Walk-Through 

Overview

All of the critical mechanics for individual characters are now in place. It's time to turn our attention to managing multiple characters effectively. The first piece in that process is the Dashboard.

Determine the Display Content

The Skeleton files provide a basic framework for the Dashboard. The character name is shown at the top. Various mouse-over icons are arrayed across the bottom. Buttons to switching between actors and move gear are down the right side. We need to figure out what additional information we want to show to the user. We don't have much space, so we need to choose carefully. The most frequently referenced information should be given priority and shown.

Looking closely at the make-up of Savage Worlds characters, there are five basic pieces of information that seem most important. These are listed below.

  • Health Status - The number of wounds and fatigue level are important, as is whether the character is shaken.
  • Parry - In a game with lots of combat, the Parry trait will be referenced frequently.
  • Toughness - The rationale for Toughness is similar to the Parry trait.
  • Power Points - It's also valuable to know how many power points remain for a character with an arcane background.
  • Bennies - Bennies are an important resource that need to be managed by the player, so knowing how many you have left at-a-glance can be extremely useful.

It's possible that we won't be able to squeeze all of this information into the limited space we have for each actor on the Dashboard. So we need to assess each one and determine both how to display it and where it might fit. At the end, if don't have room, we need to drop the lower priority piece(s) of information.

Health is probably the most important piece, although it also requires significant space. If a character is wounded, fatigued, and shaken, all three facets must be indicated in some way. We already use a very compact representation for this within the static form at the top of the main window, but it still consumes a fair bit of space. If we can't think of a more compact representation, we'll be hard-pressed to show all the other information on the Dashboard. The primary use for the Health as a quick-reference during play is to show the net impact on the character. As such, we could display a shaken indicator and only the total negative adjustment for the character. This would cut the required space in half.

Since combat tends to factor prominently in many Savage Worlds games, the Parry and Toughness traits are probably the next most important. These traits are simple values, so they don't require much space to display on the Dashboard. The Power Points are likely more important than the Bennies. Showing the Power Points can be done in two different ways. One option is to show them as we do on the static form at the top, with both the current and maximum values, but we could also just show the current value for space efficiency. The same two options apply for Bennies.

Let's consider how and where we can squeeze everything in now. The default behavior for the Dashboard is to reserve two lines of space for the actor name. The primary reason for this is to be able to show the associations for minions and masters. Since allies often play a significant role in Savage Worlds games, and since they can best be modeled as minions of the PCs, it would be nice to retain this space on the Dashboard.

If we retain that space, though, we're left with some hard decisions. Beneath the name, there really isn't enough space to show more than one line of information. Based on our prioritization above, this leaves us with the character's Health (in a compressed format) and room for probably only two traits (Parry and Toughness). We'll go with this design for now and see if there's a way we can squeeze in the Power Points at the end.

New Health Format

In the previous section, we decided on a more compact format for showing Health on the Dashboard. Now we need to synthesize that format appropriately. The current format is generated through a field on the "Actor" component. Probably the best method for us to generate the new format is via an alternate field on the same component.

We can start by cloning the existing "acDmgSumm" field and giving it a new id. We'll use "acDmgTac", since we'll be using this format on both the Tactical Console and Dashboard. Then we can revise the Finalize script to correctly synthesize the new format. The resulting new field is shown below.

<field
  id="acDmgTac"
  name="Health for Dashboard/TacCon"
  type="derived"
  maxfinal="100">
  <!-- Calculate a value that is based on all the fields referenced by the
        "finalize" script. This ensures that the field always changes if any of
        its pieces changes, which triggers the finalized value to be updated.
  -->
  <calculate phase="Render" priority="1000"><![CDATA[
    ~make sure this value consists of the elements that could cause the summary to change
    @value = field[acShaken].value * 10000 + field[acWounds].value * 100 + field[acFatigue].value
    ]]></calculate>
  <!-- Final value for display shows the shaken state, current wounds, and any
        penalty value.
  -->
  <finalize><![CDATA[
    ~if we're not shaken and have incurred no negative effects, all is good
    var net as number
    net = field[acWounds].value + field[acFatigue].value
    if (field[acShaken].value + net = 0) then
      @text = "-"
      done
      endif

    ~if we're shaken, signal it with a special indicator
    @text = ""
    if (field[acShaken].value <> 0) then
      @text &= "{font wingdings}v{revert}"
      endif

    ~get the current wounds and fatigue for use below
    var wounds as number
    var fatigue as number
    wounds = field[acWounds].value
    fatigue = field[acFatigue].value

    ~if we're incapacitated, report it
    var state as string
    if (wounds >= 4) then
        state = "Inc"
    elseif (fatigue >= 3) then
        state = "Inc"

    ~otherwise, report our total negative influence
    elseif (net > 0) then
      state = "-" & net
      endif

    ~if we have a state to report, append it
    if (empty(state) = 0) then
      if (empty(@text) = 0) then
        @text &= "/"
        endif
      @text &= state
      endif

    ~anything we output must be in red to highlight the penalty
    @text = "{text ff0000}" & @text
    ]]></finalize>
  </field>

Implement the Display

It's now time to implement the changes to the Dashboard. Open the file "form_dashboard.dat" and locate the "dashboard" template. The first thing we need to do is integrate our new field for showing the Health. The "health" portal already exists, so all we need to do is change the field it displays to the new "acDmgTac" field. This results in the revised portal below.

<portal
  id="health"
  style="lblSmall">
  <label>
    <labeltext><![CDATA[
      @text = field[acDmgTac].text
      ]]></labeltext>
    </label>
  </portal>

The "power" portal is being eliminated, but we'll want to do something similar for showing the "Parry" and "Toughness" traits. We can adapt the portal for the "Parry" trait, then we can clone it and revise it for the "Toughness" trait. The following two portals should result, along with the effective deletion of the "power" portal.

<portal
  id="parry"
  style="lblSmall">
  <label>
    <labeltext><![CDATA[
      @text = "{size 30}Pa: {size 36}" & #trait[trParry]
      ]]></labeltext>
    </label>
  </portal>

<portal
  id="toughness"
  style="lblSmall">
  <label>
    <labeltext><![CDATA[
      @text = "{size 30}To: {size 36}" & #trait[trTough]
      ]]></labeltext>
    </label>
  </portal>

The portals are now in place, so we need to position everything properly. We'll position the "toughness" portal adjacent to the "gear" portal, then we'll place the "parry" portal on the left of that. The "health" portal will then be influenced by the "parry" portal. We can also introduce a little bit more spacing to make things look good. This results in the following revised script code for positioning the three portals.

~position toughness details next to the "gear" portal
perform portal[toughness].centeron[vert,gear]
perform portal[toughness].alignrel[rtol,gear,-6]

~position parry details next to the "toughness" portal
perform portal[parry].centeron[vert,toughness]
perform portal[parry].alignrel[rtol,toughness,-6]

~position health details parallel to the "parry" portal
perform portal[health].centeron[vert,parry]
portal[health].width = portal[parry].left - 4

The final thing we need to do is handle the vertical adjustment to our new portals if the name requires two lines. The revised script code should look as follows.

~shift the health and related portals down slightly to make more room
portal[health].top += 2
portal[parry].top += 2
portal[toughness].top += 2

If we reload the data files and take a look at the Dashboard, it looks reasonable. Unfortunately, there just isn't any space to squeeze in the Power Points, so we'll just have to leave them out.

Mouse-Over Information

The final task associated with the Dashboard is to generate suitable mouse-over text for each of the five icons across the bottom. The Skeleton files utilize five different procedures for this purpose, and they are already hooked into the appropriate portals. All we need to do is revise the individual procedures to show the information we want.

Special Abilities

Moving from left to right, the first mouse-over icon is intended to show all of the special abilities for the actor. The list presented should generally contain the same items as are shown within the "Special" tab. Looking at the "DshSpecial" procedure, we'll see the that all picks with the "DashTacCon.Special" tag are included in the list that is output. If we look at the "SpecialTab" component, we'll see that every thing derived from the component is assigned that tag. Consequently, everything that appears on the "Special" tab will also be shown by this procedure.

The question we need to ask ourselves is if there is any additional material that we want to include here, or if there is any material that we want to exclude. If there is, then we simply need to make sure to assign or delete the "DashTacCon.Special" tag for those things/picks. Adding the tag will output the new material the same way special abilities are output.

After giving it a bit of thought, there is one group of things we should add - arcane powers. However, we need to display arcane powers differently from the current special abilities. That means that we can't just assign them the tag. Instead, we need to integrate them into the logic of the procedure. The arcane powers should be kept visually separate from the special abilities. For each power, we should so the point cost, range, duration, and maintenance. Within the procedure, we can insert the code below just before the last line of the script.

~if we have any arcane powers, separate them from the special abilities above
if (hero.haschild["component.Power"] <> 0) then
  final &= "{br}{horz 10}{b}{u}Arcane Powers and/or Gizmos{/u}"
  final &= "  (" & herofield[acPPSumm].text & "){/b}{br}{vert 5}"
  endif

~output all arcane powers
foreach pick in hero where "component.Power"
  final &= "{b}" & eachpick.field[name].text & "{/b} - "
  final &= "Pts: {b}" & eachpick.field[powPoints].text & "{/b}"
  final &= "; Rng: {b}" & eachpick.field[powRange].text & "{/b}"
  final &= "; Dur: {b}" & eachpick.field[powLength].text & "{/b}"
  if (eachpick.field[powMaint].isempty = 0) then
    final &= " (" & eachpick.field[powMaint].text & ")"
    endif
  final &= "{br}"
  nexteach

Skills and Other Rolls

Continuing to the right, the next mouse-over icon is intended to display the various rolls that apply for the actor. This includes skills and anything else that is used as a roll during play. We'll start by looking at the "DshRolls" procedure, where we find that it includes all picks with the "DashTacCon.Rolls" tag. This tag is included on every skill by the "Skill" component, but it is not assigned anywhere else.

There really aren't any other rolls that characters need to make, aside from attribute rolls. We could put the attributes here, but they are already included in the fourth mouse-over, as part of the basic character information. So the question is whether we want to move them. It's probably best to leave them where they are, so there are no changes necessary for this mouse-over.

Weapons and Combat

The central mouse-over icon displays all the combat-related details for the actor. Weapons, armor, shields, and combat-centric traits should be included here. The contents are governed by the "DshCombat" procedure, which outputs four groups of information. The first group consists of traits that are assigned the "DashTacCon.Combat" tag, followed by armor, shields, and weapons.

The traits to be included must be individually assigned the "DashTacCon.Combat" tag. So we'll start by assessing which traits should be included. The "Parry" and "Toughness" traits are obviously combat-related. The one questionable trait is "Pace". We'll go ahead and include for now. Once the list is determined, we can go through all traits and make sure that only those in our list possess the appropriate tag.

The next things output are any equipped armor and/or shield. For both, we need to include the defense rating. However, we need to include any parry bonus for shields and the areas covered for armor. We can revise the script code for these objects to reflect these changes, resulting in the following.

~output equipped armor and shield
var temp as string
info = ""
foreach pick in hero where "component.Armor"
  if (eachpick.field[grIsEquip].value <> 0) then
    info &= eachpick.field[name].text & " {b}" & eachpick.field[defDefense].text & "{/b}"
    temp = eachpick.tagabbrevs[ArmorLoc.?,","]
    if (empty(temp) = 0) then
      info &= ", Covers: " & temp
      endif
    info &= "{br}"
    endif
  nexteach
if (empty(info) <> 0) then
  info = "-No Armor Equipped-{br}"
  endif
final &= info
info = ""
foreach pick in hero where "component.Shield"
  if (eachpick.field[grIsEquip].value <> 0) then
    info &= eachpick.field[name].text & " {b}" & eachpick.field[defDefense].text & "{/b}"
    if (eachpick.field[defParry].value <> 0) then
      info &= ", Parry: {b}" & signed(eachpick.field[defParry].value) & "{/b}"
      endif
    info &= "{br}"
    endif
  nexteach
if (empty(info) <> 0) then
  info = "-No Shield Equipped-{br}"
  endif
final &= info & "{br}"

The final block of information included is the weapons. Since the "Armory" sort set is utilized, equipped weapons will be shown first, whether they are melee weapons or ranged weapons. After that, ranged weapons are listed, followed by melee weapons. Within the code, we need to clean up how each weapon is displayed, as well as add details that are currently omitted (e.g. armor piercing, ranges, etc.). The revised code should look like below.

~output all weapons, with equipped ones first
info = ""
foreach pick in hero where "component.WeaponBase" sortas Armory
  info &= eachpick.field[name].text & " {b}" & eachpick.field[wpNetAtk].text & "{/b}"
  info &= ", Dmg: {b}" & eachpick.field[wpDamage].text & "{/b}"
  if (eachpick.field[wpPiercing].value <> 0) then
    info &= ", AP" & eachpick.field[wpPiercing].text
    endif
  if (eachpick.tagis[component.WeapRange] <> 0) then
    info &= ", Rng: " & eachpick.field[wpRange].text
    endif
  if (eachpick.field[wpNotes].isempty = 0) then
    info &= ", Special"
    endif
  info &= "{br}"
  nexteach
if (empty(info) <> 0) then
  info = "-No Weapons-{br}"
  endif
final &= info

Basic Information

The mouse-over for basic character information shows facets of the character that aren't included elsewhere. Details like race, attributes, and other derived traits belong here. The contents are controlled via the "DshBasics" procedure, so we'll look there first.

The race makes sense to include, but the power points need to be eliminated - they are already presented in the abilities mouse-over. The next block outputs "resistance" traits that are identified by the tag "User.Resistance". Savage Worlds doesn't have the notion of special resistance traits (e.g. saving throws), so we're not using any such picks. However, we do have a few traits that we need to show somewhere (e.g. Charisma). What we can do is define a new tag to identify the traits we want to included within the "Basics" mouse-over. We'll put it in the "DashTacCon" tag group and give it the id "Basics".

Once the tag is defined, we can assign it to the appropriate traits we want to include. The only trait that is not included anywhere is "Charisma", so we definitely need to assign it the tag. We should also probably assign the tag to the "Pace" trait. This trait is used outside of combat as well, and it really doesn't hurt to repeat it in both places, so we assign it the tag. Then we can revise the code to key on our new tag, as shown below.

~output our non-combat traits
foreach pick in hero where "DashTacCon.Basics"
  final &= eachpick.field[name].text & ": {b}" & eachpick.field[trtDisplay].text & "{/b}{br}"
  nexteach
final &= "{br}"

Attributes are shown next, which we definitely want to retain. Lastly, permanent adjustments are output, which we also want to retain. So our only changes to this mouse-over are the ones outlined above.

Active Effects

The final mouse-over icon shows any temporary effects that are active on the character. This includes activated abilities and in-play adjustments, both of which are controlled via the "In-Play" tab. The contents are synthesized by the "DshActive" procedure, which requires no changes for our needs.