Adding Advancements (Savage): Difference between revisions

From HLKitWiki
Jump to navigationJump to search
No edit summary
No edit summary
 
(2 intermediate revisions by the same user not shown)
Line 96: Line 96:
===Handling Skill Increases===
===Handling Skill Increases===


As mentioned above, we need two separate mechanisms for adding skill increases. One advance is used for increases skills that are equal to or greater than the linked attribute, while the other is for skills that are less than the attribute. This allows us to apply a cost of one full advance for one type of increase and only a cost of a half-advance for the other type, which enables users to choose two of the latter advance for the price of any other advance. We've already instrumented each skill to possess a "Helper.LessThan" tag if it is less than the linked attribute value, so all we need to do is test against that tag. The only other details we have to make sure are distinct are the action text, description, and cost in advance slots. In the end, we have two separate advances that differ from each other in only those few ways, as shown below.  
As mentioned above, we need two separate mechanisms for adding skill increases. One advance is used for increasing skills that are equal to or greater than the linked attribute, while the other is for skills that are less than the attribute. This allows us to apply a cost of one full advance for one type of increase and only a cost of a half-advance for the other type, which enables users to choose two of the latter advance for the price of any other advance. We've already instrumented each skill to possess a "Helper.LessThan" tag if it is less than the linked attribute value, so all we need to do is test against that tag. The only other details we have to make sure differ are the action text, description, and cost in terms of advancement slots. In the end, we have two separate advances that differ from each other in only those few ways, as shown below.  


<pre>
<pre>
Line 117: Line 117:
   name="Increase Lesser Skill"
   name="Increase Lesser Skill"
   compset="Advance"
   compset="Advance"
   description="Increase one skill by one die type, provided that the skill is currently less than its linked attribute.\n\n{b}{text ffff00}Note!{text 010101}{/b} Select this option {b}{i}twice{/i}{/b} as a single advance, specifying a separate skill for each. This selection consumes only half an advance slot.">
   description="Increase one skill by one die type, provided that the skill is currently less than its linked attribute.{br}{br}{b}{text ffff00}Note!{text 010101}{/b} Select this option {b}{i}twice{/i}{/b} as a single advance, specifying a separate skill for each. This selection consumes only half an advance slot.">
   <fieldval field="advCost" value=".5"/>
   <fieldval field="advCost" value=".5"/>
   <fieldval field="advAction" value="Boost Lesser Skill"/>
   <fieldval field="advAction" value="Boost Lesser Skill"/>
Line 130: Line 130:
===Attribute Limits===
===Attribute Limits===


All of our advances are now in place and should be working. However, we still have a special requirement regarding advances that we need to properly enforce. Attributes can only be increased once every rank. Solving this entails a rather crude, but effective, approach. We can use a "foreach" loop to iterate through all of the advances and tally the total number of attribute advances within each rank. If any rank has more than one attribute advance within it, we need to report an error to the user so that it can be rectified. We must process the advances in the exact order in which they were added to the character, which means we need to utilize the built-in "_CompSeq_" sort set.  
All of our advances are now in place and should be working. However, we still have a special requirement regarding advances that we need to properly enforce. Attributes can only be increased once every rank. While this is probably a pretty standard rule that most gaming groups will adhere to, we can't make that assumption. So we have to allow users to break the rule, which means we need to utilize validation.
 
Solving this entails a rather crude, but effective, approach. We can use a "foreach" loop to iterate through all of the advances and tally the total number of attribute advances within each rank. If any rank has more than one attribute advance within it, we can report an error to the user so that it can be rectified. We must process the advances in the exact order in which they were added to the character, which means we need to utilize the built-in "_CompSeq_" sort set.  


Unfortunately, there's an interesting wrinkle. Since there are four advances per rank through 80 XP and two advances per rank thereafter, we need to differentiate the two progressions appropriately. This means an attribute advance is allowed only once out of every block of four advances during the first 16 advances, but only once out of every two advances after that. During our loop, we need to handle this distinction properly.
Unfortunately, there's an interesting wrinkle. Since there are four advances per rank through 80 XP and two advances per rank thereafter, we need to differentiate the two progressions appropriately. This means an attribute advance is allowed only once out of every block of four advances during the first 16 advances, but only once out of every two advances after that. During our loop, we need to handle this distinction properly.
Line 136: Line 138:
To enforce this check, we'll create a new thing that is intended solely for validation. That means we need to add it to the file "thing_validate.dat". Since the thing is performing checks on the overall hero, it can derive from the "Simple" component set. And since the thing needs to perform its checks on all actors, we assign it the "Helper.Bootstrap" tag to ensure it gets automatically applied to all heroes.  
To enforce this check, we'll create a new thing that is intended solely for validation. That means we need to add it to the file "thing_validate.dat". Since the thing is performing checks on the overall hero, it can derive from the "Simple" component set. And since the thing needs to perform its checks on all actors, we assign it the "Helper.Bootstrap" tag to ensure it gets automatically applied to all heroes.  


Most validation things possess only an EvalRule script, since they merely need to perform the validation test, and we could implement this thing the same way. However, we can make things a little simpler and more efficient by performing the validation in two stages. Each EvalRule script assumes the test is failed, so all the checks must be performed to ascertain whether test is satisfied, but it's much easier for us to simply assume success and bail out if we ever determine that the requirements aren't satisfied. Consequently, the first stage is an Eval script that determines whether all the requirements are satisfied and assigns a tag to the hero if anything is failed. The second stage is the EvalRule that merely checks for the tag and reports the validation result accordingly. All we need to do is define a new "Hero.MultiAttr" tag and write the two scripts, which yields something that looks like the following.  
Most validation things possess only an EvalRule script, since they merely need to perform the validation test, and we could implement this thing the same way. However, we can make things a little simpler and more efficient by performing the validation in two stages. Each EvalRule script assumes the test is failed, so all the checks must be performed to ascertain whether each test is satisfied, but it's much easier for us to simply assume success and bail out if we ever determine that the requirements aren't satisfied. Consequently, the first stage is an Eval script that determines whether all the requirements are satisfied and assigns a tag to the hero if anything is failed. The second stage is the EvalRule that merely checks for the tag and reports the validation result accordingly. All we need to do is define a new "Hero.MultiAttr" tag in the file "tags.1st" and write the two scripts, which yields a new thing that looks like the following.  


<pre>
<pre>
Line 144: Line 146:
   compset="Simple">
   compset="Simple">
   <tag group="Helper" tag="Bootstrap"/>  
   <tag group="Helper" tag="Bootstrap"/>  
   <eval value="1" phase="Validate" priority="8000"><![CDATA[
   <eval index="1" phase="Validate" priority="8000"><![CDATA[
     var index as number
     var index as number
     var total as number
     var total as number
Line 150: Line 152:
     ~iterate through all advances in the order they were added to the character
     ~iterate through all advances in the order they were added to the character
     foreach pick in hero where "component.Advance" sortas _CompSeq_  
     foreach pick in hero where "component.Advance" sortas _CompSeq_  
       ~determine whether we need to perform an actual check at this interval
       ~determine whether we need to perform an actual check at this interval
       is_check = 0
       is_check = 0
Line 159: Line 162:
           endif
           endif
         endif  
         endif  
       ~if we're supposed to check, do so, then reset the tally count
       ~if we're supposed to check, do so, then reset the tally count
       ~Note: If we find an error, assign a tag and there's nothing more to do.
       ~Note: If we find an error, assign a tag and there's nothing more to do.
Line 168: Line 172:
         total = 0
         total = 0
         endif  
         endif  
       ~if this is an attribute increase, tally it
       ~if this is an attribute increase, tally it
       if (each.tagis[AdvanceId.advAttrib] <> 0) then
       if (eachpick.tagis[AdvanceId.advAttrib] <> 0) then
         total += 1
         total += 1
         endif  
         endif  
       ~increment our index based on our cost and continue
       ~increment our index based on our cost and continue
       ~Note: Using the cost allows us to properly identify the transitions between
       ~Note: Using the cost allows us to properly identify the transitions between
       ~ experience point levels where we need to perform our check.
       ~ experience point levels where we need to perform our check.
       index += each.field[advCost].value
       index += eachpick.field[advCost].value
       nexteach  
       nexteach  
     ~perform a check on the final set of advances
     ~perform a check on the final set of advances
     if (total > 1) then
     if (total > 1) then
Line 182: Line 189:
       endif
       endif
     ]]></eval>  
     ]]></eval>  
   <evalrule value="1"  
 
    phase="Validate" priority="9000"  
   <evalrule index="1" phase="Validate" priority="9000"  
     message="Multiple attributes increased within a rank" summary="Multiple attributes"><![CDATA[
     message="Multiple attributes increased within a rank" summary="Multiple attributes"><![CDATA[
     ~if we have no instances of multiple advances within a level, we're good
     ~if we have no instances of multiple advances within a level, we're good
Line 190: Line 197:
       done
       done
       endif  
       endif  
     ~mark associated tab as invalid
     ~mark associated tab as invalid
     container.panelvalid[advances] = 0
     container.panelvalid[advances] = 0

Latest revision as of 07:08, 23 December 2008

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

Overview

All of the basic mechanics for advancement are now in place, so it's time to add the individual advancements for Savage Worlds.

Dynamic Tag Expressions

Each advance entails the selection of something slightly different by the user, yet the advancement mechanism uses a single chooser through which the user makes selections. Consequently, each advancement must properly dictate what can be selected, and this is achieved by using a dynamic tag expression within the chooser. Each advance possesses a field that contains the tag expression to be used, and it can be either hard-wired or synthesized on the fly via scripts, as necessary. This field must contain a valid tag expression that is applied against all things/picks to appropriately filter the list to the set of valid choices for the user.

For example, an advance that allows the user to add a new skill needs to specify a tag expression that identifies only the skills that have not yet been added to the character. Similarly, an advance that increases an existing skill needs a tag expression that identifies only those skills. By tailoring the tag expression properly, that same chooser can be re-configured for each advance to allow the user to select a completely distinct set of objects.

Handling Attribute Increases

Increasing an attribute is simple, as it can be readily adapted from the example provided within the Skeleton data files. First, we need to specify suitable action text. Next, we need to assign the "Advance.Increase" tag so that it is handled as an increase of an existing trait. The last step is to specify the appropriate tag expression for identifying valid attributes that can be increased. These consist of any attribute that is not hidden and not already at its maximum. The final result looks like the thing definition below.

<thing
  id="advAttrib"
  name="Increase Attribute"
  compset="Advance"
  description="Increase an attribute by one die type.">
  <fieldval field="advAction" value="Boost Attribute"/>
  <fieldval field="advDynamic" value="component.Attribute & !Helper.Maximum & !Hide.Attribute"/>
  <fieldval field="advCost" value="1"/>
  <tag group="Advance" tag="Increase"/>
  <child entity="Advance">
    </child>
  </thing> 

Handling New Edges

Adding new edges via an advance is a little more complicated. The action text is easy and we assign the "Advance.AddNew" tag so that it is handled as the addition of something new. In addition, we need to require that the user choose something to be added, so we must assign the "Advance.MustChoose" tag to the child gizmo. The final piece is the tag expression, which needs to be synthesized on-the-fly based on the list of edges that have already been added to the character. Specifically, the list of edges that is valid for selection consists of all edges that have not yet been added. Since the identity tags of all edges are forwarded to the hero, we can request a list of those ids from the hero and massage the list so that it can be used as part of our tag expression. Once we append the list of precluded edges to the initial tag expression, we have a result that will allow the user to choose any edge that has not already been added. This yields an advance similar to the one below.

<thing
  id="advEdge"
  name="Gain a New Edge"
  compset="Advance"
  description="Select a new edge of your choice.">
  <fieldval field="advAction" value="New Edge"/>
  <fieldval field="advDynamic" value="component.Edge"/>
  <fieldval field="advCost" value="1"/>
  <tag group="Advance" tag="AddNew"/>
  <eval index="1" phase="Render" priority="1000">
    <before name="Assign Dynamic Tagexpr"/><![CDATA[
    ~get the list of all edges on the hero and assemble it as a list of precluded tags
    var tagexpr as string
    tagexpr = hero.tagids[Edge.?," & !"]
    ~if there are any tags to exclude, append them to the tagexpr appropriately
    if (empty(tagexpr) = 0) then
      field[advDynamic].text &= " & !" & tagexpr
      endif
    ]]></eval>
  <child entity="Advance">
    <tag group="Advance" tag="MustChoose"/>
    </child>
  </thing> 

Adding New Skills

The advance for adding new skills is very similar to the one for adding new edges. The key difference is the script that synthesizes the tag expression that controls which skills can be added by the user. We can't use the same technique as for edges, because there are some skills that can be added multiple times (e.g. "Knowledge"). Instead, we have to iterate through all of the skills that have been added to the hero and determine whether each skill is unique or not. If a skill is unique, it gets added to the list of tags that must be precluded from user selection, while non-unique skills can be added any number of times. The net result should look similar to the advance shown below.

<thing
  id="advSkill"
  name="Gain a New Skill"
  compset="Advance"
  description="Select a new skill at a d4 rating.">
  <fieldval field="advCost" value="1"/>
  <fieldval field="advAction" value="New Skill"/>
  <fieldval field="advDynamic" value="component.Skill & !Hide.Skill"/>
  <tag group="Advance" tag="AddNew"/> 
  <eval value="1" phase="Render" priority="1000">
    <before name="Assign Dynamic Tagexpr"/><![CDATA[
    ~get the list of all unique skills on the hero and assemble it as a list of precluded tags
    var tagexpr as string
    foreach pick in hero where "component.Skill & !Hide.Skill"
      if (each.isunique <> 0) then
        tagexpr &= " & !Skill." & each.idstring
        endif
      nexteach 
    ~if there are any tags to exclude, append them to the tagexpr appropriately
    if (empty(tagexpr) = 0) then
      field[advDynamic].text &= tagexpr
      endif
    ]]></eval> 
  <child entity="Advance">
    <tag group="Advance" tag="MustChoose"/>
    </child>
  </thing> 

Handling Skill Increases

As mentioned above, we need two separate mechanisms for adding skill increases. One advance is used for increasing skills that are equal to or greater than the linked attribute, while the other is for skills that are less than the attribute. This allows us to apply a cost of one full advance for one type of increase and only a cost of a half-advance for the other type, which enables users to choose two of the latter advance for the price of any other advance. We've already instrumented each skill to possess a "Helper.LessThan" tag if it is less than the linked attribute value, so all we need to do is test against that tag. The only other details we have to make sure differ are the action text, description, and cost in terms of advancement slots. In the end, we have two separate advances that differ from each other in only those few ways, as shown below.

<thing
  id="advBoost1"
  name="Increase Greater Skill"
  compset="Advance"
  description="Increase one skill by one die type that the skill is equal to or higher than the linked attribute.">
  <fieldval field="advCost" value="1"/>
  <fieldval field="advAction" value="Boost Greater Skill"/>
  <fieldval field="advDynamic" value="component.Skill & !Helper.Maximum & !Hide.Skill & !Helper.LessThan"/>
  <tag group="Advance" tag="Increase"/> 
  <child entity="Advance">
    <tag group="Advance" tag="MustChoose"/>
    </child>
  </thing> 

<thing
  id="advBoost2"
  name="Increase Lesser Skill"
  compset="Advance"
  description="Increase one skill by one die type, provided that the skill is currently less than its linked attribute.{br}{br}{b}{text ffff00}Note!{text 010101}{/b} Select this option {b}{i}twice{/i}{/b} as a single advance, specifying a separate skill for each. This selection consumes only half an advance slot.">
  <fieldval field="advCost" value=".5"/>
  <fieldval field="advAction" value="Boost Lesser Skill"/>
  <fieldval field="advDynamic" value="component.Skill & !Helper.Maximum & !Hide.Skill & Helper.LessThan"/>
  <tag group="Advance" tag="Increase"/> 
  <child entity="Advance">
    <tag group="Advance" tag="MustChoose"/>
    </child>
  </thing> 

Attribute Limits

All of our advances are now in place and should be working. However, we still have a special requirement regarding advances that we need to properly enforce. Attributes can only be increased once every rank. While this is probably a pretty standard rule that most gaming groups will adhere to, we can't make that assumption. So we have to allow users to break the rule, which means we need to utilize validation.

Solving this entails a rather crude, but effective, approach. We can use a "foreach" loop to iterate through all of the advances and tally the total number of attribute advances within each rank. If any rank has more than one attribute advance within it, we can report an error to the user so that it can be rectified. We must process the advances in the exact order in which they were added to the character, which means we need to utilize the built-in "_CompSeq_" sort set.

Unfortunately, there's an interesting wrinkle. Since there are four advances per rank through 80 XP and two advances per rank thereafter, we need to differentiate the two progressions appropriately. This means an attribute advance is allowed only once out of every block of four advances during the first 16 advances, but only once out of every two advances after that. During our loop, we need to handle this distinction properly.

To enforce this check, we'll create a new thing that is intended solely for validation. That means we need to add it to the file "thing_validate.dat". Since the thing is performing checks on the overall hero, it can derive from the "Simple" component set. And since the thing needs to perform its checks on all actors, we assign it the "Helper.Bootstrap" tag to ensure it gets automatically applied to all heroes.

Most validation things possess only an EvalRule script, since they merely need to perform the validation test, and we could implement this thing the same way. However, we can make things a little simpler and more efficient by performing the validation in two stages. Each EvalRule script assumes the test is failed, so all the checks must be performed to ascertain whether each test is satisfied, but it's much easier for us to simply assume success and bail out if we ever determine that the requirements aren't satisfied. Consequently, the first stage is an Eval script that determines whether all the requirements are satisfied and assigns a tag to the hero if anything is failed. The second stage is the EvalRule that merely checks for the tag and reports the validation result accordingly. All we need to do is define a new "Hero.MultiAttr" tag in the file "tags.1st" and write the two scripts, which yields a new thing that looks like the following.

<thing
  id="valAdvance"
  name="Advances"
  compset="Simple">
  <tag group="Helper" tag="Bootstrap"/> 
  <eval index="1" phase="Validate" priority="8000"><![CDATA[
    var index as number
    var total as number
    var is_check as number
    ~iterate through all advances in the order they were added to the character
    foreach pick in hero where "component.Advance" sortas _CompSeq_ 

      ~determine whether we need to perform an actual check at this interval
      is_check = 0
      if (index % 4 = 0) then
        is_check = 1
      elseif (index % 2 = 0) then
        if (index > 16) then
          is_check = 1
          endif
        endif 

      ~if we're supposed to check, do so, then reset the tally count
      ~Note: If we find an error, assign a tag and there's nothing more to do.
      if (is_check <> 0) then
        if (total > 1) then
          perform hero.assign[Hero.MultiAttr]
          done
          endif
        total = 0
        endif 

      ~if this is an attribute increase, tally it
      if (eachpick.tagis[AdvanceId.advAttrib] <> 0) then
        total += 1
        endif 

      ~increment our index based on our cost and continue
      ~Note: Using the cost allows us to properly identify the transitions between
      ~ experience point levels where we need to perform our check.
      index += eachpick.field[advCost].value
      nexteach 

    ~perform a check on the final set of advances
    if (total > 1) then
      perform hero.assign[Hero.MultiAttr]
      endif
    ]]></eval> 

  <evalrule index="1" phase="Validate" priority="9000" 
    message="Multiple attributes increased within a rank" summary="Multiple attributes"><![CDATA[
    ~if we have no instances of multiple advances within a level, we're good
    if (hero.tagcount[Hero.MultiAttr] = 0) then
      @valid = 1
      done
      endif 

    ~mark associated tab as invalid
    container.panelvalid[advances] = 0
    ]]></evalrule> 
  </thing>