Complex Vehicles (Savage)

From HLKitWiki
Revision as of 20:08, 12 January 2009 by Colen (Talk | contribs) (Oops! We've Got a Problem)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Context: HL KitAuthoring Examples … Savage Worlds Walk-Through 


Now that all the basics of vehicles are in place, we can look at how to support more complex vehicles, such as military vehicles with weapons. Each such vehicle needs to be managed as a user-selectable pick, plus the weapons need to be associated directly with each vehicle. We also need to handle the appropriate display of the weapons for each vehicle, which can be handled a few different ways.

Assigning Equipment to Vehicles

The biggest wrinkle posed by complex vehicles is how to define the various equipment separately (e.g. weapons) and then associate that equipment with the vehicle. Your first thought might be to use a bootstrap to associate the equipment, but that won't work. The vehicle is assigned to the character, so any things bootstrapped by the vehicle will also be assigned to the character. What we need is to treat each vehicle as its own container, into which the various equipment picks can be added.

In order to accomplish this, we must use a entity. When the entity is added to the character, it becomes a gizmo, which is a separate container. We can then add the various equipment picks into the gizmo. Entities are defined separately and then added via a thing. When the thing is added to the character as a pick, the associated entity is automatically added as a gizmo.

So our first task is to define an entity that we can use with vehicles. When we define the entity, we can automatically assign things into the entity, which will be added to every gizmo that derives from the entity. However, every vehicle is unique and there is nothing that must exist for every vehicle. We can also assign tags to every entity that will always be assigned to every derived gizmo, but we don't need any of those either.

This leaves us with an incredibly simple entity. The entity is merely a shell that will be separately customized for every vehicle. Since the entity is used to contain the various equipment possessed by each vehicle, we'll refer to it as the "load-out" for a vehicle and name it accordingly. This results in an entity definition that looks like the one below, which we can define at the bottom of the file "equipment.str".


Putting the Entity to Use

Now that we've got an entity, we need to put it to use. To demonstrate how this works, we'll define a the "A6M Zero" aircraft that is presented in the core rulebook. We start with the basic details for the vehicle, which should look like the following.

  name="A6M Zero"
  description="Description goes here">
  <fieldval field="grCost" value="0"/>
  <fieldval field="vhAccel" value="20"/>
  <fieldval field="vhTopSpeed" value="140"/>
  <fieldval field="vhTough" value="12"/>
  <fieldval field="vhArmor" value="2"/>
  <fieldval field="vhCrew" value="1"/>
  <fieldval field="vhCost" value="0"/>
  <usesource source="TimeModern"/>
  <tag group="VehType" tag="Aircraft"/>
  <tag group="VehEra" tag="WWII"/>
  <tag group="thing" tag="holder_top"/>
  <tag group="User" tag="Military"/>

The Zero has two pairs of weapons in its armament. It has two 7.7mm machine guns and two 20mm cannons. So we next need to define the two weapons appropriately. These should look like is shown below.

  name="7.7mm MG"
  description="Description goes here">
  <fieldval field="wpDamage" value="2d8+1"/>
  <fieldval field="wpShort" value="24"/>
  <fieldval field="wpMedium" value="48"/>
  <fieldval field="wpLong" value="96"/>
  <fieldval field="wpPiercing" value="2"/>
  <fieldval field="wpFireRate" value="3"/>
  <fieldval field="wpShots" value="500"/>
  <fieldval field="wpAmmo" value="7.7mm"/>
  <usesource source="TimeModern"/>
  <tag group="Equipment" tag="Natural"/>

  name="20mm Cannon"
  description="Description goes here">
  <fieldval field="wpDamage" value="3d8"/>
  <fieldval field="wpShort" value="50"/>
  <fieldval field="wpMedium" value="100"/>
  <fieldval field="wpLong" value="200"/>
  <fieldval field="wpPiercing" value="4"/>
  <fieldval field="wpFireRate" value="3"/>
  <fieldval field="wpShots" value="60"/>
  <fieldval field="wpAmmo" value="20mm"/>
  <usesource source="TimeModern"/>
  <tag group="Equipment" tag="Natural"/>
  <tag group="Weapon" tag="HvyWeapon"/>

We can now assign the load-out entity to the vehicle. Once we have the entity to contain the weapons, we can then assign the weapons into it. This is accomplished by bootstrapping the weapons into the entity, as opposed to bootstrapping them onto the vehicle. So we add the following material to our Zero, which adds the entity and then puts the two weapons into it.

<child entity="LoadOut">
  <bootstrap thing="vw77MG">
  <bootstrap thing="vw20mm">

That's all there is to it. The two weapons now reside within the entity and behave as children of the vehicle itself.

Oops! We've Got a Problem

Go to the "Gear" tab and add a vehicle. Our new Zero shows up nicely in the list of vehicles. However, the moment that we add the Zero to the character, HL presents us with error messages. It tells us that the "live state" of the gizmo is being tested before the live state of the parent pick is resolved. Aside from that error, everything appears to be working correctly, so we need to figure out what's going wrong.

The error message is being shown twice. Curiously, we have two weapons that we are bootstrapping into the gizmo. And the gizmo is the source of the error. It turns out that we've introduced an interesting timing issue within our data files. In order to fix this, let's look a little more closely at how things work within the HL engine.

All picks have a "live" state. If the pick is "live", then it behaves normally. If it is non-live, then it is treated as if it doesn't exist within the character. Any scripts that are defined for the pick are ignored, and any scripts that try to access to the pick (e.g. to pull a value from it) report a run-time.

The live state is resolved separately for every pick. It can also be resolved at a completely different time for each pick. Depending on how the data files are structured, one pick could have its live state resolved during the Initialize phase at priority 1000 and another pick could have its live state resolved during the Final phase at priority 6000. The key requirement is that all tests governing the live state for a given pick must be performed before any scripts (for example, eval rules or eval scripts) are invoked that manipulate that pick.

There can be multiple tests governing the live state for a particular pick. In addition, the use of Secondary and Existence tag expressions (assigned to picks added through particular tables) introduces situations where additional tests are added dynamically to picks, based on how and where they are added to the character. This can make things somewhat complex for an author to keep track of, so the HL engine does what it can to simplify the situation. This amounts to resolving the live state at the latest possible time for each pick. The live state for a pick is resolved immediately before the very first script (whether a component script or a thing script) is scheduled on that pick. This ensures that the window of opportunity for scheduling Secondary and Existence tag expressions on the pick is maximized without introducing timing problems.

Of course, there are exceptions to every rule. For this particular behavior, the exception is for picks that attach gizmos or minions. Such picks always have their live state resolved immediately after the last test governing their live state - i.e. as early as possible. However, if a pick with a gizmo or minion does not possess any tests governing its live state, then its live state is resolved as normal, just before its first script.

When gizmos and minions are employed, the gizmo/minion is only live if the pick attaching it is live. And if the gizmo/minion is non-live, then none of the picks within the gizmo/minion will be live. Consequently, the live state of a pick within a gizmo must first verify that the pick attaching the gizmo is also live. This means that the live state of the pick attaching the gizmo must be resolved before the live state of picks within the gizmo are resolved.

With all that in mind, let's look back at our current problem. The error is saying that the live state of the gizmo is being tested before the live state of the pick that attaches the gizmo has been set. The live state of the gizmo is being tested by the weapon picks within it. So we need to analyze when the live state is being verified for the weapons and make sure that the live state is resolved prior to that time for the pick that attaches the gizmo.

We'll start by determining when the live state is being verified by the weapons. The weapons are always live, so the live state of the weapon picks is being resolved at the timing of their earliest script. Go the "Develop" menu and into the "Floating Info Windows" sub-menu, then select the "Show Task List (Active Hero)" option. Enlarge the window so you can see the list of tasks that are scheduled for the character. At the very top, you'll see "Pick Condition" entries for the weapons on the Zero, immediately followed by Eval scripts on those weapons. The "Pick Condition" task is the one that resolves the final live state for a pick. Just as we expected, the live state for the weapons is being resolved immediately before the first script on those weapons. This is occurring in the Initialize phase at priority 1000.

Now let's review the timing for the vehicle itself - the pick that attaches the gizmo. Just like with the weapons, there are no tests governing the live state for the vehicle. This means that the live state for the vehicle is being dictated by the timing of the first script on the vehicle. Scrolling down through the task list, we'll find a "Pick Condition" test for the "A6M Zero" pick at a timing of Setup/10000. Just below this task, we'll see that the first Eval script for the vehicle is scheduled at the same timing.

So now we understand why the problem is occurring. The next step is to figure out how to solve it. One solution would be to move the timing of the weapon scripts to later. Another solution would be to move the timing of the vehicle scripts to earlier. However, both of these approaches could lead to a domino effect with the timing of other scripts and behaviors, so we should only change them if we have no other choice.

Let's look back at the logic used by the engine for picks that attach gizmos and minions. For those two situations, the live state is resolved immediately after the last test governing the live state. There is currently no such test for the vehicle, because none is needed, but this is the reason the vehicle's live state is resolved so late. But if we did have a live test that occurred before Initialize/1000, then the live state would be resolved then and all of our problems would disappear.

So the solution is to add test of the live state that always succeeds for the vehicle and schedule it prior to Initialize/1000. This can be accomplished by defining a ContainerReq tag expression on the Vehicle component with an expression of "TRUE" (meaning the test will always succeed). Since we now have a test of the live state on the vehicle, the live state will be resolved immediately after this test is performed, and the timing errors will go away. You can add this to the component within the file "equipment.str", and it should look like the following.

<containerreq phase="Initialize" priority="900">TRUE</containerreq>

Once the change is made, try reloading the data files and add the Zero to the character. No errors appear. If you look at the task list, you'll see the new "Component Condition" test scheduled at Initialize/900, followed by the "Pick Condition" test scheduled right after it. The "Pick Condition" tests for the weapons follow at Initialize/1000, but everything has been safely resolved, so no error is reported.

Modify the Description Procedure

Let's take this opportunity to revise the "InfoVeh" procedure that synthesizes the details for each vehicle. We need to add the particulars the weapons within the gizmo. So open up the file "procedures.dat" and locate the procedure.

The first thing we need to do is differentiate between a vehicle that possesses a load-out entity and one that doesn't. Basic vehicles don't need the entity, so we don't define one for them. This means that we need to avoid trying to access a non-existent entity for vehicles that lack them. Within our script, we check if an entity exists and then bail out if we don't have one. The code should look something like the snippet below.

~if there is no child entity/gizmo, then there's nothing more to do
if (isentity = 0) then

The next thing we need to do is report the basic load-out for the vehicle based on the contents of the entity. We want to identify each piece of equipment within the entity, list each along with the pertinent characteristics of the equipment. This can be achieved through the use of a "foreach" statement.

At this point, we run into an important distinction. Accessing the contents of a entity associated with a thing is very different from accessing the contents of gizmo beneath a pick. This is because the entity has not been added to the character yet, and that imposes some significant limitations on what can be accessed. We must use two completely different methods for accessing the contents of entities and gizmos, although the basic logic can be similar.

The problem we face is that the "wpNotes" field for the weapons is synthesized via an Eval script. No scripts are invoked for things, so we will have to synthesize all the information manually if we want to display it for things. In addition, we're going to run into some additional limitations associated with customizing things within the entity in just a moment. Since some weapons will be re-used with slight differences (e.g. ammunition quantities), we'll want to override the values of various fields via the bootstrap, but that information isn't accessible on the thing - only once the weapon is actually added to the gizmo as a pick. So we need to re-think our approach at this point.

Handling Display of Entities

Let's reconsider exactly what information we need to display to the user during selection of vehicles. The load-outs for each vehicle in Savage Worlds is fixed, and the number of vehicles is not exhaustive. Consequently, subtle differences between the load-outs of two vehicles is not going to be a serious consideration for players when selecting vehicles. That means that we really just need to identify the weapons and equipment for each vehicle by name during selection, without displaying all of the weapon characteristics. That information will be useful during the game, but not important during selection.

Given this situation, the best solution is probably to simply add a new field to each vehicle that lists equipment comprising the load-out. This field can be displayed within the description for the vehicle during selection and ignored when we have full access to the gizmo contents. So we'll define a new "vhLoadout" field for exactly this purpose, which should look like the field specification below.


Return to the Description Procedure

Now that we have the issue of load-outs resolved during selection, we can get the description procedure properly into place. If we have a thing, we will simply output the "vhLoadout" field for the vehicle. If we have a pick, then we'll go through the contents of the gizmo and properly synthesize all of the appropriate details.

The net result is code that looks like below. This new logic can be added at the end of the "InfoVeh" procedure, after we've verified that we indeed have an entity for the vehicle.

~if this is a thing, report the basic load-out for the vehicle based on the field;
~otherwise, synthesize any load-out for the vehicle as a pick appropriately
~Note: We must differentiate between a thing and a pick when accessing the entity,
~       as we want summary details for a thing and complete details for a pick
var loadout as string
if (ispick = 0) then
  loadout = field[vhLoadout].text & "{br}"
  loadout = ""
  foreach pick in gizmo
    loadout &= "{horz 10}" & chr(149) & " " & eachpick.field[name].text & ": "
    loadout &= eachpick.field[wpDamage].text & " - " & eachpick.field[wpRange].text
    if (eachpick.field[wpNotes].isempty = 0) then
      loadout &= " - " & eachpick.field[wpNotes].text
    loadout &= "{br}"

~if there is any load-out for the vehicle, output it
if (empty(loadout) = 0) then
  iteminfo &= "Weapons/Equipment:{br}" & loadout

Refining the Entity

Let's return at our Zero now. We need to specify suitable text for the new "vhLoadout" field. For clarity, we'll put each weapon on a new line and indent it. This yields the following contents for the field.

<fieldval field="vhLoadout" value="   2x 7.7mm MGs{br}   2x 20mm Cannons"/>

Now we'll take a look at our Zero within HL. Go to the "Gear" tab and add a vehicle. Our Zero should be listed, and it should show the load-out equipment properly. Add the Zero to the character, then check the mouse-info text for the vehicle. The display is different, since the Zero actually has two of each weapon that is listed. We need to fix this.

Before we continue, let's take a look at various other vehicles and their load-outs. Some weapons are re-used for different vehicles, which is great, but different ammunition quantities are specified. So we have to handle that. In addition, some vehicles simply re-use weapons and tweak them slightly. We can either implement lots of different weapons that are nearly identical or customize each weapon when it is added to the vehicle.

All of the adjustments can be most easily handled by customizing each weapon via the "bootstrap" element that adds it to a vehicle. Each bootstrap element can possess optional "assignval" child elements, which allow you to override the value of individual fields on the child pick. We can use "assignval" to modify the name, ammunition quantity, or any other facet of individual weapons.

In order to override the values of fields, those fields must be able to be overridden. This requires that a field be designated as the "derived" type. However, the majority of the fields used on weapons are "static". So we need to identify the various fields that we'll want to override and change them from "static" to "derived". After looking through the various vehicles in the core rulebook, the list of fields we need to change include: "wpPiercing", "wpFireRate", "wpShots", and "wpAmmo". Open the file "equipment.str" and modify each of these fields to the new type.

Once this is done, we can customize the weapons that are bootstrapped into our Zero appropriately. For each weapon, we can tailor the name via the "livename" field and we can tailor the ammunition quantity via the "wpShots" field. This results in a revised "child" element for the Zero that looks like below.

<child entity="LoadOut">
  <bootstrap thing="vw77MG">
    <assignval field="livename" value="2x 7.7mm MGs"/>
    <assignval field="wpShots" value="500"/>
  <bootstrap thing="vw20mm">
    <assignval field="livename" value="2x 20mm Cannons"/>
    <assignval field="wpShots" value="60"/>

Reload the revised data files and add a Zero to the character again. It will now display its load-out details with appropriate names and accurate ammunition quantities.