Integrated Editor Support (Savage)
Our data files are basically complete. Our focus now shifts to the final steps necessary to prepare the data files for release to others. The first of these final steps is setting things up so that users can leverage the integrated Editor provided within HL.
Most gamers will have their own custom tweaks that they employ within their gaming group or that are unique to a particular game world. The goal is to enable them to easily add that material. You've invested quite a bit of time in creating your data files, so spending a small amount more to maximize the ability of others to use your files is a worthwhile investment.
Using the Editor
We assume that you're at least moderately familiar with using the Editor for a different game system. If you haven't done so yet, take the time to review the documentation for one our licensed game systems and familiarize yourself with how the Editor works. After a little bit of experimenting, you should be able to appreciate how much simpler the Editor is to use than manually editing XML files. Our new objective is to wrap our data files up in a way that allows users to quickly and easily create new content via the Editor.
Internally, the Editor is driven by an assortment of "editthing" XML elements. Every "thing" is derived from a component set. Every type of thing that you want users to be able to create must have an "editthing" defined for it. Consequently, you'll need to define a separate "editthing" for every component set that users can create things for.
Within each "editthing", you must define one or more "inputthing" elements. Each "inputthing" corresponds to exactly one specific facet of a particular thing. For example, the value of a field is controlled via an "inputthing". So is whether a particular tag is assigned to the thing.
In an effort to keep everything as simple as possible for users, the Editor uses only a handful of simple mechanisms. These include checkboxes, picking an option from a list, entering a value, and a few others. Each "inputthing" specifies the exact user-interface mechanism to be used for customizing a single facet of the thing being edited. Along with the mechanism, it must also provide the internal details for how to map the visual mechanism to something concrete in the data files.
Let's look at an example. Skills can optionally require the user to specify a domain. Internally, this detail is controlled for skills via the "User.NeedDomain" tag. Within the Editor, we want the user to simply specify whether the skill requires the domain or not. The best way to handle an either-or situation like this is by showing the user a checkbox. So we would define an "inputthing" that uses the "tagcheck" mechanism. This mechanism presents a checkbox to the user and assigns a tag to the thing based on whether the checkbox is checked. If we specified the "User.NeedDomain" tag for the "tagcheck", that tag would be added to the thing if the checkbox is checked and omitted if unchecked.
The contents of an "editthing" are primarily just a list of "inputthing" elements. The order of the "inputthing" elements dictates the order in which everything is presented to the user within the Editor. This gives us complete control and allows us to incrementally get our data files to fully utilize the Editor. In the sections below, we'll be looking at this process in detail, with concrete examples. Through the process, you'll be exposed to most of the different "inputthing" mechanisms and how to use them.
Use Tags Whenever Practical
Before we start implementing Editor support, we need to first address an important detail. The majority of the "inputthing" mechanisms center on the use of tags. The reason for this is that tags provide a critical benefit that simple values and text fields do not. Tags offer a restricted set of inputs that the user can select from.
In most situations, the data files for any game system will be designed around various small groupings of options. In Savage Worlds, all attributes and skills use a die type that ranges from "d4" to "d12". Hindrances can be either minor or major in severity, thereby having a cost of 1 or 2 points. The character's rank ranges through five levels, and edges can specify a dependency on the rank level. The list goes on and on.
If we present the user with an empty field to enter a value into, we have no way of knowing that the user entered something valid. For example, if we present a field for the user to specify the minimum strength requirement for a ranged weapon, there is nothing to stop the user from entering a value of 42. Even if the user means well, the question arises whether he should be entering a value of 10 for a "d10" or a value of 5 to correspond with the technique we use internally. However, if we present a list of tags containing only the five valid die types, there is no opportunity for confusion and no opportunity for error.
There are still a variety of places where an arbitrary text or value field is exactly what we need. However, we will strive to use them only when they are truly appropriate. Keep this in mind as you're working on your own data files. We'll be running into a few situations below where we need to change our data files to leverage tags instead of fields.
The final thing we should do before starting on our implementation is take inventory of our data files. We need to go through all the different files to identify which component sets we want to expose to users within the Editor. In general, this list should consist of any objects that we expect users will want to customize for their own games. Once we have the list culled out, we should then organize it into a reasonable sequence that will be easiest to implement.
The list we can identify for Savage Worlds is presented below, showing the component set id and the corresponding group of objects the user will be operating upon. With this list, we have a clear path for getting full Editor integration operational.
- Attribute - Attributes
- Skill - Skills
- Trait - Derived Traits
- RaceAbil - Racial Abilities
- Edge - Edges
- Hindrance - Hindrances
- Reward - Hindrance Rewards
- Equipment - Mundane Items
- Melee - Hand Weapons
- Ranged - Ranged Weapons
- SpecWeap - Special Weapons
- Armor - Armor
- Shield - Shields
- Vehicle - Vehicles
- Arcane - Arcane Backgrounds
- Power - Arcane Powers
- Drawback - Arcane Drawbacks
- Injury - Injuries
- Race - Races
- Creature - Creatures
- Simple - Simple (used for validation rules and such)
- Mechanic - Mechanics (used for auto-bootstrapped behaviors)
Since we want to utilize tags whenever possible within the Editor, we need to go through each of the identified component sets above and look for places where we must make revisions. There are a total of four changes we need to make to the mechanics to streamline Editor integration.
The first change we'll notice is within the "Edge" component. The "edgIsWild" field is a simple "yes" or "no" situation. As such, it must be changed to the use of a tag, with the tag indicating a wildcard is required. To handle this, we'll define a "User.NeedWild" tag. We can then delete the field and go through all edges to assign the tag whenever the edge requires a wildcard. The final modification is within the "prereq" element on the "Edge" component, where the test of the field value must instead test the presence of the tag.
The second change is within the "MinRank" component, which will impact both edges and arcane powers. The minimum rank is specified via a field value, but it should be handled via a tag. We'll define a new "MinRank" tag group with tags corresponding to each rank level, as shown below.
<group id="MinRank"> <value id="0" name="Novice"/> <value id="1" name="Seasoned"/> <value id="2" name="Veteran"/> <value id="3" name="Heroic"/> <value id="4" name="Legendary"/> </group>
With the tag group in place, we can delete the field and then tweak the "prereq" element on the component so that it checks for the tag instead of the field value (just like we did above). We can then go through all edges and arcane powers, converting all use of the "rnkMinRank" field over to the appropriate "MinRank" tag.
The third change is required within the "Hindrance" component. The "hinMajor" field has a simple "yes" or "no" behavior, so we can change it to key on the presence of a tag. We'll define a new "User.HindMajor" tag that indicates a hindrance starts out as a major one. Then we can go through all hindrances and assign the tag whenever a hindrance starts out as major.
This situation is a bit different from the wildcard field, though. The user can select the severity for some hindrances. Consequently, we cannot delete the field, since we need it to store the user-selected severity rating. What we'll do instead is configure the initial state of the field based on the presence of the tag. Since the field is user-modifiable, we can't do this in an Eval script, because we would end up clobbering the user-assigned state with our script every time. Instead, we'll handle it within a Creation script, since we can setup the initial state in the Creation script and then allow the user to adjust the field thereafter. Our script should look like below.
<creation><![CDATA[ field[hinMajor].value = tagis[User.HindMajor] ]]></creation>
The final change we need to make is with weapons. The minimum strength requirement is currently handled via a field value.
"Hindrance" component - "himMajor" field must be dictated by a tag
"WeapMelee" component - need "StrReqDie" tag to indicate minimum strength required when there is no weapon die
"WeapRange" component - use "StrReqDie" tag
- 69 - add User.NeedWild tag
- 70 - add User.HindMajor tag
- 481 - add MinRank tag group
- 556 - add StrReqDie tag group
- 353 - check User.HindMajor
- 460 - user StrReqDie tag instead of field
- 617 - Creation script
- 682 - Delete "edgIsWild" field
- 722 - check "User.NeedWild" tag instead of field
- 490 - "wpStrReq" changed to "derived"
- 637 - add Eval script to convert "StrReqDie" tag to "wpStrReq" field
- 658 - use tagvalue of "StrReqDie" tag instead of field value
- 639 - delete "rnkMinRank" field
- 646 - use tagvalue of "MinRank" tag instead of field value
- change "rnkMinRank" field to "MinRank" tags
- change "hinMajor" field to "User.HindMajor" tag
- change "rnkMinRank" field to "MinRank" tags
- change "edgIsWild" field to "User.NeedWild" tag
- change "wpStrReq" fields to "StrReqDie" tags
- delete defunct "User" tags