NPC Support (Savage)

From HLKitWiki
Revision as of 15:33, 29 January 2009 by Rob (Talk | contribs) (Showing Resources Differently)

Jump to: navigation, search

Context: HL KitAuthoring Examples … Savage Worlds Walk-Through 

Overview

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.

Our Approach

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.

<value id="NPC"/>

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

Once we designate a character as an NPC, we'll immediately notice a variety of behaviors that need to be modified for NPCs. The majority of these behaviors center around the way we show information to the user. For example, all the validation errors for attribute points, skill points, and edges are no longer applicable. The display of the allocation of points to those categories on the Basics tab are now inappropriate. The title bars above skills and edges that

resources that we use to

Calculating the XP

Reporting Inconsistencies

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