NPC Support (Savage)
Context: HL Kit Authoring Examples Savage Worlds Walk-Through
A major benefit of HL to players is the speed and simplicity of creating characters. That same benefit should exist for GMs, but the advancement mechanism of Savage Worlds requires that characters be created one advance at a time. This leaves GMs with a choice of either creating an NPC using an incremental approach or ignoring all the validation errors by just "winging it". GMs typically need lots of NPCs, so the one-at-a-time advancement method is clunky and slow. GMs also need to have a good idea of the relative power levels of NPCs compared to the PCs, so ignoring the validation errors requires the GM to have an innate sense of how powerful a given NPC is.
There must be a better way to create NPCs using our data files. What we need is a way to create a character without any restrictions and tell the GM exactly how powerful that character is. Unless a character is created in a step-wise manner, we'll never be able to determine exactly how many advances a character used to reach a particular set of abilities. However, we can make a very close estimate, which is generally all that a GM really wants.
We're going to allow the GM to create NPCs freely, without any of the normal restrictions that apply to new characters. This requires that we let the user tell us whether he's creating an NPC. Since this is something fundamental to the character, we should add this to the "Configure Hero" form. This also requires that we turn off various validation tests that won't be applicable to NPCs.
Based on the various increases assigned to the character, we'll calculate how many advances were needed to reach that point. These advances must be tracked beyond the normal assignments for a starting character. Based on the advances, we can then determine how many XP were required for the character, which will then tell us the rank of the NPC.
The one main problem with this approach is that we don't know the exact order in which the advances are selected for the character. For edges, this isn't a problem, because we can simply assume that any edges satisfying its pre-requisites was taken in a valid order during the character's evolution. For attributes, this also isn't a problem, since characters are allowed exactly one attribute increase per rank. Skills are where we run into a problem with the order in which advances are taken. We can't know whether a skill was increased before or after its linked attribute was increased.
This leaves us with two options for determining the number of advances: optimism or pessimism. In an optimistic model, we assume that all skills are advanced after any linked attributes are first increased. While not always realistic, this method presumes the NPC optimized his advances to get the most out of them. The alternative is a pessimistic model, where we assume all skills are increased before their linked attributes. This approach is much less realistic than the optimistic model, so we'll go with the optimistic one.
Identifying an NPC
Before we can do anything else, we need to enable the user to identify an NPC as distinct from a normal PC. This requires that we track that state somewhere within each character. The obvious solution is to use a field on the "Actor" component, just like we did for identifying wildcards. We'll assume that all characters start out as PCs, which means our default state is for the NPC field to be zero. So we'll define a new first for the purpose, as shown below.
<field id="acIsNPC" name="Is NPC?" type="user" defvalue="0"> </field>
While a field value is useful in most cases for identifying an NPC, there may be times we'll want to check for an NPC via a tag expression. In fact, using a tag is generally the best solution, as it's the most general technique. So we need to define a tag to identify NPCs, and we should make a point of using the tag everywhere instead of the field for consistency. Since the tag will only ever be on the hero, we'll add it to the "Hero" tag group.
We now need to assign the tag to the hero appropriately. For that, we'll define an Eval script on the "Actor" component. Within this script, we'll also take the opportunity to configure other behaviors that we want for NPCs. For example, the "Advances" tab makes zero for NPCs, so we should hide it when the character is an NPC. This results in a script like the following.
<eval index="6" phase="Initialize" priority="1000"><![CDATA[ ~if this is not an NPC, just get out if (field[acIsNPC].value = 0) then done endif ~assign a tag to indicate we're an NPC perform hero.assign[Hero.NPC] ~hide components of the interface that don't apply for NPCs perform hero.assign[HideTab.advances] ]]></eval>
We can track whether a character is an NPC internally, so it's time to expose that the user. We decided earlier that we'll add a new option to the "Configure Hero" form (within the "cnfStart" template). We can use a simple checkbox, just like we did for the wildcard state. This checkbox will be tied to the new "acIsNPC" field and should look like the one below.
<portal id="isnpc" style="chkNormal" tiptext="Check this option to construct the character as an unlimited NPC"> <checkbox field="acIsNPC" message="Create Unlimited NPC?"> </checkbox> </portal>
The next step is to place the portal appropriately within the template. However, when we take a look at the current template, there is an option in there that requires special handling. The option to specify starting XP is rather silly for an NPC. As such, we should hide that option if the user chooses to create an NPC. If we hide the portal, we then need to shift other portals around to keep everything looking good. This results in the following revised Position script for the template.
~set the width of the template to something we like width = 185 ~determine whether the starting xp is visible based on if we're an npc portal[xp].visible = !hero.tagis[Hero.NPC] portal[lblxp].visible = portal[xp].visible ~position the title at the top perform portal[label].centerhorz ~position the starting cash beneath the ability slots perform portal[cash].alignrel[ttob,label,15] perform portal[lblcash].centeron[vert,cash] portal[cash].width = 50 portal[lblcash].left = (width - portal[lblcash].width - portal[cash].width - 10) / 2 perform portal[cash].alignrel[ltor,lblcash,10] ~if visible, position the starting xp beneath the starting cash var y as number if (portal[xp].visible = 0) then y = portal[cash].bottom else perform portal[xp].alignrel[ttob,cash,15] perform portal[lblxp].centeron[vert,xp] portal[xp].width = 50 portal[lblxp].left = (width - portal[lblxp].width - portal[xp].width - 10) / 2 perform portal[xp].alignrel[ltor,lblxp,10] y = portal[xp].bottom endif ~position the wildcard checkbox beneath the starting xp perform portal[iswild].centerhorz portal[iswild].top = y + 15 ~position the npc checkbox beneath the wildcard perform portal[isnpc].centerhorz perform portal[isnpc].alignrel[ttob,iswild,14] ~set the height of the template based on the extent of the portals ~Note: Include a little extra space at the bottom for borders and such. height = portal[isnpc].bottom + 3
This looks good, but it's a little bit tight with the menu for the specifying whether the character is an ally or enemy of the party. We can increase the gap easily within the Position script for the layout. This is accomplished by changing the top position of the "cnfAlly" portal, which results in the new line of code shown below.
portal[cnfAlly].top = template[cnfStart].bottom + 20
The final thing we need to do here is add a small safeguard. If the user enters a value for the starting XP and then selects an NPC, the portal will disappear and the character will still have a non-zero starting XP. The solution is to forcibly zero out the field if the user ever selects the NPC option. We can do this in the Eval script we added to the "Actor" component earlier by just setting the field value.
Unfortunately, the compiler complains when we try to set the field value. This is because it's a "user" field, and such should generally only be modified by the user, so an error is reported. Fortunately, we can tell the compiler we know what we're doing via use of the "trustme" statement. This results in the following lines of code being added to the end of the Eval script.
~force the starting XP field to zero in case the user has modified it trustme field[acStartXP].value = 0
Showing Resources Differently
Calculating the XP
Integrating the XP
Creating NPCs as the Default
new source to control Eval script that sets the value of the fields at the very beginning