Creature Refinement (Savage)
Context: HL Kit … Authoring Examples … Savage Worlds Walk-Through
Overview
We've now got the ability to define and customize creatures in place. However, there are still a fair number of tasks we need to complete before creatures are fully handled. The sections below address these remaining issues.
Organizing Abilities
Between all the races and creatures, we've got a long list of special abilities. These abilities seem to break down into five different classifications.
- Common racial abilities that can be shared by multiple races (e.g. Tough, Agile)
- Race-specific abilities that are unique to a single race (e.g. Avion Flight, Saurian Senses)
- Common creature abilities that can be shared by multiple creature (e.g. Size, Infravision)
- Creature-specific abilities that are unique to a single creature (e.g. Rollover, Bear Hug)
- Generic abilities that are shared by both races and creatures (e.g. Natural Weapons, Natural Armor)
To keep everything organized, we'll partition these abilities across three separate data files.
- One file will contain all the races and race-specific abilities ("thing_races.dat")
- Another file will contain all the creatures and creature-specific abilities ("thing_creatures.dat")
- The third file will contain only re-usable special abilities for both races and/or creatures ("thing_abilities.dat")
We need a way to readily identify common creature abilities as such. For this, we'll define a new "User.Creature" tag, which we can then assign to all abilities that can be used freely by any creature. These abilities will consist of the two generic abilities we created for natural weapons and armor, as well as all of the common creature abilities spelled out in the rulebook.
Defining Abilities
We can now focus on getting all of the common creature abilities properly defined. The core rulebook outlines a couple dozen of these abilities. We should get them all into place before we start defining lots of creatures that depend on them.
We'll pick a few of these abilities to implement as examples here. We'll start with the "Size" ability, which specifies a non-standard size for the creature. The ability requires the specification of a size bonus (or penalty). We'll utilize the customizable ability mechanism and define the size bonus via the "abilValue" field. This bonus is both applied to the "Toughness" trait as an adjustment and incorporated into the name for display. This results in the ability definition below, which is followed by an example of its use.
<thing id="abSize" name="Size" compset="RaceAbil" isunique="yes" description="Description goes here"> <tag group="User" tag="Creature"/> <!-- Apply the size bonus to the Toughness trait --> <eval index="1" phase="PreTraits" priority="5000"> <before name="Calc trtFinal"/><![CDATA[ perform #traitadjust[trTough,+,field[abilValue].value,"Size"] field[livename].text = field[name].text & " " & signed(field[abilValue].value) ]]></eval> </thing>
<bootstrap thing="abSize"> <assignval field="abilValue" value="2"/> </bootstrap>
Our next example is the "Immunity" ability. This ability requires that specification of the nature of the immunity. The customizable ability mechanism can again be used, only this time we'll specify the specific immunity via the "abilText" field. The immunity can then be integrated into the name for the display. This yields the ability definition below, followed by an example of its use.
<thing id="abImmunity" name="Immunity" compset="RaceAbil" description="Description goes here"> <tag group="User" tag="Creature"/> <!-- Append the immunity to the name --> <eval index="1" phase="Traits" priority="10000"><![CDATA[ field[livename].text = field[thingname].text & ": " & field[abilText].text ]]></eval> </thing>
<bootstrap thing="abImmunity"> <assignval field="abilText" value="Fire"/> </bootstrap>
Some abilities automatically confer other abilities. For example, the "Elemental" ability confers the "Fearless" ability. This is easily achieved by simply bootstrapping the conferred ability, as shown in the definition below.
<thing id="abElement" name="Elemental" compset="RaceAbil" isunique="yes" description="Description goes here"> <tag group="User" tag="Creature"/> <bootstrap thing="abFearless"/> </thing>
You should now be able to implement all the various abilities utilized in the rulebook. Alternately, you can simply look at the completed Savage Worlds data files provided with HL.
New Abilities Tab
We eliminated the "Edges" tab with the assumption that we would replace it with a suitable alternative. We'll take care of that now. What we need is a tab where all the abilities assigned to the creature can be shown to the user. We only need one table, so we can clone the "Skills" tab and adapt to our needs.
Start by copying the "tab_skills.dat" file to "tab_abilities.dat", then open the new file for modification. We need to define a new table portal. Since all the abilities are static for a creature, we simply need a fixed table. All we need to show is the name of each ability, so we can use the "SimpleItem" template to show the abilities. This results in the portal definition shown below.
<portal id="abAbility" style="tblNormal"> <table_fixed component="RaceAbil" showtemplate="SimpleItem"> <headertitle><![CDATA[ @text = "Monstrous Abilities" ]]></headertitle> </table_fixed> </portal>
We can then adapt the layout to show our new portal and have its own identity. The new layout should look like the following.
<layout id="abilities"> <portalref portal="abAbility" taborder="10"/> <position><![CDATA[ ~position and size the table to span the full layout; it will only use the ~vertical space that it actually needs perform portal[abAbility].autoplace ]]></position> </layout>
The final step is to revise the panel definition. We want this panel to appear in place of the "Edges" panel, so we'll assign it the same "order" value as that panel. We also want this panel to only appear for creatures, so we'll use a Live tag expression that requires the presence of the "Hero.Creature" tag. After we modify the name and other references appropriately, we end up with the panel below.
<panel id="abilities" name="Abilities" marginhorz="5" marginvert="5" order="130"> <live>Hero.Creature</live> <layoutref layout="abilities"/> <position><![CDATA[ ]]></position> </panel>
We're now ready to give it a try. Reload the files and create a creature. The new tab should appear with a list of the various abilities assigned to the creature.
Defining Complete Creatures
All the pieces are in place to allow you to define any of the creatures in the core rulebook. We'll take this opportunity to define a few of them here as examples.
The first example we'll use is the "Alligator/Crocodile" creature. This creature has a non-standard "Pace" on the ground, as well as a special "Pace" in the water. It utilizes both a natural weapon and natural armor. Plus it has a creature-specific ability. The full definition of the creature and its new special ability is shown below.
<thing id="creAlligat" name="Alligator/Crocodile" compset="Creature" isunique="yes" description="Description goes here"> <fieldval field="crePace" value="-3"/> <tag group="AgiDie" tag="2"/> <tag group="SmaDie" tag="2"/> <tag group="SpiDie" tag="3"/> <tag group="StrDie" tag="5"/> <tag group="VigDie" tag="5"/> <bootstrap thing="skFighting"> <autotag group="SkillDie" tag="4"/> </bootstrap> <bootstrap thing="skGuts"> <autotag group="SkillDie" tag="3"/> </bootstrap> <bootstrap thing="skNotice"> <autotag group="SkillDie" tag="3"/> </bootstrap> <bootstrap thing="skSwimming"> <autotag group="SkillDie" tag="4"/> </bootstrap> <bootstrap thing="abArmor"> <assignval field="abilValue" value="2"/> </bootstrap> <bootstrap thing="abWeapon"> <assignval field="livename" value="Bite"/> <assignval field="abilText" value="+d6"/> </bootstrap> <bootstrap thing="abAquatic"/> <bootstrap thing="abRollover"/> <!-- We have an aquatic pace of 5 --> <eval index="1" phase="PreTraits" priority="5000"><![CDATA[ hero.child[trPace].field[trtSpecial].value = 5 ]]></eval> </thing> <thing id="abRollover" name="Rollover" compset="RaceAbil" isunique="yes" description="Description goes here"> </thing>
For our next example, we'll use the "Large Bear" entry. This creature introduces the use of a Strength rating above "d12". It also utilizes a natural weapon and the "Size" ability, as well as its own custom ability. The full definition of the creature is shown below.
<thing id="creBearLg" name="Bear, Large" compset="Creature" isunique="yes" description="Description goes here"> <fieldval field="crePace" value="2"/> <tag group="AgiDie" tag="3"/> <tag group="SmaDie" tag="3"/> <tag group="SpiDie" tag="4"/> <tag group="StrDie" tag="10"/> <tag group="VigDie" tag="6"/> <bootstrap thing="skFighting"> <autotag group="SkillDie" tag="4"/> </bootstrap> <bootstrap thing="skGuts"> <autotag group="SkillDie" tag="5"/> </bootstrap> <bootstrap thing="skNotice"> <autotag group="SkillDie" tag="4"/> </bootstrap> <bootstrap thing="skSwimming"> <autotag group="SkillDie" tag="3"/> </bootstrap> <bootstrap thing="abWeapon"> <assignval field="livename" value="Claw"/> <assignval field="abilText" value="+d6"/> </bootstrap> <bootstrap thing="abSize"> <assignval field="abilValue" value="2"/> </bootstrap> <bootstrap thing="abBearHug"/> </thing> <thing id="abBearHug" name="Bear Hug" compset="RaceAbil" isunique="yes" description="Description goes here"> </thing>
For our final example, we'll implement a creature that's got some interesting twists. The "Lich" is a wildcard, has skills that exceed a "d12" rating, and has a skill that requires a domain be specified. It also has gear in the form of magical armor and gains arcane powers with extra bonuses to spells and power points. The full implementation is shown below (the "Undead" ability is assumed already defined as one of the common abilities shared by creatures).
<thing id="creLich" name="Lich" compset="Creature" isunique="yes" description="Description goes here"> <tag group="User" tag="Wildcard"/> <tag group="AgiDie" tag="3"/> <tag group="SmaDie" tag="8"/> <tag group="SpiDie" tag="5"/> <tag group="StrDie" tag="5"/> <tag group="VigDie" tag="5"/> <bootstrap thing="skFighting"> <autotag group="SkillDie" tag="4"/> </bootstrap> <bootstrap thing="skGuts"> <autotag group="SkillDie" tag="6"/> </bootstrap> <bootstrap thing="skIntimid"> <autotag group="SkillDie" tag="6"/> </bootstrap> <bootstrap thing="skKnow"> <autotag group="SkillDie" tag="8"/> <assignval field="domDomain" value="Occult"/> </bootstrap> <bootstrap thing="skNotice"> <autotag group="SkillDie" tag="5"/> </bootstrap> <bootstrap thing="skSpellcst"> <autotag group="SkillDie" tag="6"/> </bootstrap> <bootstrap thing="abDeathTch"/> <bootstrap thing="abSpellLch"/> <bootstrap thing="abUndead"/> <bootstrap thing="abZombie"/> <bootstrap thing="armMagLich"> <autotag group="Equipment" tag="StartEquip"/> </bootstrap> </thing> <thing id="armMagLich" name="Magical Armor +6" compset="Armor" description="Description goes here" isunique="yes"> <fieldval field="defDefense" value="6"/> <tag group="Equipment" tag="StartEquip"/> <tag group="ArmorLoc" tag="Torso"/> <tag group="ArmorLoc" tag="Arms"/> <tag group="ArmorLoc" tag="Legs"/> <tag group="ArmorLoc" tag="Head"/> </thing> <thing id="abDeathTch" name="Death Touch" compset="RaceAbil" isunique="yes" description="Description goes here"> </thing> <thing id="abSpellLch" name="Spells" compset="RaceAbil" isunique="yes" description="Description goes here"> <bootstrap thing="edgArcMag"/> <eval index="1" phase="PreTraits" priority="5000"><![CDATA[ ~confer an extra 40 power points and an extra 7 spells (50/10 total) #resmax[resPowers] += 7 #trkmax[trkPower] += 40 ]]></eval> </thing> <thing id="abZombie" name="Zombie" compset="RaceAbil" isunique="yes" description="Description goes here"> </thing>
Cleanup the Interface
Due to our changes in how creatures are handled, there are a number of facets of the interface that need some work. We'll start with the issues on the "Basics" tab.
The first thing we can't miss on the "Basics" tab is the display of XP information and the list of creation resources. Creatures don't track any XP and they don't observe any special creation rules regarding point allocations. Consequently, both of these sections of information and any associated separators need to be hidden for creatures. This can be handled easily by adding the block of code below to the Position script for the "basics" layout.
~if we're creating a creature, don't show XP or creation details if (hero.tagis[Hero.Creature] <> 0) then portal[baRank].visible = 0 portal[baCreation].visible = 0 portal[separator1].visible = 0 portal[separator2].visible = 0 endif
There is also the matter of the header above the table of attributes. The header shows the number of remaining points that can be spent, which is meaningless for creatures. We need to revise the header to simply show the text "Attributes" when the user is creating a creature. This is accomplished by changing the code for the HeaderTitle script to that shown below.
@text = "Attributes" if (hero.tagis[Hero.Creature] = 0) then @text &= " - " & hero.child[resAttrib].field[resSummary].text endif
We can now proceed to the "Skills" tab, where we'll find a few additional issues to address. For example, the header above the list of skills suffers the same problem that the one above the attributes did on the previous tab. The solution is the same, replacing the HeaderTitle script with the new code below.
@text = "Skills" if (hero.tagis[Hero.Creature] = 0) then @text &= " - " & hero.child[resSkill].field[resSummary].text endif
If we click on the "add" item at the bottom of the table of skills, we'll see a similar problem again. The title shown at the top of the selection form shows same extra information. We need to revise the Titlebar script in the same way, as shown below.
@text = "Add a Skill" if (hero.tagis[Hero.Creature] = 0) then @text &= " - " & hero.child[resSkill].field[resSummary].text endif
The final issue on the "Skills" tab is that the "add" item at the bottom of the table is using the highlight information for a normal character. This information is controlled via the "resAddItem" field of the resource. We already disabled the special formatting for NPCs, and we need to change that logic to apply to any non-PC character (i.e. including creatures). This is done by changing the one line of code in the Finalize script to the following.
if (tagis[Helper.NPCImpact] + !hero.tagis[Hero.PC] >= 2) then
Validation Rules
The first thing we'll notice regarding the validation report is that various resources will sometimes report that they have been overspent. These are the same resources that we disabled for NPC creation, and they must also be disabled for creatures. This can be solved by modifying the Eval Rule on the "Resource" component to consider the rule as valid for any non-PC. The new line of code required is shown below.
if (tagis[Helper.NPCImpact] + !hero.tagis[Hero.PC] >= 2) then
If the user elects to construct a creature, the specific creature type needs to be specified. We need a validation rule to verify this is done. We already a thing defined with a validation rule to verify a race is selected. We can easily clone that thing and adapt it for use with the creature type chooser, resulting in the thing shown below.
<thing id="valCreatur" name="Creature Type" compset="Simple"> <tag group="Helper" tag="Bootstrap"/> <evalrule index="1" phase="Validate" priority="8000" message="Must be selected"><![CDATA[ ~if we have a creature type selected, we're good if (hero.tagis[Creature.?] <> 0) then @valid = 1 done endif ]]></evalrule> </thing>
Unfortunately, we have a small problem still. If we create a normal character, we always get a validation error about the creature type. If we create a creature, we always get a validation error about the race. Each of these validation rules needs to be applied only when the appropriate character type is in use. One solution would be to utilize an appropriate ContainerReq on each of the validation things. An easier solution is to test whether we have a creature within the Eval Rule script. The revised script code for the "valRace" and "valCreatur" things is shown below.
~if we have a race selected or we are a creature, we're good if (hero.tagis[Race.?] + hero.tagis[Hero.Creature] <> 0) then @valid = 1 done endif
~if we have a creature type selected or we're not a creature, we're good if (hero.tagis[Creature.?] + !hero.tagis[Hero.Creature] <> 0) then @valid = 1 done endif
Output Revisions
The final detail we haven't addressed yet for creatures is output. Both character sheet and statblock output assume that every character will be assigned a race. We need to revise those mechanisms to handle the presence of a creature type instead.
Doing a scan through the data files, there are actually five different places where the race is being output. Each of these will need to be revised to accommodate creatures as well. This is a perfect opportunity for us to add a new procedure that can be shared by all these places. Unfortunately, we need different formatting in different situations, so a procedure will not simplify matters for us.
However, there is another solution we could use. If we add two new fields, we can setup those fields to contain the appropriate prefix and name to be used (e.g. "Race" and "Human"). For creatures, we can set the fields to use the proper info for the creature (e.g. "Creature" and "Goblin"). Once this is done, all five places can simply retrieve the fields and not have to do any special handling for creatures.
Our two fields must be added to the "Actor" component. We'll call them "acRacePref" and "acRaceName", and we'll need to define an Eval script on the component to synthesize them properly. In case we decide to use short names anywhere for races in the future, we need to schedule our script after Render/100, but we otherwise want to have our names in place very early in the Render phase. This yields the following field definitions and script below.
<field id="acRacePref" name="Race Prefix" type="derived" maxlength="10"> </field> <field id="acRaceName" name="Race Name" type="derived" maxlength="50"> </field>
<eval index="9" phase="Render" priority="1000"><![CDATA[ if (hero.tagis[Hero.Creature] <> 0) then field[acRacePref].text = "Creature" field[acRaceName].text = hero.firstchild["Creature.?"].field[name].text else field[acRacePref].text = "Race" field[acRaceName].text = hero.firstchild["Race.?"].field[name].text endif if (field[acRaceName].isempty <> 0) then field[acRaceName].text = "-none-" endif ]]></eval>
Now that the fields are being setup properly, we can go through and change all references to the race to utilize the new fields. We'll start with the statblock output. The Synthesize script assumes the use of a race, and switching it to the new fields results in the following revised block of script code below.
~output any race or creature type append @boldon & herofield[acRacePref].text & ": " & @boldoff append herofield[acRaceName].text & @newline
Switching our focus over to the character sheet, the same situation exists. The "oHeroInfo" portal assumes we always use a race on the first sheet, and the "details" portal for allies makes the same assumption. We'll revise the pertinent code for each Label script to look like the following.
~start with the character's race @text &= "{size 36}" & herofield[acRacePref].text & ": " & herofield[acRaceName].text
~output any race @text &= "{b}" & herofield[acRacePref].text & ":{/b} " & herofield[acRaceName].text
Three down. Two to go. Next up is the Eval script on the "Actor" component that synthesizes the "acRecap" field for display on the "Allies" tab. The code for outputting the race gets changed to the following.
~output any race recap &= field[acRaceName].text & ", "
Last on our list is the procedure that synthesizes the name for the display on the Dashboard and within the Tactical Console. The "DshBasics" procedure has its race handling code changed to what's shown below.
~output our race final = herofield[acRacePref].text & ": {b}" & herofield[acRaceName].text & "{/b}{br}{br}"
We can now reload our data files and verify that creatures are being handled properly within output everywhere.
Let User Add Abilities
We allow the user to customize creature traits from the defaults. It would optimal if we allowed the user to do the same for creature abilities. The first step in this process is to convert the table of abilities to be dynamic. We only want to show common abilities that are used across multiple creatures, so we'll leverage the "User.Creature" tag we defined earlier for this purpose. The changes are simple and result in the new portal below.
<portal id="abAbility" style="tblNormal"> <table_dynamic component="RaceAbil" showtemplate="SimpleItem" choosetemplate="SimpleItem"> <candidate>User.Creature</candidate> <titlebar><![CDATA[ @text = "Add a Monstrous Ability" ]]></titlebar> <headertitle><![CDATA[ @text = "Monstrous Abilities" ]]></headertitle> <additem><![CDATA[ @text = "Add Monstrous Abilities" ]]></additem> </table_dynamic> </portal>
This works great, but it doesn't handle abilities that need to be customized. For example, the "Immunity" ability needs to specify the nature of the immunity, while the "Size" ability needs to specify the rating adjustment. We need to let the user customize these abilities properly.
In order to do that, we need to identify the abilities that can be customized. We also need to identify how they can be customized. We can define a pair of tags for this purpose. The approach will be similar to the way that domains are handled, so we'll have one tag to identify when a value is needed and another to identify when text is needed. The two tags will be defined in the "User" tag group and should look like the following.
<value id="NeedText"/> <value id="NeedValue"/>
With the tags defined, we can go back to all of our abilities and flag them appropriately. The assumption we'll make is that no ability requires both a value and text - always one or the other.
We now need to expose the customization fields to the user within the table. This requires that we define a new template for the purpose. Since our needs a similar, we can clone the template used for skills and adapt it. We're going to need the usual "name", "info", and "delete" portals. In addition, we're going to need one edit portal for entering text and another for values, plus a label to show next to the edit portal. We'll use a Label script to tailor the label based on whether a value or text is required. For the name, we need to show the customized name if the pick is not user-added or the original thing name if we're showing the portals to customize the ability. In the Position script, we only show one of the two edit portals, and we only show it if the pick has been added by the user (i.e. can be deleted). This results in the template presented below.
<template id="abPick" name="Ability Pick" compset="RaceAbil" marginhorz="3" marginvert="2"> <portal id="name" style="lblNormal" showinvalid="yes"> <label> <labeltext><![CDATA[ if (isuser = 0) then @text = field[name].text else @text = field[thingname].text endif ]]></labeltext> </label> </portal> <portal id="label" style="lblSecond"> <label> <labeltext><![CDATA[ if (tagis[User.NeedText] <> 0) then @text = "Details:" else @text = "Rating:" endif ]]></labeltext> </label> </portal> <portal id="text" style="editNormal" width="100"> <edit field="abilText"> </edit> </portal> <portal id="value" style="editNormal" width="25"> <edit field="abilValue" format="integer" signed="yes"> </edit> </portal> <portal id="info" style="actInfo"> <action action="info"> </action> <mouseinfo/> </portal> <portal id="delete" style="actDelete" tiptext="Click to delete this item"> <action action="delete"> </action> </portal> <position><![CDATA[ ~set up our height based on our tallest portal height = portal[info].height ~if this is a "sizing" calculation, we're done if (issizing <> 0) then done endif ~position our tallest portal at the top portal[info].top = 0 ~determine whether our user fields are visible ~Note: If the pick cannot be deleted, it has been bootstrapped, so we assume ~ that no users fields can be specified portal[value].visible = 0 portal[text].visible = 0 if (candelete <> 0) then if (tagis[User.NeedValue] <> 0) then portal[value].visible = 1 elseif (tagis[User.NeedText] <> 0) then portal[text].visible = 1 endif endif if (portal[value].visible + portal[text].visible = 0) then portal[label].visible = 0 endif ~position the other portals vertically perform portal[name].centervert perform portal[delete].centervert perform portal[label].centervert perform portal[text].centervert perform portal[value].centervert ~position the delete portal on the far right perform portal[delete].alignedge[right,0] ~position the info portal to the left of the delete button perform portal[info].alignrel[rtol,delete,-8] ~position the name on the left portal[name].left = 0 ~position the label and edit portals to the right of the name perform portal[label].alignrel[ltor,name,15] perform portal[value].alignrel[ltor,label,3] portal[text].left = portal[value].left ~if the ability is auto-added, change its font to indicate that fact if (candelete = 0) then perform portal[name].setstyle[lblAuto] endif ]]></position> </template>
Unfortunately, our new template won't compile. The problem is that we can't associated "derived" fields with edit portals - only "user" fields are allowed. So we'll change the two fields to be "user" fields. However, this creates a new problem, as we can't assign values via bootstraps to "user" fields on unique picks. We could change all of our abilities to be non-unique, but then the user could assign the any ability multiple times, even when doing so is meaningless. That's not a good option.
We seem to be at a stalemate, so it's time to get creative. We need "user" fields to work with the edit portals, and we need "derived" fields to be assigned via bootstraps. It looks like the solution is to have both. We'll start by defining a pair of new fields on the "RaceAbil" component, as shown below.
<field id="abilUsrVal" name="User Value" type="user"> </field> <field id="abilUsrTxt" name="User Text" type="user" maxlength="25"> </field>
Once we hook up the edit portals to these two fields, the compiler is happy. We can reload our files and actually enter values for abilities that we add. Now we need to connect our "user" fields with the "derived" fields that are used by the Eval scripts on the various abilities.
We can accomplish this by defining an Eval script on the "RaceAbil" component. This script will copy the contents of the "user" fields into the corresponding "derived" fields. Since "user" fields are always in place from very beginning, we'll schedule the script to occur early in the evaluation cycle, ensuring that the "derived" fields contain the proper values when accessed. The one thing we need to be careful of, though, is that we must not copy the field values if the user did not add the pick. If we do, then we'll overwrite the values assigned via the bootstraps with empty values, since no user values will exist for those abilities. Putting this all together yields the Eval script shown below.
<eval index="3" phase="Setup" priority="5000"><![CDATA[ ~if this pick was NOT user-added, no user fields can be accessed if (isuser = 0) then done endif ~if we have a user value, put it into the derived field if (tagis[User.NeedValue] <> 0) then field[abilValue].value = field[abilUsrVal].value ~if we have user text, put it into the derived field elseif (tagis[User.NeedText] <> 0) then field[abilText].text = field[abilUsrTxt].text endif ]]></eval>
With the script in place, we can reload the data files and everything works the way we want it. We can easily mix and match bootstrapped abilities with user-added abilities, and the user can properly customize any abilities that he adds.