Initiative (Savage)

From HLKitWiki
Revision as of 22:51, 26 January 2009 by Rob (Talk | contribs) (Forcing a Re-Shuffle)

Jump to: navigation, search

Context: HL KitAuthoring Examples … Savage Worlds Walk-Through 

Overview

As mentioned in the previous section on the Tactical Console, initiative is usually pretty simple to implement. However, Savage Worlds uses a deck of playing cards for initiative. That requires a bit more work, so this topic works through the various steps involved in setting up such an initiative mechanism within HL.

Orientation

Before we launch into the changes that need to be made, we'll start with a quick orientation to how initiative is managed within the Kit.

Every game system possesses some sort of mechanic for determining the order in which characters act during conflicts. Consequently, the Kit provides a built-in framework for handling the process. The most common term used for the game mechanic is "initiative", so the Kit uses the same terminology.

The specifics of initiative range from the simple to the complex. To handle the full spectrum of games, the initiative framework within the Kit provide a fair amount of sophistication. Game systems that don't require all that sophistication can simply ignore what they don't need.

Internally, every character possesses an assortment of fields that are specific to initiative management. However, only two of these fields are exposed to you, as the author, for control. These fields track the primary initiative score for the character and a secondary tie-breaker score. As its name suggests, the tie-breaker is used to resolve situations where two characters possess the same primary initiative score. In many game systems, it's usual for ties to arise, so a mechanic is introduce to decide which character goes first.

The HL engine maintains the initiative for all characters. It automatically sorts characters into appropriate order based on the established rules for the game system. It also will automatically assign initiative values to characters, including the use of random die rolls when the game system calls for it. Provisions are made to allow users to either accept the assigned values or assign their own values. All of this is orchestrated via the Tactical Console, which is described in the User Manual for the product.

The data files are responsible for defining how the initiative score is determined for each character, as well as the tie-breaker. This is achieved via the Initiative script, which is defined within the file "definition.def". The script is invoked whenever an initiative score is needed for a character.

Two additional scripts can be defined for a game system. The "NewCombat" script is invoked for each character at the start of each new combat, while the "NewTurn" script is invoked for each character at the start of each combat turn. Both of these scripts provide the author with the opportunity to control state information for the characters. For example, many game systems have the concept of "holding" an action (although it may be called "delaying", "waiting", or something else). This state must be reset either at the start of a new combat or at the start of a new turn, and the two scripts make it easy to accomplish this.

You can dictate various characteristics of initiative for the game system. This is done via a number of attributes on the "behavior" element within the definition file. For example, you can control whether the game system generates new initiative values at the start of each turn or once at the start of the combat.

Lastly, there is a fourth script that can be defined for initiative. The vast majority of game systems use a simple numeric value to indicate the initiative score. Sometimes the score is generated randomly via a die roll, while other times it is calculated, but it is usually a number. In the few cases where the initiative score is not a number, you can define an "InitFinalize" script. This script behaves as a standard Finalize script that is applied to the initiative score for the character. This makes it possible to display the initiative to the user in whatever fashion is most appropriate for the game system.

Modeling a Deck of Cards

Savage Worlds used a deck of cards for initiative. This presents a number of wrinkles that we need to handle, the first of which is how to even model a deck of cards. There are 52 cards in a standard deck, plus the two jokers, for a total of 54 cards. This means that we can use a value in the range of 1 through 54 to represent the entire deck, with each value corresponding to a single card.

But we can't just use a random number generator, since all the cards in the deck must be unique and random numbers can be duplicated. We also need to effectively "shuffle" the deck to get all 54 values in a random order. Fortunately, the Kit provides a mechanism to generate a random set of values within a fixed range. This mechanism can be used for a variety of different purposes, but it's perfect for what we need.

Within the "state" script context, there are a number of target references that specifically pertain to managing random sets of values. The "setrandom" target reference allows us to create a new random set of values, giving it a name and the number of values to hold. We can use this to create a "deck" with 54 values in it, ranging from 0 to 53. The "setextract" target reference pulls a value (card) from the set (deck).

In the NewCombat script, we can create a newly shuffled deck of cards. In the Initiative script for each character, we can extract a card from the deck as our initiative value. The only consideration we have to worry about is that the NewCombat script is invoked for every character and we only want to shuffle a new deck once. This can be handled by keying on the "@isfirst" special symbol, which indicated when the first character is being processed.

Within the Initiative script, there are two special symbols that we need to assign. The "@initiative" special symbol represents that actual initiative value, so we extract a value from the set for that purpose. The "@tiebreaker" special symbol is used by HL to differentiate when two characters have the same initiative. Since a deck of cards is used, all values are guaranteed unique, so there is no tie-breaker in Savage Worlds. Consequently, we can set this symbol to zero.

Putting this together yields an initial two scripts as shown below.

<newcombat><![CDATA[
~if this is the first actor, shuffle a new deck for initiative
if (@isfirst <> 0) then
  perform state.setrandom[deck,54]
  endif

~reset the abandon state in case it's still set from the previous combat
herofield[acAbandon].value = 0
]]></newcombat>

<initiative><![CDATA[
  ~generate the primary initiative rating by drawing a card from the deck
  @initiative = state.setextract[deck]

  ~we have no tiebreaker initiative rating since the cards are always unique
  @tiebreaker = 0
  ]]></initiative>

After putting the above code into place, we can test our data files. Create a portfolio with a number of different characters in it, then show the Tactical Console. Every time we start a new combat, each character is randomly issued a new initiative value in the range of 0 to 53. There are never any duplicates due to the use of the set mechanism.

Configuring the Initiative Behavior

When we did our testing above, there were a few things you probably noticed. First of all, a new initiative is only generated at the start of a new combat. Savage Worlds issues a new initiative at the start of each round, so we need to change that behavior. This is controlled via the "initperturn" attribute within the "behavior" element of the definition file. The default value is "no", so we need to specify a value of "yes".

While we're here, we should also impose an appropriate minimum and maximum value on the initiative range. The user is allowed to adjust the value freely within the TacCon, so negative values are possible and so are values larger than the number of cards in the deck. If we only have a deck of 54 cards to work with, we need to specify a minimum value of zero and maximum of 54. This is done via the "initminimum" and "initmaximum" attributes.

Applying these changes to the "behavior" element results in something that looks like below.

<behavior
  initperturn="yes"
  initminimum="0"
  initmaximum="53">

Forcing a Re-Shuffle

The next thing we need to deal with is when to re-shuffle the deck. We currently shuffle the deck at the start of the combat. However, we pull new cards from the deck every round, so we're bound to run out at some point. We also have to accommodate the rule that the deck is re-shuffled any round after a character draws a joker.

The fact that a joker is drawn can be tracked through a global state variable. Most everything with the HL is tied to a specific actor. However, in situations like this, you can set and retrieve state information that is global across all characters. Within the "state" script context, you can use the "value" target reference for this purpose. By specifying a unique id, you can save and retrieve a named value, which the following code demonstrates.

var temp as number
state.value[joker] = 42
temp = state.value[joker]

Let's put this technique to use. We only need to know whether a joker has been drawn of not, so we'll use a value of 1 to indicate a joker being drawn and a value of zero to indicate no joker. Whenever we re-shuffle the deck, we need to set the state value to zero to indicate no joker. Whenever we draw a card from the deck, we need to set the state value to one. We can accomplish this by revising the NewCombat and Initiative scripts to those shown below.

<newcombat><![CDATA[
  ~if this is the first actor, shuffle a new deck for initiative
  if (@isfirst <> 0) then
    perform state.setrandom[deck,54]
    state.value[joker] = 0
    endif

  ~reset the abandon state in case it's still set from the previous combat
  herofield[acAbandon].value = 0
  ]]></newcombat>

<initiative><![CDATA[
  ~if this actor is holding his action, he does not get a new card
  if (herofield[acAbandon].value <> 0) then
    done
    endif

  ~generate the primary initiative rating by drawing a card from the deck
  @initiative = state.setextract[deck]

  ~if we drew a joker, set the state accordingly
  if (@initiative >= 52) then
    state.value[joker] = 1
    endif

  ~we have no tiebreaker initiative rating since the cards are always unique
  @tiebreaker = 0
  ]]></initiative>

NOTE! Since HL assumes that higher values go first for initiative, and since jokers are the best cards in Savage Worlds, we assume that the jokers are the highest card values (52 and 53).

Now we have to decide how we're going to handle things every round. The NewTurn script is invoked for every actor, but we only want to trigger a re-shuffle once. Fortunately, the NewTurn script has the same "@isfirst" special symbol that the NewCombat script possesses. This means we can check for whether to re-shuffle the deck within the NewTurn script, and we only do it for the first actor.

We need to trigger a re-shuffle whenever the "joker" state value is non-zero. However, we also need to re-shuffle in another situation. If don't have enough cards to pass out to all the actors, we need to re-shuffle the deck to ensure we do have enough cards. We can check this condition first and simply set the "joker" state to one if we don't have enough cards. When we then check the "joker" state, we'll then perform the shuffle if either condition occurred.

The NewTurn script shown below shows an implementation of this logic.

<newturn><![CDATA[
  ~if this is the very first actor for the turn, we've got some work to do
  if (@isfirst <> 0) then

    ~if we don't have enough cards left for all actors, force a re-shuffle
    if (state.actorcount > state.setremain[deck]) then
      state.value[joker] = 1
      endif

    ~if a joker has been pulled, re-shuffle the deck for initiative
    if (state.value[joker] <> 0) then
      perform state.setrandom[deck,54]
      state.value[joker] = 0
      endif
    endif
  ]]></newturn>

We can now reload the data files and put our changes to the test. If you want to make absolutely sure that the logic is performing the way you want it, you can insert a few "debug" statements into the scripts and watch the debug output while you start new combats and new turns.

Omitting Held Cards from Re-Shuffle

Eliminate Initiative Adjustment

Displaying Cards

Revising the Display