Creatures (Savage Worlds)

From HLKitWiki
Revision as of 05:01, 6 February 2009 by Rob (Talk | contribs) (Assigning Attributes)

Jump to: navigation, search

Context: HL KitAuthoring Examples … Savage Worlds Walk-Through 

Overview

If we want to be big help for GMs, we need to allow GMs to do more than just create NPCs. We must also let them quickly create monsters and animals.

New Character Type

Since creatures are fully defined within the rulebook, there is no substantial customization to be done, and there are definitely no rules that govern construction of creatures. Consequently, the first thing we need to do is add support for a new character type - the creature.

We currently has a field that indicates whether the character is an NPC or not. Since we'll now have three types of character, we'll change the field for more generalized use. The "acIsNPC" field can be changed to an "acCharType" field, where the value of the field can be one of three possibilities. A "zero" indicates a normal PC, a "one" indicates an NPC, and a "two" indicates a creature. The new field will look like below.

<field
  id="acCharType"
  name="Character Type"
  type="user"
  defvalue="0">
  </field>

We have a tag that identifies an NPC, but we really need separate tags to indicate all three different character types. We'll assign the appropriate tag based on the field value. The new tags will be defined within the "Hero" tag group and should include the following.

<value id="PC"/>
<value id="Creature"/>

The existing Eval script that identifies and NPC and sets up appropriately can be readily adapted. Instead of just recognizing an NPC, the script can recognize all three different values within the "acCharType" field. Based on the field value, the appropriate tag can be assigned to the hero, plus any other customizing of the interface. For example, the "Journal" tab is of no use for a creature. We're also going to need to either overhaul the "Edges" tab to only show racial abilities or replace it with an alternate tab. We'll assume the latter for now, which results in the following revised Eval script on the "Actor" component.

~assign a tag to indicate we're a PC, NPC, or Creature, as appropriate
if (field[acCharType].value = 0) then
  perform hero.assign[Hero.PC]
elseif (field[acCharType].value = 1) then
  perform hero.assign[Hero.NPC]
else
  perform hero.assign[Hero.Creature]
  endif

~if this is a standard PC, there's nothing more to do
if (hero.tagis[Hero.PC] <> 0) then
  done
  endif

~hide components of the interface that don't apply for NPCs and/or Creatures
perform hero.assign[HideTab.advances]
if (hero.tagis[Hero.Creature] <> 0) then
  perform hero.assign[HideTab.edges]
  perform hero.assign[HideTab.journal]
  endif

~force the starting XP field to zero in case the user has modified it
trustme
field[acStartXP].value = 0

Integrate Into Configure Hero Form

Now that we've changed the field, we need to change the "Configure Hero" form to properly set the field. We currently have a checkbox to designate whether the character is an NPC. We need to replace this checkbox with a way to choose between the three options: PC, NPC, and creature.

The easiest way to do this is with a menu that consists of literal choices. That way, we don't have to go through the extra work of defining an assortment of "things" to represent each option. With a literal menu, we have complete control over the choices presented and the corresponding values associated with each choice. We just need to spell out the list of choices as part of the menu definition.

You'll find an example of using a menu like this on the "Personal" tab. It's the one for selecting the character's gender, with options for male and female. We can copy this menu and adapt it for our needs. We've already determined the meaning of the values 0-2 for the "acCharType" field, so all we need to do is define corresponding options. This results in a new menu portal that looks like below.

<portal
  id="chartype"
  style="menuNormal">
  <menu_literal
    field="acCharType">
      <choice value="0" display="Type: Player Character"/>
      <choice value="1" display="Type: NPC (Unlimited)"/>
      <choice value="2" display="Type: Creature"/>
    </menu_literal>
  </portal>

Once the portal is defined, we can easily integrate it into the Position script. We're replacing the "isnpc" portal, so we can swap out all references to that portal and use "chartype" instead. The only detail that differs between menus and checkboxes is that checkboxes are automatically sized to fit the text they contain, while menus are not. This means we have to specify the width of the portal to something appropriate. We must do this before we center the portal horizontally by adding the line of code below.

portal[chartype].width = width

Our new portal should now be ready to go, with the proper selections being presented and stored internally within the field. However, our behaviors aren't quite right yet. We need to control the visibility of various portals based on the character type. The wildcard checkbox should only be visible for non-creatures, while the starting cash and XP should only be visible for PCs. This requires changing the block of lines at the start of the script to be the following.

~determine whether the wildcard option is visible based on whether we're a creature
portal[iswild].visible = !hero.tagis[Hero.Creature]

~determine whether the starting cash is visible based on whether we're a pc
portal[cash].visible = hero.tagis[Hero.PC]
portal[lblcash].visible = portal[cash].visible

~determine whether the starting xp is visible based on if we're a pc
portal[xp].visible = hero.tagis[Hero.PC]
portal[lblxp].visible = portal[xp].visible

New Creature Component

We could adapt the existing "Race" component for use with creatures, but creatures behave quite differently from normal characters. As such, we should define a new component and component set for handling creatures. We'll use the id "Creature" for both, and we'll force the creature specification to be unique for every actor. This yields the following basic definitions, which we'll extend below.

<component
  id="Creature"
  name="Creature"
  autocompset="no">
  </component>

<compset
  id="Creature"
  forceunique="yes">
  <compref component="Creature"/>
  </compset>

Some creatures are wildcards, while others are not. Based on the way creatures are presented in the rulebook, the wildcard designation is fixed for each particular creature. We've already omitted the checkbox from the "Configure Hero" form, so we need a way to designate a creature as a wilcard or not within its definition.

We could easily use a field on the "Creature" component for this purpose. However, the best way to handle either-or conditions for users is to utilize a tag. If the tag is present, the condition exists, else it doesn't - clean and simple. So we'll define a new tag that can be specified on the creature and give it the "Wildcard" unique id. Since this tag will be user-defined as part of the thing definition, we'll define the tag within the "User" tag group. Any creature that should be behave as a wildcard must be assigned this tag.

Now we need to do something with the tag. The actual wildcard behavior is managed via the "acIsWild" field on the "Actor" component. So we need to translate the tag into an appropriate modification of the field. This can be accomplished via an Eval script on the "Creature" component. We simply set the field value based on the presence of the tag. The appropriate Eval script looks like the one below.

<eval index="1" phase="Initialize" priority="5000"><![CDATA[
  trustme
  herofield[acIsWild].value = tagis[User.Wildcard]
  ]]></eval>

We should also track the creature type on the actor, just like we track the race. We can achieved that by defining an identity tag on each creature and forwarding that tag to the hero. This entails the following additions.

<identity group="Creature"/>

<eval index="2" phase="Setup" priority="5000"><![CDATA[
  perform forward[Creature.?]
  ]]></eval>

We can now define creatures that don't do anything useful, but the framework is in place that we can build upon.

Selecting the Creature

The next thing we'll be tempted to do is start solving how to implement creatures internally. However, that process is going to entail some iterative evolution. So the best thing we can do next is setup a means of selecting a creature, allowing us to test our implementation along the way.

The race is selected on the "static" form at the top of the main window, so we might as well do the same for the creature type. The question becomes whether we want to use the same chooser for both race and creature, or use a separate chooser for each. Through the use of various scripts and tag expressions, we could dynamically configure the chooser appropriately to each context. However, it's probably going to be a good bit simpler to manage a separate chooser, so that's what we'll do.

Creating our new chooser portal is rather simple. We can start by cloning the one for the race and then adapting it. We change the id, component, and text shown in the scripts. This results in the new portal shown below.

<portal
  id="stCreature"
  style="chsNormal"
  width="110">
  <chooser_table
    component="Creature"
    choosetemplate="LargeItem">
    <chosen><![CDATA[
      if (@ispick = 0) then
        @text = "{text ff0000}Select Creature Type"
      else
        @text = "Type: " & field[name].text
        endif
      ]]></chosen>
    <titlebar><![CDATA[
      @text = "Choose the type for your creature"
      ]]></titlebar>
    </chooser_table>
  </portal>

We now need to integrate the new portal into the layout, which starts with adding a new "portalref" for the chooser. Based on the nature of the character, we must show either the race or creature chooser. We also need to adjust the positioning of the "stActor" template to depend on whichever portal is being shown. The revised layout should look like the following.

<layout
  id="static">
  <portalref portal="stRace" taborder="20"/>
  <portalref portal="stCreature" taborder="20"/>
  <templateref template="stName" thing="heroname" taborder="10"/>
  <templateref template="stActor" thing="actor"/>

  <!-- This script sizes and positions the layout and its child visual elements. -->
  <position><![CDATA[
    ~determine whether the race of creature chooser is visible
    if (hero.tagis[Hero.Creature] <> 0) then
      portal[stRace].visible = 0
    else
      portal[stCreature].visible = 0
      endif

    ~position the name template on the left, with a little margin, then render it
    ~to generate the appropriate dimensions
    template[stName].left = 10
    perform template[stName].render

    ~position the race/creature portal to the right of the name
    var x as number
    x = template[stName].right + 20
    if (portal[stRace].visible <> 0) then
      portal[stRace].left = x
      x = portal[stRace].right
    else
      portal[stCreature].left = x
      x = portal[stCreature].right
      endif

    ~position the actor template a little to the right of the race/creature
    template[stActor].left = x + 15

    ~limit the width of the actor template to the remaining space available and
    ~then render the template
    template[stActor].width = width - template[stActor].left
    perform template[stActor].render

    ~center all visual elements vertically
    ~Note: We can't do this until after we've calculated the heights for both
    ~       templates, which is done when we render them.
    portal[stRace].top = (height - portal[stRace].height) / 2
    portal[stCreature].top = (height - portal[stCreature].height) / 2
    template[stName].top = (height - template[stName].height) / 2
    template[stActor].top = (height - template[stActor].height) / 2
    ]]></position>

  </layout>

There are two basic problems at this point. The first is simple to fix. The width of the race chooser is good for races. However, there are a variety of creatures with rather long names (e.g. "Alligator/Crocodile" and "Shark, Great White"). The current width of the portal is insufficient, so we'll widen it. We can solve this by changing the "width" attribute on the portal to something like "170".

The other problem we face is if the user does something we aren't handling. What if the user creates a normal character, selects a race, and the decides to change the character to be a creature. The race portal will disappear, but the selected race will still exist on the character. That means any abilities or bonuses conferred by the race will continue to apply, although there will be no way to change it. More importantly, there is no way to select "none" for the race, so any race the user selects will persist for the life of the character. That's not very useful.

What we need is a way to ensure that the race gets deselected if the character type changes to a creature. Just in case the user goes the other direction, we also need a way to ensure that any selected creature type is discard if the character type changes away from being a creature. Fortunately, the Kit provides a convenient mechanism for accomplishing exactly this behavior.

The mechanism is an Existence tag expression, which establishes requirements for the continued existence of picks. This tag expression is defined for a table or chooser and works just like a ContainerReq tag expression. Any pick that is added to a container via the portal inherits the Existence tag expression of the portal. The tag expression is then evaluated at a specified time during the evaluation cycle. If the tag expression ever fails to be satisfied, it fails its existence requirements and HL automatically deletes the pick.

We can put this to use with our two choosers. Any race added via the "stRace" chooser must be discarded if the character type becomes a creature. This results in an Existence tag expression that requires the container (i.e. the character) to not possess the "Hero.Creature" tag. We have to schedule the test to be performed before any scripts that depend on the race occur, but after the tag is assigned to the actor. So we add the following XML to the "stRace" chooser to impose the requirement.

<existence phase="Initialize" priority="2000">!Hero.Creature</existence>

Similarly, the "stCreature" chooser needs to enforce an Existence tag expression that is just the opposite in behavior. If the character type ever ceases to be a creature, the creature type must be discarded. This results in the XML element below being added to the chooser.

<existence phase="Initialize" priority="2000">Hero.Creature</existence>

At this point, switching between a creature and non-creature will automatically discard any existing selection corresponding to the other type.

Directly Defining Traits

It's now time to figure out how we're going to be handling the details of creatures. Each creature in the rulebook has die-types specified for each attribute. Creatures also have a list of skills, with the appropriate die-type for each. Derived traits are specified, incorporating any special adjustments beyond the standard derived value. Lastly, a list of gear and/or special abilities is given.

Our solution for creatures must enable all of these different facets to be readily assigned to each creature. We want a design that is both easy to use and also easy to utilize via the integrated Editor. For the latter, it's always easiest when tags are utilized, since users can simply pick from a controlled list that the Editor presents. The next easiest mechanism is simple fields, wherein the user can enter text or a value. After that, whatever works is going to be pretty much the same.

Given the list of requirements and goals above, we'll see about devising a good solution. As an added benefit, we'll also strive to allow the user to customize the default values for each creature if he wants.

Internal Approach

There are two different facets of traits that we'll need to handle. The first is the basic die-type for the trait, ranging from "d4" to "d12". The second is the roll bonus in situations where the creature has a trait that exceeds a "d12" rating.

These two facets are managed internally via two separate fields for each trait. As such, our first thought will likely be to do the same. We could have two separate fields that specify the starting die-type and bonus for each trait. An Eval script could be written that assigns the die-type value to the "trtBonus" field and the bonus value to the "trtRoll" field.

This would give us a workable solution, but it would be far from ideal. Since we're applying the values as bonuses, we run into a number of limitations. The most important limitation is that we are unable to allow the user to adjust the trait values. Since the attributes start at the minimum and the bonus is applied, users can increase the die-type but not decrease it. Consequently, we can't allow adjustment, since users will expect full range of adjustment to be possible. Another issue is that users would be unable to adjust the roll bonus. If the user wanted to change a trait from "d12+2" to "d12+1", there would be no way to do it, other than applying a permanent adjustment to the creature.

Giving the problem a bit more thought, a better approach would be to actually set the "trtUser" field of the trait to the proper die-type. This would make it possible for the user to then edit the die-type as he saw fit. Unfortunately, there are two liabilities with this technique. There would still be no way for the user to easily adjust any roll bonus, plus there would be nothing to prevent the user from modifying the die-type independently from the roll bonus. For example, a trait of "d12+1" could have only the die-type modified downwards one notch, which would result in a trait of "d10+1" instead of "d12".

Based on this analysis, we need a single unified value that represents both the die-type and the roll bonus. Once the value reaches a "d12" rating, increasing it one step would become "d12+1". The increase would steadily adjust the roll bonus as soon as the maximum die-type was reached. How can we do this?

The trick is to modify the behavior of the "trtUser" field when dealing with a creature. For a normal character, the "trtUser" field has a fixed range of 2-6, corresponding to the "d4" through "d12" die-types. However, we already have to handle the case of the die-type exceeding "d12" when determining what to display, so having a value of 8 will still result in a "d12" being displayed. This means we can change the maximum limit to higher than 6 and the die-type display will continue to work fine.

Armed with this knowledge, we can consider values larger than 6 to indicate progressive increases in the roll bonus. A value of 7 would translate to "d12+1", while 8 would translate to "d12+2", etc. We can write an Eval script to detect a value greater than 6 and automatically apply the excess as a roll bonus by modifying the "trtRoll" field.

Since the "trtUser" field is directly modifiable by the user via the incrementer, this approach affords the user with a simple, intuitive means of adjusting traits. They start out at "d4", progress upwards through "d12", and then continue to increase as "d12+N". Decrementing the trait works seamlessly in the other direction. We've got our strategy mapped out.

External Approach

We also have to figure out a convenient way to let authors specify the die-type and roll bonus values for each trait. Whatever we come up with should be readily adaptable for use within the integrated Editor, thereby allowing users to add their own custom creatures easily.

As above, the first idea is probably going to be two separate fields that can be specified. Since we're only assigning starting values, we don't have to worry about having a smooth progression. However, two separate values does make it possible for the user to inadvertently specify a roll bonus with a die-type less than "d12", such as "d8+2". So a single value would be ideal.

If we only have a single value, we then need to assess what that value will be and how the user will specify it. We currently use a value of 2-6 for the die-types "d4" through "d12". Our internal solution calls for using values of 7+ to indicate the roll bonus beyond a "d12". While all this makes perfect sense for internal use, it's going to seem like nonsense to a user with no knowledge about the inner workings of our data files. An most users don't want to understand the inner workings - they just want to knock out a quick creature for use in the upcoming game and be done with it. That means field values are a poor solution.

The best solution is to use an assortment of tags. We could define a new tag group that contains tags corresponding to each internal value we want to support. Each tag could be defined with a suitable publicly visible name that presents something intuitive to the user. For example, the tag for the value "7" would have a name of "d12+1", so the user would select "d12+1" within the Editor and the proper value of 7 would be assigned internally. We can use the "tagvalue" target reference within the scripting language to extract a value from the unique id of each tag, so we simply need to ensure that the unique id of the tags is the value we want to use internally.

At this point, we now have both our internal and external approaches mapped out. We can now begin implementing our solution.

Assigning Attributes

We'll first setup the means for specifying the attribute values to be used when defining creatures. We need to use a group of tags to specify both the die-type and roll bonus for each attribute. However, if we only define one set of tags and re-use them, we'll run into the problem of determining which tag is associated with which attribute. This means we need a separate set of tags for each attribute.

The tag group for specifying the "Agility" attribute is shown below. We define tags that extend up to a "+10" bonus on the trait, which will be the maximum we support. Comparable tag groups must also be specified for each of the other four attributes.

<group
  id="AgiDie"
  name="Agility Die-Type">
  <value id="2" name="d4"/>
  <value id="3" name="d6"/>
  <value id="4" name="d8"/>
  <value id="5" name="d10"/>
  <value id="6" name="d12"/>
  <value id="7" name="d12+1"/>
  <value id="8" name="d12+2"/>
  <value id="9" name="d12+3"/>
  <value id="10" name="d12+4"/>
  <value id="11" name="d12+5"/>
  <value id="12" name="d12+6"/>
  <value id="13" name="d12+7"/>
  <value id="14" name="d12+8"/>
  <value id="15" name="d12+9"/>
  <value id="16" name="d12+10"/>
  </group>

We should now have five tag groups, each with its own set of tags. Each creature can be assigned a single tag from each tag group, with each tag dictating the appropriate die-type and roll bonus for its corresponding attribute. For example, the creature below would be assigned die-types of "d4" through "d12" for the various attributes.

<thing
  id="creSample"
  name="Sample"
  compset="Creature"
  isunique="yes"
  description="Description goes here">
  <tag group="AgiDie" tag="2"/>
  <tag group="SmaDie" tag="3"/>
  <tag group="SpiDie" tag="4"/>
  <tag group="StrDie" tag="5"/>
  <tag group="VigDie" tag="6"/>
  </thing>

Assigning Skills

Assigning Derived Traits

Assigning Gear

Selecting Abilities

  • race-specific abilities
  • creature-specific abilities
    • shared abilities with races
    • separate file to hold all of the general/shared abilities
  • default abilities that can be added to any creature
    • identifying the abilities distinctly from other abilities

Defining a Complete Creature

-change history tracking to "changes" to ignore values of zero

Tailoring the Interface

Resource Revisions

Basics and Skills Tabs

New Abilities Tab

Safety Checks

Existence Tag Expressions

-existence tagexpr to auto-delete race/creature upon switch

Validation Rules

Allow User to Add Abilities

-tag to identify general abilities for re-use -allow user to specify facets of dynamic abilities that are needed (value/text)