Adding Advancements (Savage): Difference between revisions
No edit summary |
No edit summary |
||
(One intermediate revision by the same user not shown) | |||
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. | 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 190: | Line 190: | ||
]]></eval> | ]]></eval> | ||
<evalrule | <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 |
Latest revision as of 07:08, 23 December 2008
Context: HL Kit … Authoring Examples … 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>