Advancement Support (Savage)
Context: HL Kit … Authoring Examples … Savage Worlds Walk-Through
Overview
The Savage Worlds game system uses a character advancement system that requires each separate advancement be performed in a strict, formalized sequence. This is because different types of advancements are restricted based on the relative state of different traits (e.g. skill values relative to their linked attributes), and that state changes with each advancement. Sequential advancement logic can be somewhat complex to support, so the Skeleton files provide a built-in mechanism to orchestrate serialized advancement that can be readily adapted for use with most game systems that require the mechanism. This section outlines how that adaptation is achieved for Savage Worlds.
IMPORTANT! Before continuing with the rest of this section, you should be familiar with the basics of how advancement is handled within the Skeleton files.
Accessing Linked Attributes
A fundamental facet of the advancement logic is that each advance is actually a gizmo that contains the details of the advance within it as child picks. Selections that reside within a gizmo are not directly attached to the hero. However, the Kit provides a mechanism called displacement that allows specific selections within a gizmo to appear and behave as if they are directly attached to the hero.
The advancement logic makes use of displacement so that new skills and edges added to a character via an advance behave as if the user added them directly to the hero. But this only applies to new skills and edges. If the advance increases an existing skill or attribute, the selected trait is not displaced to the hero, as it already exists on the hero. This distinction is critical when working with the advancement logic, as it has important implications for linkages.
Linkages look for the linked pick within the same parent context as the reference pick. For example, when accessing the linked attribute for a skill on the hero, that attribute will be sought within the hero. However, if the skill is within the gizmo for an advance, accessing the attribute linkage will look for the attribute within the gizmo. This will obviously fail, since there is no attribute within the gizmo. This will result in a run-time error being reported.
The use of displacement makes this whole mechanism a bit more murky. If a skill within a gizmo is displaced to the hero, then that skill behaves as if it exists directly on the hero. As such, attempts to access the attribute linkage for a displaced skill will succeed, as it works exactly like a skill that is directly added to the hero. However, skills within a gizmo that are not displaced will fail to access the linkage and report the run-time error.
The way that advancements work, new skills (and edges) get displaced onto the hero. However, increases to existing skills (and attributes) are not displaced, so the skill selection lives solely within the context of the gizmo. This means that any script performed on a skill will be processed in two different contexts. Skills added directly to the hero (or displaced onto the hero) will be able to access their linked attribute, while skills within the gizmos will not. Any script that accesses the attribute linkage must verify whether the linkage is available before accessing it. This can be determined by simply verifying whether the effective container for the pick is the hero, which is achieved by the script logic shown below.
if (container.ishero <> 0) then ~the container is the hero, so accessing linkages will succeed endif
So this begs the question of what happens when the linkage isn't accessible. The answer is "nothing", and that's perfectly acceptable in this situation. The reason for this is that attribute linkages are only needed for new skills that get added to the hero (both directly and via displacement). Skills that are selected as increases don't need to actually perform normal handling for skills. Instead, all they need to do is apply the appropriate increase to the existing skill within the hero, and that does not require access to the attribute linkage.
This conditional handling of access to the attribute linkage is needed in the Eval script that retrieves the identity tag of the linked attribute into the skill. The revised version of the script should look similar to the one below.
<eval index="3" phase="Setup" priority="5000"><![CDATA[ ~only access the linkage if the skill is directly on the hero; if not, we assume ~it is within an advancement gizmo and no linkage will exist there; we also don't ~need the linked attribute tag on advancement skills, so it's a non-issue if (container.ishero <> 0) then perform linkage[attribute].pullidentity[Attribute] endif ]]></eval>
NOTE! The attribute linkage of skills should not be confused with the "basis" linkage used within advances. Whenever an existing pick is selected via a chooser, that pick is automatically tracked by HL as a "basis" pick, and accessing that pick is achieved via the "linkage[basis]" script transition. This mechanism ensures that any advance applied to an existing pick (e.g. an attribute or skill increase) can always identify that "basis" pick in order to apply adjustments to it. The "basis" linkage is a special type of linkage that is very different from normal linkages, but it is accessed via the "linkage[]" transition for syntactic and semantic consistency within scripts.
Increasing Skills Via Advances
After assessing the different advancement options for Savage Worlds, the only ones that introduce new complexities are those pertaining to skills, and there are two issues to contend with. The first wrinkle is the distinction between skills that are less than their linked attribute and those that are not, as a different advance must apply to each. The easiest way to solve this is by having each skill identify its own nature during evaluation, with an appropriate tag being assigned. Since it's an either-or situation, only a single tag is needed, with its presence indicating one state and its absence indicating the other.
We define a "Helper.LessThan" tag and add an Eval script to the "Skill" component to make the appropriate determination. The script can be processed very late in the overall logic and should look similar to the one below. Since the attribute linkage must be accessed, we need to limit our processing to skills that are directly on the hero (or displaced onto the hero).
<eval index="4" phase="Final" priority="10000"><![CDATA[ ~only access the linkage if the skill is directly on the hero; if not, it is ~likely within an advancement gizmo and no linkage will exist there; we also ~don't need the linked attribute tag on advancement skills, so it's a non-issue if (container.ishero <> 0) then if (field[trtFinal].value < linkage[attribute].field[trtFinal].value) then perform assign[Helper.LessThan] endif endif ]]></eval>
The second area of complexity is the advance that allows the character to increase two separate skills as part of the one advance. Doing this is possible, but it would require a fair amount of work and would go beyond the framework provided by the built-in advancement mechanism. So a simpler alternative would be preferable. Fortunately, this is easy to solve if we have the user increase the two skills separately via distinct advances. We can accomplish this by introducing a new advance that only increases a single skill and assigning it an advancement "cost" of only half an advance. This way, the user can select two of these advances for the standard cost of a normal advance. Since there is only one situation like this, we can safely introduce this new mechanism and have it be relatively intuitive for the user to work with. The specifics of this approach will be implemented later when we define the skill-related advances.
Identify Existing Skills
There are some advances that need to uniquely identify individual skills for appropriate handling. To support this, every skill must possess a suitable identity tag, and those tags must be forwarded up to the hero for reference. This entails defining the identity tag and forwarding it exactly the same way that we've already done a few times. The XML element below will define the identity tag behavior, and the script below will handle the forwarding. These must be added to the "Skill" component in the file "traits.str".
<identity group="Skill"/> <eval index="1" phase="Setup" priority="5000"><![CDATA[ perform forward[Skill.?] ]]></eval>
Don't Apply Creation Costs
With the changes made thus far, attributes, skills, and edges can now be added/increased via advances. These advances must not count towards the number of attribute and skill points that are spent on a given character, nor any starting edges that a character may be entitled to. This requires appropriate handling for each different type of trait.
For attributes, we only want to accrue attribute points when the user increases the attribute during character creation. All other attribute increases (e.g. via advances) must be ignored. This is achieved by ensuring that our attribute points only utilize the user-specified value for the attribute, as all advances increase the "bonus" field. Fortunately, we're already doing this, so no adjustments are required.
For skills, we need to address two separate situations. First, skill advances must be handled the same way as attributes, limiting the cost to the value assigned to the user-specified field for the skill. Just like with attributes, we're already doing this, so no adjustments are required. The second situation is for new skills that are added via advances - not just increased. For such skills, the entire skill needs to be ignored when tallying the skill point cost, else we'll treat the initial adding of the skill as one skill point. This requires that we modify the existing Eval script for tallying the points so that skills added via advances are ignored. The revised script below shows the change that must be made within the "Skill" component.
<eval index="2" phase="Setup" priority="5000"><![CDATA[ ~if this skill is not added directly to the hero (i.e. an advance), skip it entirely if (origin.ishero = 0) then done endif ~the base value for skills is two, so we need to adjust by one to get the proper cost hero.child[resSkill].field[resSpent].value += field[trtUser].value - 1 ]]></eval>
Edges are never increased via advances, but they are added via advances just like skills. As such, they need to be handled the same way that skills are handled. The edge needs to be ignored when tallying edge slots when it is added via an advance. This entails that same change as above for the "Edge" component, yielding the following revised script.
<eval index="2" phase="Setup" priority="5000"><![CDATA[ ~if this edge is not added directly to the hero (i.e. an advance), skip it entirely if (origin.ishero = 0) then done endif ~consume another edge slot #resspent[resEdge] += 1 ]]></eval>
Configuring Serialized Advancement
The logic for serialized advancement can be enabled and configured for a game system via the file "definition.def". Within this file, the "advancement" element encapsulates all the generalized details for serialized advancement. Enabling advancement is controlled via the "enable" attribute. You can also customize the terminology HL uses in conjunction with advancement via the "createterm" and "advanceterm" attributes, although the default terms are usually acceptable.
Within the "advancement" element, there is a child "canadvance" element that contains the CanAdvance script. This script allows you to perform verification before allowing the user to transition from the initial "creation" mode into "advancement" mode. For example, if you want to ensure that the user has allocated all the appropriate starting resources before switching to "advancement" mode, you can perform the proper checks in this script. If the script returns an empty "@message" special symbol, HL assumes all is well and allows the transition to occur. If any message text is generated by the script, failure is assumed and the message is displayed to the user as an explanation of what the problem is. For Savage Worlds, we need to verify that the user has properly allocated all attribute and skill points, as well as any starting edges and any rewards for starting hindrances.
Also within the "advancement" element is a child "transition" element that contains the Transition script. This script determines the message to be displayed to the user when the character transitions into and out of "advancement" mode. Normally, a simple reminder of the transition's implications and how to transition back is all that is needed, but you can customize the script to whatever extent you feel is appropriate. The default script provided by the Skeleton files presents a suitable notification that should work in most cases. For Savage Worlds, the default script works great.
Putting it all together, the "advancement" element for Savage Worlds should look similar to the example shown below. The mechanism is enabled, the terms are left in their default state, the Transition script is unchanged, and the CanAdvance script is tailored for the needs of a Savage Worlds character.
<advancement enable="yes" <canadvance><![CDATA[ var bullet as string bullet = "{bmp bullet_red}{horz 4}" @message = "" ~perform tests to assure all starting resources have been assigned if (#resleft[resCP] <> 0) then @message = @message & bullet & "Character points must be assigned for the character.\n" endif if (#resleft[resAbility] <> 0) then @message = @message & bullet & "Ability slots must be assigned for the character.\n" endif ]]></canadvance> <transition><![CDATA[ if (state.iscreate <> 0) then @message = "{b}{text ffff00}Creation Phase{text 010101}{/b}" @message &= "\n\n" @message &= "{align left}You have unlocked your character, thereby exiting the Character Advancement phase and moving back to the Character Creation phase. " @message &= "\n\n" @message &= "While unlocked, traits defined during character creation can be adjusted, as long as those traits have not yet been altered on the Advances tab. " @message &= "Traits that already have advancements applied to them will remain locked unless those advancements are deleted. " @message &= "\n\n" @message &= "Lock your character and remove any advancements on a trait if you wish to revise the rating that trait was given during character creation. " else @message = "{b}{text ffff00}Advancement Phase{text 010101}{/b}" @message &= "\n\n" @message &= "{align left}You have locked your character creation traits. " @message &= "\n\n" @message &= "By locking your character creation traits, you have begun the Character Advancement phase of play. " @message &= "While locked, you cannot alter traits defined during character creation. " @message &= "Use the Advances tab while the character is locked to allocate advances to new abilities or to increase existing traits. " @message &= "\n\n" @message &= "Unlock the character to go back to the Character Creation phase. " endif ]]></transition> </advancement>