Dungeon Siege University Compilation

Siege University Compilation
October 2002

Siege University 100:
100: The Basics of the Siege Editor
Siege University 200:
200: Dungeon Siege Concepts and Terminology
201: Templates
202: Lorebooks
203A: Triggers I
203B: Triggers II
204: Moods
205: Fades
206: Elevators
207: Non-Interactive Sequences
208A: Non-Player Characters
208B: Talk Skrits
209: Quests
210: Sounds
211: Naming Key
212: SiegeFX
213: Dungeon Siege Resource System
214: Siege Max: Characters
215: Siege Max: Objects
216: Siege Max: Terrain
217: Actor & Party Commands
Siege University 300:
301: Introduction to Dungeon Siege Architecture
302: Teleporters & Displacers
303: Skrit
304: Deep Modding: .dsdll extensions

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_100 Tutorial

The Basics of the Siege Editor

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

What This Tutorial Will Teach You

Opening An Example

Basic Movement Controls Within SE

101: Nodework

Create A Map

Create A Region

Add Floors And Walls

102: Objects

Add Details To House

Add Details To Surrounding Farmland

Add Starting Positions

103: Lighting

Ambient Light

Directional Lights

Point Light

104: Fading

Node Camera Flags

Fading Nodes With Triggers

105: Moods

106: Monster Placement

107: Opening Your Map in Dungeon Siege

Conclusion

Appendix A: Tips

Appendix B: Siege Editor Preferences

Introduction

Building a region for Dungeon Siege using the Siege Editor ("SE") is a straightforward task, even for a beginning mod maker. This Siege U tutorial has two purposes: familiarization with the Siege Editor and familiarization with Siege University. At the conclusion of this tutorial, you will be familar with basic SE functionality and will possess an understanding of how Siege U works.

This tutorial will walk you through building a simple, small region. It will not delve into all the options of each section, nor will it offer much more than a cursory explanation of the motives of building a region. These topics are covered in further detail in the Siege Editor Manual (included during the installation of the Siege Editor) and further, more specific Siege U tutorials.

What You Need For This Tutorial

  • Dungeon Siege, updated to version 1.09B or later
  • Siege Editor, with the Preferences configured to Appendix B
  • Siege Editor Manual (included with the Siege Editor during installation)
  • Siege U 100 companion map

What This Tutorial Assumes You Have Already Learned

  • Nothing

What This Tutorial Will Teach You

  • Basic familiarization with SE
  • Node Placement
  • Object Placement
  • Lighting
  • Fading
  • Moods
  • Monster Placement
  • Tanking/untanking maps for play in Dungeon Siege

Opening An Example

The best way to start is to first take a look at a sample map before beginning this Tutorial. Download the Siege U 100 companion map and extract it according to the DSReadme.rtf included with it.

Once you have the companion map installed on your computer, you will need to "untank" it. When working on a map in SE, you are working on the "bits"; that is, the changes you make are stored in different files in different locations (all within My Documents\Dungeon Siege\Bits). However, for performance reasons and size constraints, these files are compressed into a single "tank" file for use within Dungeon Siege.

Any time that you want to work on a map in SE, you need to edit the bits.
Any time that you want to open a map in DS, you need to "tank the bits" and create a .dsmap file for DS to use.

Fortunately, most of the conversion work is only one-way -- from SE to DS. That is, once you have started working on bits, you only need to tank when you are ready to test your regions. Once you have tested them and are ready to continue editing, you do not need to untank the map; you can just continue editing the same bits. The only time that you need to untank a map is when you do not already have the current bits on your computer (which would happen if you download a new map).

To untank the companion map into its editable format, follow these steps:

  1. Start the Siege Editor.
  2. Select Convert .dsmap to Files... from the File Menu.
  3. For the Source File, browse to where you installed Tutorial.dsmap (the default is the Maps folder in the Dungeon Siege game directory).

  4. For the Destination Folder, leave it at its default setting (My Documents\Dungeon Siege\Bits)
  5. Click OK to start the process.

  6. Once the conversion process is complete, you will see the results with a success message.

  7. Select Open... from the File Menu.
  8. Expand the gpg_cgm map on the Load Region dialog, and select the region named tutorial.
  9. Click OK.

Basic Movement Controls Within SE

All actions and movement in SE are controlled with the mouse. Please note that a three-button, scroll-wheel mouse is required for the Siege Editor to function properly with it's default key settings.

  • To move around a map, right click and hold, then move the mouse in the desired direction.
  • To zoom, scroll the mouse wheel.
  • To move vertically, hold down shift while right clicking and moving the mouse, or hold down both the left and right mouse buttons while moving the mouse.
  • To rotate the region, click and hold the middle mouse button, then move the mouse.

Now that you have seen the end result, let us walk through building a copy of this region together. The region you build does not have to match the example identically, so don't worry about switching back and forth to compare regions.

101: Nodework
<Back to Table of Contents>

Assembling terrain is the most important aspect of designing regions for Dungeon Siege. By snapping together some of our 3,800 different nodes, you can create almost any type of environment imaginable -- from vast, rolling plains, to the deepest vertical dungeons, and everything in between.

Create A Map

  1. Choose New Map from the File Menu. [default hotkey = Ctrl+M]
  2. Enter a name for the map. This name will not be visible within Dungeon Siege and cannot contain any spaces and must be in lowercase. For example, my_world.
  3. Enter a screen name. This is the name of the map you will see within Dungeon Siege. For example, My World.
  4. Enter a description of the map. For example, This is my SU 100 map.
  5. Leave everything in the Advanced section as is.
  6. Click OK.

Create A Region

  1. Choose New Region from the File Menu. [default hotkey = Ctrl+N]
  2. From the drop down under Select Map to Create Region, choose the map you just created.
  3. Enter a name into the Region Name box. For example, my_house.
  4. In the Advanced section, click on the Default buttons for both Scid Range and Region ID. The Scid Range and Region ID settings must be unique for all regions in the map. Accepting the defaults ensures that no numbers will be duplicated in the map.
  5. Select the Target Node (starting node) you will build the world around:
    a. Expand the grass_1 directory and scroll down to the houses folder.
    b. Expand the houses directory and select the node "t_grs01_houses_generic-b-log.sno."

  6. Click OK.
  7. Click on the floppy disk icon in the upper left toolbar to bring up the Save dialogue. Make sure all options are checked and click Save. [default hotkey = Ctrl+S]

  8. Your region will now look like a house sitting in black space.

Add Floors And Walls

  1. Click on the Custom View tab at the bottom found at the lower left side of SE.
  2. Expand the tree to CGM_Afterlife_Tutorial/Nodes.
  3. Select "t_xxx_flr_08x08-v0.sno" (floor node).

  4. Click on the house node and use the Source Doors: arrow buttons located at the lower left side of the editor to rotate through the node’s doors until you get to Door 1. You will see a red line on the door in the Main Window, with an arrow pointing away from the node. [default hotkeys = a, d]
    Note: The term "door" (as used in SE) refers to a connectable edge of a node.
  5. Use the Destination Doors: buttons to rotate the floor node (t_xxx_flr_08x08-v0.sno) so Door 6 of the floor node is connected to Door 1 of the house node. [default hotkey = w]

  6. Select the floor node you just added and rotate through its doors until Door 3 is highlighted.
  7. With "t_xxx_flr_08x08-v0.sno" still selected in the Custom View panel, click Attach Node to add another floor node so Door 6 of the new node is connected.

  8. Snap more floor nodes around the house node by rotating to unconnected doors and adding nodes as described above. Be careful not to overlap one node over another. Node doors can only connect to one other door, so if you overlap nodes, your region will not connect correctly and has the potential to crash or display strange behavior.
  9. Save after placing every few nodes. When you save, it also connects all node doors that are adjacent to other nodes. Important note: After the target node has been placed (which is the house in this tutorial), all new nodes must connect to other nodes. If deleting a node cuts off several nodes from the target node, they will automatically be deleted. To avoid deleting an entire string of nodes, never delete a node unless you have just saved.
  10. You should now have a house in the center of a grassy area.

  11. Add a field to the right side of the house. Select "t_grs01_field-rows-corner-b.sno."
  12. Rotate the field-row corner so Door 2 connects to the terrain and the textures match up.
  13. Continue adding field-row nodes, using all of the variations in the folder. Create a field that is three field-row nodes wide and six field-row nodes long.

  14. Add a Chicken Coop and Well. First select "t_grs01_houses_coop-a.sno" and add it next to one of the field-row edge nodes, then select "t_xxx_flr_well-a.sno" and add it to the other side of the grassy terrain.

  15. Continue building out the grassy terrain around the house, field, and coop using the available floor node variations.
  16. Add rock cliffs around your farm to make it into a valley. Choose from any of the wall ("wal") type nodes (ex: t_xxx_wal-12-thck) and add them to the edges of the region so Doors 1 and 2 always connect to the ground level.
  17. Use the corner ("cnr") type nodes to change Wall direction by snapping them to unconnected wall sides.

  18. Save.
At first glance, the generic node naming conventions might seem a bit unintelligible. However, they actually can be decoded into useful information. With any node set, there are at least five different subsets separated by underscores that make up each node's name.

Let's look at the name of the generic node, "t_xxx_cnr_12-ccav.SNO," found in Terrain Nodes/Generic/Corner. The first part of the name, "t," refers to "terrain." The second part of the name is the set identifier. For example, "xxx" indicates the node is part of the generic set and has a swappable texture. (A swappable textures means that you can select a node, and then go to Node | Current Node Set to change the texture of the node to a different texture set.) If this part was something different, such as grs01, it would tell us that the node belongs to the Grass_1 set and has a grassy-looking texture. Following the set identifier, is the node type. For example, there are fifteen different node types that make up the Generic set, plus water versions for all of them. "Cnr" tells us that it's a Corner type node. If it was something else, for example, "for," we would know that it's a Forest type node, and so on. Usually, the next part of the name refers to the node's size (for nodes that have multiple sizes). "12" means that this node is 12 meters high, so any nodes we want to connect to it must also have a "12" somewhere in their name.

At this point, the basic properties of the node have been identified, and all further information defines variation. "-ccav" means this node has an inward curve, or is concave. If it was "-cnvx," then the node would have an outward curve (convex). Both corner types allow you to turn a wall ninety degrees. The ".SNO" is the filename extension, and means "Siege NOde."

102: Objects
<Back to Table of Contents>

Populating your world with objects is fast and easy. Within Siege Editor, an object is any model that is not terrain. This part of the tutorial will deal with "non-interactive objects", which are decorative objects that do not contain AI or animation components, and cannot be selected by the player in-game. Objects, in general, are far more flexible than terrain. They can be scaled, rotated, and placed with great precision. The key to good object placement is using enough to make an area appear lush and realistic, while minimizing the amount of different objects used, so performance remains reasonable.

Before you can add and move objects, you must be in the correct mode. Under the Object Menu, look at the options under Mode. "Placement Mode" must be checked to add an object to your region. "Movement Mode" must be checked to move an object within your region. Placement Mode has a button that appears on the Object Toolbar, since you will only want to be in Placement Mode when you are ready to add an object. If you remain in Placement Mode all the time, you will add objects when you try to simple click-select an existing object.

Add Details To House

Decorate the house with objects that make it look realistic and lived-in.
  1. Rotate the view until you are looking straight down into the house.
  2. Expand "cgm_afterlife_tutorial/objects/house".
  3. Select "rug_round_blue."
  4. Click on the center of the house floor to place the rug.
  5. To move the rug after it is placed, click on the rug to select it (a green selection box will appear around the rug when it is selected) and move it to the center of the house by dragging the mouse.

  6. Continue placing objects in the house using the method above. Use each of the house objects in the Custom View window to furnish the house.
  7. To rotate objects, select an object and choose Properties from the Object Menu and click the "Rotation" tab in the Object Properties window. Rotate the "Yaw (Y):" control to spin it about the Y-axis.
    Note: The Properties window can be resized and moved. If it covers the object that you are trying to rotate, resize and move it so that you can see the object as you rotate it.

  8. Place a candle on a windowsill.
    a. Select "candle_short" from the Custom View panel and place it on the floor near a window.
    b. To place the candle on the sill, look under the Object Menu and uncheck Object-->Mode-->Auto Snap to Ground. Click the candle to select it (you are out of Placement Mode, right?), then click and hold the left mouse button over it, then press and hold the Shift key. Holding the Shift key allows you to only change the vertical position of an object. Raise the candle up to the level of the windowsill. You can release the shift key while holding the mouse button to move the candle horizontally. Try to position the candle so that the bottom of its green selection box just disappears into the sill. Don't worry if the light source on the candle remains at floor level -- we'll covering lighting later on.
  9. Note that some objects (like the rug) will not block the player's movement in the game, while others (the bed) will block the player. You can find out if an object is "blocking" or "non-blocking" by viewing its Properties. Look for the value marked "does_block_path" under the "Template Properties" tab.

Add Details To Surrounding Farmland

  1. Add a crop to your farm in front of the house.
    a. When working with foliage, you can easily randomize the size and orientation of the objects as you place them by using the "Next Object" function. Select Next Object… from the Object Menu. On the Next Object Placement Settings window check the Random Orientation/Yaw (Y) and Scale/Random Scale boxes. Set Minimum Multiplier to "0.65" and Maximum Multiplier to "1.1." Click "OK". Now each time you place an object, Siege Edit will randomly vary its scale from 0.65 to 1.1 times normal size, as well as randomly vary its rotation.

    b. Select "grs_cabbage" from the foliage directory and click on the field area to add your crop, varying the rows to give them a realistic look. Placing foliage, trees and other plants "too neatly" almost always looks artificial.
  2. Add a forest around your house.
    a. Select "grs_bush_rhod_sm." Add clumps of six to eight rhododendrons around the valley, as well as a few bushes around the corners of the house, the back of the chicken coop and some at the base of the canyon wall. Create two clumps behind and on either side of the well.
    b. Select "grs_flr_aster_blue" and add them around the outskirts of the rhododendron patches here and there and around the well and house.
    c. Select "grs_pine_9m_sway" and add them on top of the rhododendron clumps. The trees should look as if they are growing out of the rhododendron undergrowth. This will help blend the trees into the terrain, giving a much more natural appearance. Space the trees so they touch a bit, but without too much overlap. Add in some "grs_aspen_red_lg_sway_grassy" in the same way for variety.

Add Starting Positions

  1. Select the Starting Positions Gizmo from the gizmos folder and place seven of them in front of the house. Move the original starting position that was automatically created with the house node to be grouped with the others. These define the location where your heroes will enter when the map is loaded.

  2. Save.

103: Lighting
<Back to Table of Contents>

Lighting a region well can make a decent environment something spectacular. Lighting can range from subtle tones to full-on vibrant color, depending on the mood you want to set. A well-lit level makes an area feel believable to the player – for example, every light object in the world (i.e. - a candle) has an obvious lightsource (i.e. - the glow and flicker of its flame).

The following steps will explain how to set ambient light for your region, how to create sunlight using directional lights, and how to place and modify point lights. Before we start, let's make sure you can see lighting changes. Under Preferences (View Menu), ensure that "Ignore Vertex Color" (under "Rendering") is not checked.

Ambient Light

Set the general brightness of your region's terrain, objects, and actors by adding region-wide ambient light:
  1. In the Lighting Menu, select Ambient Lighting to bring up the Region Ambient Light Settings window.
  2. In the Ambient Intensity portion, select the value box labeled General: and enter a value of "0.2" (the "General" setting will affect terrain). Repeat this for Object: with a value of "0.2" (affects objects), and Actor: with a value of "0.25" (affects actors).
  3. View your changes by clicking the Update button. Your region will get very dark - don't panic! We haven't added the sunlight in yet.
  4. Click OK.

Directional Lights

Create sunlight for your region by adding directional lights:
  1. In the Lighting Menu, select Edit Directional Lights.
  2. In the Edit Directional Lights window, click the New button. A number, representing the ID of a new directional light, will appear in the small box to the left of the button. Select this number, and click the Edit button. This will open the Directional Light Properties window.
  3. Create sunlight for your region. Select the Lighting tab. In the Direction section, enter a value of 2.0 in the "X:" box.
  4. In the "Options" section of the window, make sure all boxes are checked. Note that selecting "On Timer" will make the light change its color and intensity in game based on time of day. Click OK.

  5. Create lighting to realistically illuminate shadows in your region. Add a second directional light and click Edit. Under Color in the Color Values portion of the window, make the light a shade of blue by entering in the values 0.5 for Red:, 0.55 for Green:, and 0.98 for Blue:. Click Apply, and then click the Lighting tab to continue configuring the light.

  6. In the Lighting window for the shadow light (step 5), enter a Direction value of –2.0 for X:. In the Intensity: box, enter a value of 0.7. Do not check any additional check boxes under Options.

  7. In the Edit Directional Lights window, click Update All, and then click OK. There may be a pause as Siege Editor updates the region’s lighting before the window disappears.
Point Lights

Add individual light sources to your region:
  1. Add a light source near the candle. Select point light from the Custom View (under gizmos), and place it near the candle in the house.
  2. Select the point light and, using the same method for moving objects, position the light slightly above the candle that you placed on the windowsill.
  3. Select the light and right click on it. Click Properties, and then click on the Lighting tab.
  4. Set the reach of the light by entering a value of 2.0 for the Inner Radius: box, and a value of 5.0 for Outer Radius:.
  5. Click Choose Color. In the Color window, select a bright shade of yellow. Click OK.

  6. Under Options (still on the Object Properties window), check the Occlude box so that the light will be blocked by terrain, and check the Draw Shadows box so that actors near the light will cast shadows. Leave the rest of the options as they are. Click OK to close the Object Properties window.

  7. Under the Lighting Menu, select Update All Node Lighting to see your changes applied.
  8. Save.

104: Fading
<Back to Table of Contents>

Fading is the term we use to describe terrain becoming invisible at times when you do not want it to obstruct the camera. This is done with a mixture of node camera flag settings and special trigger gizmos that control node behavior. A detailed explanation of fading can be found in Siege U: 205 - Fades.

Node Camera Flags

  1. Add the door top to the house. The node "t_grs01_houses_generic-b-log-door-top.sno" connects to Door 16 of the house node.
  2. Set Camera Flags to define how the game camera will behave when it encounters the nodes in your region. From the Node Menu, choose Select All Nodes.
  3. Choose Properties from the Node Menu to bring up the Node Properties dialogue box. Under the General tab, only Occludes Light and Bounds Camera should be checked. By occluding the light for all the nodes in the region, no light rays will shine through nodes; that helps create realistic shadows. "Bounds Camera" prevents the player's camera from passing beyond the nodes. For example, the camera will stay within the boundary of the region's walls, following corners, without jumping beyond. Click OK.

  4. Select just the house node. Open Node Properties again and adjust the settings so only Occludes Light and Occludes Camera are checked. By occluding the camera on the house, the player will not see through the house if he walks so that the house is between the camera and his character. The Siege Engine will automatically adjust the camera up or to the side to keep the character in view. Click OK.

  5. Select just the Door Top node. Open Node Properties again and adjust the settings so only Occludes Light and Camera Fade are checked. "Camera Fade" will cause the node to fade when the camera ray intersects in (like if you walked inside the house). Click OK.
    Note: If the "Camera Fade" flag is checked, "Occludes Camera" and "Bounds Camera" should not be checked. Doing so can cause all sorts of problematic behavior in-game.

  6. Save.

Fading Nodes With Triggers

Add the roof node to the house by snapping either "t_grs01_houses_generic-b-roof-shingle.sno" or "–thatch.sno" to Door 15 of the house node.

To make the roof fade away when somebody goes inside the building, and fade back in when somebody leaves the building:

  1. Change the roof node's fade flags.
    a. Select the roof node and open its Node Properties dialog.
    b. Under Fade Settings, set the Section, Level, and Object fields all to 5, then click OK. The value "5" doesn't have any special significance, other than that we know no other nodes have those values. When we command 5,5,5 to fade, only the roof will fade.

  2. Add two fade triggers.
    a. Under the Settings Menu, select Region.
    b. Copy the value from the Region GUID field. Click Cancel.
    c. Select "trigger_generic" from the gizmos folder.
    d. Click on the floor in the doorway of the house to make a new trigger there.
    e. Select the gizmo by clicking on it, then right click on the trigger and select Properties.
    f. Select the Trigger Properties tab.
    g. Press the New button at the bottom-left. In the Triggers field (directly above), this has created a new trigger attached to our trigger_generic object. It is hidden right now.
    h. Click the box marked with a plus symbol to the left of "trigger_generic", which will bring down the text "trigger_0". Click on "trigger_0" to select it.

    i. Click the Add Condition button on the right.
    j. Select "party_member_within_bounding_box" from the drop-down dialogue that pops up, then press OK.

    k. Change the values (in the Value column) for Half diag X, Y, and Z to 1, 1, and 0.5, respectively. This creates a bounding box that is long and thin.

    l. Click the Add Action button, and select "fade_nodes_global" in the drop-down dialog, and press OK.
    m. Paste the region GUID (from step 2b) into the value field for the Region ID.
    n. Set the Section, Level, and Object all to 5 (remember step 1b?).

    o. Double-click on the field in the Value column, to the right of Fade Type. This will bring up a drop-down list of all valid choices; select out:black.

    p. Click the Rotation tab, and enter in 20 as the Y value. OR rotate the trigger so that the bounding box covers the entire floor beneath the doorway.

    q. Click OK on the bottom of the Object Properties window.
    r. With the trigger selected, click on Copy in the Edit Menu. [default hotkey = Ctrl+C]
    s. Point the mouse cursor at the front steps of the farmhouse, and press Ctrl+V [default hotkey for Edit Menu | Paste]. This creates a copy of the first trigger.
    t. Rotate and position the trigger so that its bounding box completely covers the bottom stair. This way the character will always trigger it when he crosses the first step.

    u. Select the second trigger, and bring up its properties.
    v. Change the value for Fade Type to in.

  3. Save.

105: Moods
<Back to Table of Contents>

Moods are atmospheric settings used to polish and optimize a level. They consist of weather events such as rain, snow, and fog, music, ambient noises, and a few other advanced features that are covered in greater detail in Siege U: 204 - Moods. For now, we have provided a pre-created mood to use with this tutorial.

  1. Create a new generic trigger in the middle of the start points. Refer to section 105, steps 2c - 2i if you need assistance.
  2. Add a new Condition and choose "party_member_within_sphere."
  3. Change the Radius (of the sphere) to 5.
  4. You should see a sphere debug HUD with the new trigger in the middle (you may need to move the Object Properties dialogue out of the way). Visually verify that this encompasses all of the start points.
  5. Add a new Action to the trigger and choose "mood_change."
  6. Enter the following value for the Mood Name: "map_world_fh_r1_4" (no quotes).

  7. Click OK to close the Object Properties dialogue.
  8. Save.

106: Monster Placement
<Back to Table of Contents>

Monster placement is one of the easiest aspects of the Siege Editor, but also one of the most influential. Where the monsters are placed directly affects how the game will eventually play out. A few simple guidelines:

  • Never place monsters right next to Starting Positions.
  • The monster classes are similar to the player classes: Melee, Ranged, and Magic.
  • Monsters appear in 3 variations: Normal, Revealing, and Generators.
    • Normal monsters are visible and ready to attack the player when spotted. For example, the Krugs at the start of Dungeon Siege.
    • Revealing monsters are visible but play an animation when the player is spotted. They do not attack and are not targetable until the animation has completed. For example, the gargoyles that sit on pedestals in the Crypts.
      • You can also hide revealing monsters. Those monsters cannot be seen by the player until the player reaches the reveal trigger set by the designer.
    • Generator monsters do not exist until the player triggers the generator. Once activated, the generator will spawn monsters based on its settings.

For this tutorial you will be placing 2 groups of monsters: one group of Melee/Normal and one group of Melee/Generator.

[Note: This "exploding door" generator is not included with Dungeon Siege v1.0. If you do not have Dungeon Siege v1.09B or greater, skip down to the "Move over to the well node" paragraph and continue on. Once you have upgraded your version of Dungeon Siege, we encourage you to revisit the "exploding door".]

First is the Generator group:

  1. From the Custom View open the monsters folder and select "tutorial_gen_explode_door-krug_grunt".
  2. Find the house node, and place the "exploding door" object near the doorway.
  3. Adjust the door to fit into the doorway. If it is too small to cover the doorway, open the door's Properties, click on the Template Properties tab, and adjust the scale_multiplier setting until the door fits.

You’ve just placed an exploding door that will spawn in a Krug Grunt (a very tough monster) so watch out when approaching the house. Also of note, you’ll see a red circle around the door generator; this is its detection range. In game, when a player enters that circle, the door will explode and a Krug Grunt will appear.

Move over to the well node. If you did not place shrubbery behind the well, go ahead and add two groves now. This is a good place to have a Krug Scavenger ambush.

  1. Select "krug scavenger" from the custom view.
  2. Place three Krug Scavengers near the well.
  3. Select one Scavenger and move it to the middle of the area between the groves.
  4. Select another Scavenger, and drag it inside one of the forest groves (Note: watch the outermost circle as you do this. This is the monster’s player detection range, just as with the door generator. You’ll want to make sure each of the Scavengers detection circles overlap, so they will converge on the player at the same time).
  5. Repeat step 4 with the third Scavenger, moving it to the other forest grove.

  6. Save.

107: Opening Your Map in Dungeon Siege
<Back to Table of Contents>

Now that you have built a map, you need to test it. In order to do that, you will need to "tank" your map into a DS-readable format.

  1. Save your map. If you tank and have not saved recently, any unsaved changes will not be tanked.
  2. Under the File Menu, select "Save Map as .dsmap..."
  3. Select the map you would like exported. We suggested "my_world" at the beginning of this tutorial, but if you have forgotten the name of the map you have been working on, it is displayed in the title bar of Siege Editor. (The title bar is the top strip of the SE window that contains the minimize/maximize/close buttons.)

  4. Click OK. It is highly advised that you accept the default Destination File, as it vastly simplifies the export/import process. Advanced users may elect to redirect the map to a different location, but we'll stay with the easy route for now.
  5. On the following window, the only recommended fields to change are "Title", "Author", "Copyright", and "Description". All other fields should be modified by advanced users only.

  6. Click Start!.
  7. A "Processing Resource File..." progress display will appear. The final status will be listed in the "Status" window as "Success!" if you followed these steps properly. If any errors result, read the error message and start the export over. Often times the error is the result of varying from the default settings.

  8. Click Close.
Let's try your map out!
  1. Close Siege Editor.
  2. Launch Dungeon Siege.
  3. Select "Single Player", "Start a New Game", and choose your hero.
  4. At the "Load Map" screen, select your map, click Next, and choose "Normal" difficulty.

  5. Assuming you built your map correctly, you should be standing at one of your starting locations. Congratulations and watch out for that door!

Conclusion
<Back to Table of Contents>

That concludes this tutorial. You should now be familiar with the Siege Editor and its basic functionality. We have covered placing nodes, objects, and monsters; we have discussed lighting, fading, and moods. Finally, we discussed how to convert your map into DS-usable format and back to SE-editable bits. If you would like to delve further into modding Dungeon Siege, check out the advanced topics covered in the Siege University 200 and 300 series.

Appendix A: Tips
<Back to Table of Contents>

Here are some tips from the GPG level designers that might simplify your SE interaction:

  • Use the "generic" node set by default. It contains the greatest variety of nodes and offers the most compatability.
  • Use the generic 8x8 (t_xxx_flr_08x08-v0.sno) or 4x4 (t_xxx_flr_04x04-v0.sno) node as your starting node. It is a very simple and clean node, offering the most expandability.
  • Get used to using a,d,w for node placement and orientation. The hotkeys are much faster than using the Source Doors, Destination Doors, and Attach Node buttons.
  • If you keep the "focus" on a node (ie - you don't select another node), you can try out different nodes on a particular door easily. For example, select a node that you would like to attach another node to. Select the door you would like to connect to (use a and d), select the node you want to attach to that door, and then hit w until the node lines up. Now, select another node from the list on the left and carefully click on the "root" node in the main window (the node you attached the 2nd node to). If the selection does not drop from that "root" node, you can hit w and rotate the new node in the place of the 2nd node.
  • You can add nodes and objects from the Main View window to the Custom View window. Right-click on the node and select "Add to Custom View". This is extremely helpful when you are doing "bulk" nodework, especially with walls and corners. That will greatly speed up your efficiently, since you won't have to scroll the Main View window so much.

Appendix B: Siege Editor Preferences
<Back to Table of Contents>

Here are the suggested Preference settings for generic SE work. The Preferences can be adjusted under View | Preferences. For information on what the settings are, click on the "Help" button on the Preference menu or refer to the Siege Editor Manual.

Node Settings checked:

  • Draw Door Nodes
  • Draw Stitch Doors

Rendering checked:

  • Dynamic Lighting
  • Static Lighting
  • Dynamic Shadows
  • Static Shadows

Misc checked:

  • Update Lighting/Effects
  • Draw Light Data
  • Right Click Camera Scrolling

Content Display: development name

Key Movement Delta: 0.1

Zoom Rate: 0.5

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_200 Concepts and Terminology

Dungeon Siege Concepts and Terminology

A B C D E F G H I J K L M
N O P Q R S T U V W X Y Z

A

Action: What a trigger does when its conditions are tested "true". Actions include the direct execution of a number of functions and the sending of messages. Messages are sent via the game's message delivery system to other objects in the game that are waiting for them, such as other triggers, gizmos, and actors.

Actor: A Game Object with AI (artificial intelligence), but also includes the party. Actors include all monsters, ambient creatures, and NPCs; they also include other objects you wouldn't normally consider an actor, but have [actor] blocks in their template, like certain doors and chests. Actors that can be manipulated in SE have a SCID, but all actors (even ones that don't appear in SE, like the starting player character or monsters spawned from generators) have a GOID.

Artificial Intelligence (AI): Code that determines how an actor interacts with the world by scanning for objects/actors the actor can see. AI sends messages to its two subsections (brain and jobs) for interaction commands.
Brain: The higher level of AI. The Brain is responsible for high-level behavior and typically runs Jobs in response to stimuli. The Individual Brain (per actor) decides how to interact with objects/actors (i.e. attack, guard, flee). Every actor can be assigned a brain, even things not assigned intelligence in the real world, like trees, chests, furniture, etc. A Party Brain can be assigned to control group tactics, like healing friends, supporting friends, or moving and attacking in formation.

Jobs: The lower level of AI. Jobs are responsible for handling specific functions, using chores to accomplish those tasks. Jobs manage:
  • Movement: Following, moving in formation, guarding, moving
  • Attacking: How to get there, what animations to use
  • Item manipulation: Getting items, dropping, equipping
  • Talking to other actors: Selecting the appropriate conversation, syncing dialog and animations, playing any special actions that occur as a result of the conversation
  • Other actions: Using, drinking, going unconscious/conscious, special monster behaviors, fleeing, dying
Chores: Used by Jobs, but not a dedicated section of AI. Chores can also be used by objects through attached skrits or external gizmos. Chores are useful for controlling animations, including playing, blending, and modifying.
C <Back to Top>

Component: Represents the code or skrit used to handle a certain aspect of a GO. For example, the mind component of an actor runs AI. Components are defined by the template(s) that a GO specializes. (Siege U: 201 - Templates)

Conditions: A predetermined requirement or set of requirements that must be met before the trigger's actions will be executed. Conditions include testing whether a certain kind of object has entered, exists within, or has exited a specified area; whether the trigger has received a particular message from another object or the game itself; or both. (Siege U: 203A - Triggers I, Siege U: 203B - Triggers II)

D <Back to Top>

Door: A numbered line of polygons on a node, usually along an edge, used for connecting other nodes.

F <Back to Top>

Fades: The act of making nodes visible or invisible is called "fading". Triggers that activate fading actions are usually called fade triggers. When a node is faded out (completely invisible), all objects associated with that node are also invisible, as well as unselectable. (Siege U: 205 - Fades)

Frustum: The "world frustum" is the area around a party member where the world is "loaded" and visible to the player. It is a box-like shape whose center is permanently focused on the active party member (or members, in the case of a multiplayer game). Any node or GO outside this area is "unloaded"--compressed down into a small data structure until it comes within the frustum. As the player moves, nodes and objects are "streamed" into the frustum and loaded. Any nodes or objects leaving the frustum are "streamed out" and unloaded. We generally cannot directly "talk to" a node or object outside the frustum (since it is in an unloaded state), but any messages sent to these nodes or objects are queued by the game's messaging system to be delivered when the node/object gets loaded again.

G <Back to Top>

Game Object (GO): A GO, or more generally, "object", is nearly any mesh (3D object) in the game that isn't a node. Objects can be placed and moved on, above, or below nodes, and include things like actors, furnishings, gizmos, lights, etc. The generic term "object" is also used to talk specifically about meshes that don't have another more specific name (like actor or gizmo), such as trees, chairs, rocks, fences, etc. All GO's are defined by their "template".

Gas Files: Essentially plain text files which are written in a specific format that the Siege Engine knows how to read and interpreted at run time. These files actually help define the game while the gaming engine knows how to make sense of all this data, how to properly display it, and how to allow the user to interact with it.

The types of data that are defined in Gas files vary widely. Gas files can define what a tree looks like, where a monster is placed in the world, how the world itself is built, what happens in the world, and even what the user interface looks like or what the default preferences for the game are. The engine then takes all this information and magically makes it all work when you run the game.

Hex numbers appear differently in Gas files than in the Siege Editor. When hex numbers are saved out to Gas files, they always appear with eight (8) digits, even if there are leading zeros. For example, numbers written as "0x4444, 0x12345678, 0xaa, 0x8" will appear in Gas files as "0x00004444, 0x12345678, 0x000000aa, 0x00000008", respectively.
Generator: A Siege Editor gizmo that creates monsters once triggered. A generator creates monsters based on its settings, edited in SE. (Siege U: 100)

Gizmo: A logical Game Object placed and manipulated in SE. Gizmos are self-contained pieces of skrit or other functionality that accomplish specific actions in the game. Gizmos are connected or "wired" to many other objects, actors, and gizmos. Gizmos include things like AI commands, elevators, creature generators, sound and sfx emitters, light manipulators, and lots more. Much of the functionality of triggers will be in activating/deactivating gizmos. The trigger itself is a gizmo. Gizmos share the same identifiers as all objects: SCIDs and GOIDs.

GOID: See Identifiers.

GUID: See Identifiers.

I <Back to Top>

Identifiers: All nodes and objects have recognizable template names that you use to identify them (like krug_scavenger), but SE needs to keep track of each unique "instance" of that node or object. For example, you may have 20 Krug Scavengers in your region, but each of those 20 instances has the same template name, krug_scavenger. Internally, the game engine assigns several of the following "identifiers" to make the instances unique. Each identifier is an eight-digit hexadecimal number, preceded by "0x", which looks like this: 0xe589a1ff.
Global Unique Identifier (GUID): Each Node and Region is assigned a Node or Region GUID (pronounced "gwid") on creation. The GUID is a unique number per map (see the SE Manual for more info).

Static Content Identifier (SCID): A SCID (pronounced "skid") is a unique number assigned to every object placed in SE. SCIDs are used to reference almost every kind of object, including actors and gizmos.

Global Object Identifier (GOID): Every object in the game is assigned a GOID by the game engine. They are different from SCIDs in that a SCID is used to connect objects in SE. GOIDs are used to reference and connect objects dynamically in the game engine. Sometimes an object doesn't have a SCID, like the starting player character or a monster spawned from a generator, but it always has a GOID.

L <Back to Top>

Level Of Detail For Items (LODFI): LODFI is both a way of removing objects from the world to reduce rendering time and an optimization for save game and multiplayer. LODFI is a pair of values in the [aspect] block of a template called lodfi_lower and lodfi_upper.

For the purposes of rendering time, LODFI works to remove objects with LODFI values greater than 0 from the world so that they are not drawn. Objects whose LODFI is lower than the value represented by the in-game object detail bar, whose range is from 0.2 to 1.0, will be drawn. Objects whose LODFI is higher than this setting are not drawn.

However, LODFI is used for many things even if the detail slider is not involved. For example, while a path-blocking bed in a farmhouse is considered "required" due to its blocking property and should never go away regardless of the slider, it should not be marked as -1. It should instead be marked as 0 so it's still considered a LODFI object but is always loaded. LODFI objects are not saved out in the save game (because their state cannot change) and they are not transmitted over the network in multiplayer. They do not show up in AI sensor queries and are considered 100% pure eye candy. They are specially permitted to block paths, and that's what the local-plain is meant for, but they're still considered a LODFI Object in the eyes of the save game system.

From the perspective of the loader, objects can be broken down into three LODFI categories:

  • Global: Objects with lodfi_upper < 0. Used for anything that is generally interactive, like monsters, doors, or openable chests.
  • Local-LODFI: Objects that are client-side only and are used for decorations and have lodfi_upper >= 0.
  • Local-plain: Special objects that are loaded locally but may have global effects. The server needs to load them for frustums outside its screen player's (usually reserved for blocking objects), and are set on the server if lodfi_upper == 0, it is a multiplayer session, and aspect:does_block_path = true.
As described in components.gas:

[lodfi_upper] { type = float; default = -1.0; constrain = range[ 0.0, 1.0 ];
    doc = "Upper bound for level of detail for items - will always render if
    object detail level higher than this (set -1 for critical/global)"; }
[lodfi_lower] { type = float; default = -1.0; constrain = range[ 0.0, 1.0 ];
    doc = "Lower bound for level of detail for items - will not render at all
    if object detail level lower than this"; }
The numbers correspond to the object detail slider on the options menu, where the lowest detail setting was chosen to be 0.2 and the highest is 1. The upper/lower_lodfi settings in [aspect] define a range. If the object detail is lower than the range, it never loads, and if it's higher, then it always loads. If it's somewhere in the middle then there is a random chance it will load (this is good for shrubs). Here is the algorithm that the loader uses when deciding to load an object:
  • If lodfi_upper <= 0 then load always
  • If object detail level > lodfi_upper then load always
  • If object detail level < lodfi_lower then never load
  • Pick random number between lodfi_lower and lodfi_upper. If object detail level < this then don't load, otherwise load.
Note again that we chose a minimum value for the object detail bar of 0.2, and not 0 as perhaps would be expected.

M <Back to Top>

Map: The sum collection of all stitched regions. This is the entire "world". The map is not directly referenced within SE since you are already working on a region inside it. You cannot reference a region in a different map.

Messages: Used to send events throughout the game. Messages are used to trigger the AI, activate gizmos, control animations, etc. Messages can be sent as "normal" or "delayed"; normal messages are processed immediately. Delayed messages can be sent to unloaded SCIDs (see Identifiers), which are queued until the objects are loaded. Data contained in messages includes the sender, the recipient, and optional data elements.

Mod: A user-created modification of Dungeon Siege. Mods can be very simple (changing a texture on one weapon) to very complex (a entirely new story with new map and features). Also known as "Siegelet".

Moods: A block of settings that specifies the fog near and far distance, the fog color, the music and how often it repeats, the ambient track, the weather type and density, the wind speed and direction, the EAX settings, and even (in some cases) the size of the frustum. (Siege U: 204 - Moods)

N <Back to Top>

Node: The basic building block of terrain. Nodes are individual 3D tiles locked together to create the game world. Nodes can be individually selected, and are identified in SE by a Node GUID (see Identifiers).

O <Back to Top>

Object: See Game Object.

P <Back to Top>

Parameterized Content (pcontent): Randomly generated content, defined by a set of rules. For example, a chest can be loaded with a specific range of weapons to drop, instead of adding a specific template name. For further information, examine the compressed file components.gas.

Party Member: Also considered an actor (and therefore a GO), a party member is the player's character or group of characters, including the packmule. The starting party member doesn't have a SCID, but all the party members that join him/her do since they start life as objects in SE. All party members have a GOID.

Properties: Used to define components. For example, the Aspect component has a property called "scale_base" that defines the size of the object in game.

R <Back to Top>

Region: A discrete "level" composed of nodes. SE loads individual regions for editing. Regions are "stitched" to each other so they appear seamless in game. Regions are identified in SE by a Region GUID, and must be assigned to a map.

S <Back to Top>

SCID: See Identifiers.

Skrit: A scripting language that can trigger and manipulate game events. Many gizmos are self-contained pieces of skrit. (Siege U: 303 - Skrit)

Siege Editor (SE): The Siege Editor is the primary tool used for constructing maps for Dungeon Siege.

Siege Max (SM): The Dungeon Siege game pack for gmax, from discreet. Siege Max allows the modification and creation of custom 3D models and animations that can be directly imported into Dungeon Siege. (Siege U: 214 - Siege Max: Characters, Siege U: 215 - Siege Max: Objects, Siege U: 216 - Siege Max: Terrain)

Siege Objects: Siege Objects are SE-specific objects that can be placed and manipulated within SE, but are never selectable within the game itself. Falling into this category are Starting Positions, Point Lights, Spot Lights, and Decals. Siege Objects are defined in the code and do not have templates at all.

Sound Effect Descriptor (SED): .GAS files that contain configurable information that affects the playback of a particular sound sample. An SED can control the playback rate of a sample, the maximum number of simultaneous instances of a sample that may be played, and the override falloff distance of a sample. (Siege U: 210 - Sounds)

Special Effects: Mainly particle effects. A particle in Dungeon Siege is a forward-facing, textured polygon, which can blend with other particles and other objects. Special effects run on two objects: a source and a target. Effects are most commonly visual, but certain effects can collide or cause damage. Also referred to as "Siege FX", "flamethrower", and "worldfx". (Siege U: 212 - SiegeFX)

Stitch: Region stitching is the process of joining two region boundaries so they are read into DS as if they were the same region.

T <Back to Top>

Templates: At the most basic level, a template tells the game engine how to use certain game resources - such as art, sound, and physics properties - to create (instantiate) a Game Object (rock, tree, sword, a Krug, etc.) in the world. A template is simply a block of text that defines the characteristics of a Game Object. (Siege U: 201 - Templates)

Trigger: A Siege Editor gizmo that makes something happen in the game when you want it to happen. To accomplish this, a trigger does two things:

  1. Tests to see whether certain conditions are true
  2. If true, executes certain actions.
(Siege U: 203A - Triggers I, Siege U: 203B - Triggers II)

U <Back to Top>

User Interface (UI): The on-screen controls that a player uses to play Dungeon Siege. Sometimes referred to as "GUI", which stands for "Graphical User Interface" (as opposed to a text-based interface).

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_201 Templates

Templates

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

What This Tutorial Will Teach You

Untank Logic.dsres

Show Me A Template!

Template Structure

Components

Fields

Values

Specialization

Core Templates

Base Templates

Leaf Templates

Instance Templates

Overriding a Specialized Component or Field

Naming Conventions

Art Assets: Placing and Naming

Conclusion

Beyond The Scope Of...

Appendix A: Naming Conventions & Siege Editor: Template Sorting

Category Names

Doc Names

Introduction

At the most basic level, a template tells the game engine how to use certain game resources - such as art, sound, and physics properties - to create (instantiate) a game object (rock, tree, sword, a Krug, etc.) in the world. A template is simply a block of text that defines the characteristics of a game object.

When modding a game, one can either modify the content already included in the game, add completely new content, and most often a combination of the two. The need to create a template arises when a mod developer wishes to add content to the game or use the existing content in new ways, without losing any functionality of the existing content. For instance, if you would like to create a new sword with completely new art, you should create a new template for it. In the same vein, if you wish to create a new sword just with new stats, you should create a new template so as to preserve all of the already defined swords in the game. These are not laws, of course, but rather guidelines as to the purpose of creating your own templates.

What You Need For This Tutorial

  • Dungeon Siege, updated to version 1.09B or later
  • A text editor. Notepad will work fine; UltraEdit-32 offers advanced functionality.
  • Untanked resource files.

What This Tutorial Assumes You Have Already Learned

  • Siege U: 100 - The Basics of the Siege Editor, specifically sections regarding tanking/untanking maps.
  • Siege U: 200 - Dungeon Siege Concepts and Terminology

What This Tutorial Will Teach You

  • Definition of a template
  • Why templates are used
  • Template structure
  • Template specializations
  • Naming conventions
  • Sorting templates within Siege Editor
Untank Logic.dsres

To untank the Logic.dsres into its editable components, follow these steps:

  1. Start the Siege Editor.
  2. Select Convert .dsmap to Files... from the File Menu.
  3. For the Source File, browse to Dungeon Siege\Resources.
  4. Under "Files of type:", switch to Dungeon Siege Resource Files (*.dsres)
  5. Select Logic.dsres and click Open.
  6. For the Destination Folder, leave it at its default setting (My Documents\Dungeon Siege\Bits).
  7. Click OK.
  8. Once the untanking is completed, exit the Siege Editor.
You have now untanked the Logic.dsres file into My Documents\Dungeon Siege\Bits. File paths listed in this tutorial can be found in this directory. If you untanked to a non-default directory, you will have to look there to find the files referenced below.

Show Me A Template!
<Back to Table of Contents>

Templates reside in GAS files and can range from as few as four lines of text to as many as a hundred or more. A GAS file can contain one or one thousand templates - the game engine does not care (for your own sanity, however, a good guideline is under 100 templates per file). The following code shows an example of a template that actually resides in a file in the Logic.dsres tank located in the \Resources directory of your installation of Dungeon Siege.

[t:template,n:blacksmith_hammer]
{
    specializes = base_misc_fs1;
    category_name = "other";
    doc = "blacksmith hammer";
    [aspect] { model=m_w_hmr_004; }
    [attack]
    {
        damage_max = 2;
        damage_min = 1;
    }
    [common]
    {
        screen_name = "Blacksmith Hammer";
    }
    [gui]
    {
        active_icon = b_gui_ig_i_ic_hmr_001;
        inventory_height = 2;
        inventory_icon = b_gui_ig_i_w_hmr_004;
        inventory_width = 1;
    }
}
(taken from world\contentdb\templates\regular\interactive\wpn_misc.gas)

One of the great things about Dungeon Siege is the large amount of documentation included in the game files themselves. Much of the following can also be found in - and is, in fact, taken from - the various files touched upon below. This documentation may appear in the form of comments at the beginning of the file or in-line with the data. See templates.gas and components.gas (discussed below) for some good examples of this.

One thing to keep in mind, however, is that some of the commented documentation in these files may be out of date as they were written during the initial implementation of a feature or system. It's possible (and often likely) that the operation of the feature or system documented has since mutated between the initial implementation and the final release version of Dungeon Siege. The information within this document should therefore be used in conjunction with the information in these files, with the data here taking precedence over file documentation.

Template Structure
<Back to Table of Contents>

Templates are made up of 3 types of data:
  • Components
  • Fields
  • Values
  • "Blocks"
OK, that's four, but a block is really just the collection of the first three.

A template is made up of blocks which contain a component, one or more of the component's fields, and those fields' values. Let's look at the [common] block in the above template:
[common]
{
    screen_name = "Blacksmith Hammer";
}
We can see that this block contains a single field - screen_name, and that field's value - "Blacksmith Hammer". A block is considered to include the declaration of the component (in this case [common]) and everything between the open and close braces. The field lines contained in a block are terminated by semicolons. Anyone familiar with C, C++, Java or similar languages will recognize these syntax conventions, which, incidentally, must be followed exactly or your object will fail to load into the game engine.

All of the templates in the game can be found in the GAS files in Logic.DSRES, under world\contentdb\templates.

All of the base templates including the core templates in templates.gas (discussed later) can be found in the \regular subdirectory. The \veteran and \elite directories contain the templates that have been tuned for the other two difficulty levels. It's recommended that after you've read this document and become familiar with templates and the template structure, you look through the veteran and elite files and templates to gain an understanding of how these are used by the engine to create alternate difficulty levels. Basically, the templates within \veteran and \elite are mirrors of their \regular counterparts, with increased stats and pcontent ("parameterized content") ranges. Parameterized content is the system by which the engine creates dynamic content based on ranges of values. While pcontent is outside the scope of this document, more information can be found in components.gas (see below).

Components
A component is a basic building block of a game object. Various components are defined that implement certain tasks in the game engine. Within templates, components appear inside square brackets ([]). For example, the [common] component in the above template contains one field (the 'screen_name' field) that tells the engine what to display on screen when the item is picked up or moused-over.

One of the most valuable files with regards to learning about templates is components.gas, located in world\contentdb\components\.

This is your Secret Decoder Sheet for deciphering all of the components and their fields in the game. I would recommend having this file open while you look through some of the templates to familiarize yourself with the various components and their myriad fields. As you discover a new component in a template, you can refer back to components.gas to find out just what it does.

Fields
For example, if you were unsure as to the purpose of the "screen_name" field in the above template's [common] component, just crack open components.gas and do a search on 'screen_name'. This should yield a hit on the following line, just under the [t:component,n:common] heading:

[screen_name]
{
    type = string;
    flags = LOCALIZE;
    doc = "Text seen in-game upon mouse over";
}
The two most important parts of any field's line in components.gas is the default= and the doc=. Just about every field will have a default (there are exceptions, as in this case), and the doc will (usually) tell you exactly what that field's purpose is. Although this field of the common component doesn't have a default, we'll talk more about defaults later when we get into specialization.

Values
The values are pretty self-explanatory. Each field specified in a component block of a template must have a value. In the above example template, the value of the screen_name field is "Blacksmith Hammer"..

Specialization
<Back to Table of Contents>

Specialization is one of the most important aspects of the template system. It is the process by which templates inherit properties (components, fields, and values) from other templates. In the simplest terms, templates lower in the tree that specialize templates higher in the tree inherit all of components and fields that appear in that specialized template.

All of the templates in the game (over 7,300) are arranged in a logical tree, with the core templates (see templates.gas) making up the trunk of the tree, base templates as the branches of the tree, and the leaf templates as the final extension of the tree's branches. The fourth type of template, the instance, is not often (if ever) edited by hand and is created by the Siege Editor when saving out a region of a map in which you've placed content. The four types of templates are discussed briefly below.

  • Core templates
    Core templates, also called root templates, are the foundation templates that the rest of the templates in the game are built from. Every other template in the game eventually inherits information from one of the core templates (if not overridden…more on that later). All of the core templates can be found in templates.gas, located here:

    world\contentdb\templates\regular\_core\templates.gas

    Many of the base templates in the game specialize a core template in templates.gas. This is not a global rule, however, as there are also many base templates that specialize other base templates.

  • Base templates
    The base templates reside in the various GAS files under the world\contentdb\templates\ directory structure, often as the first template listed in a particular GAS file. These templates are often named in the format of "base_templatetype", e.g. [t:template,n:base_container_barrel] in ctn_container.gas.

    All of the leaf templates in the game specialize a base template.

  • Leaf templates
    Leaf templates are at the end of the specialization chain, and actually define the only objects that may be added via Siege Editor or instantiated by the game engine. In other words, as soon as any template B specializes template A, template A can no longer be added to a region with SE or at the console.

    Instance templates specialize leaf templates, although instances do not have a "specialize" field - it's an implicit specialization.

  • Instance templates (or instances)
    The fourth type of template, called an instance, is created only while using the Siege Editor to place content. These instances appear in the various GAS files in a map's region after placing objects or actors and saving out that region. These instances possess the properties of their leaf templates - the engine treats this specialization in exactly the same way the leaf and higher templates are specialized even though there is no specializes= field. In addition, you will almost never manipulate these instance templates by hand, and the practice is in fact discouraged.

Specialization Example
Given the above, you can think of specialization as a snowball effect. Consider the following hypothetical example:

  1. Core template "core_template" defines A, B, and C in file templates_hypothetical.gas.
  2. In stuff.gas (somewhere down the directory tree), base template "base_template" specializes "core_template", and additionally defines D and E.
  3. Two leaf templates "leaf_template_01" and "leaf_template_02" specialize "base_template".
  4. leaf_template_01 defines BOB
  5. leaf_template_02 defines SALLY
Now, through specialization, the leaf templates actually possess much more than just BOB and SALLY even though this is the only component that appears in their templates:

leaf_template_01 | leaf_template_02
A | A
B | B
C | C
D | D
E | E
BOB | SALLY

This is why leaf templates often appear so small, when in fact they may have inherited a whole slew of components, fields, and values from multiple levels of specialization.

For an actual example, let's look at a very small leaf template and trace its specialization trail back to one of the core templates. The following leaf template can be found in world\contentdb\templates\regular\non_interactive\signs_directional.gas:

[t:template,n:sign_cav_glitterdelve]
{
    doc = "directional_glitterdelve";
    specializes = base_sign_directional;
    [aspect]
    {
        model = m_i_cav_sign-glitterdelve;
    }
    [common]
    {
        screen_name = "Glitterdelve";
    }
}
This template, sign_cav_glitterdelve, specializes base_sign_directional. If we look at the top of the base_sign_directional.gas file we see the base template base_sign_directional that this template specializes. We can also see the two components and their fields that all other signs in this file that specialize base_sign_directional inherit:

[t:template,n:base_sign_directional]
{
    category_name = "sign";
    doc = "base template for directional signs";
    specializes = interactive;
    [aspect]
    {
        draw_selection_indicator = false;
        is_selectable = true;
        lodfi_lower = 0;
        lodfi_upper = 0;
        megamap_icon = b_gui_ig_mnu_arrow;
        megamap_orient = true;
        megamap_override = true;
    }
    [common]
    {
        auto_expiration_class = immediate;
        forced_expiration_class = immediate;
        rollover_display = true;
    }
}
As you can see, this base template specifies much more than the leaf templates that specialize it. This is an excellent example of the power of specialization - instead of having the large amount of identical data in each and every leaf template, all of the common component field values have been put in the base template. All of the other signs in this file then inherit these values. Not only does this speed up the GAS parser on game load, it's also easier on the eyes when working with the templates in this file. This is GOOD PRACTICE™ - try to have as much as possible in your base templates, and make your common leaf templates as small as possible. Alright, on with the specialization trail…

This base template - base_sign_directional - in turn specializes the interactive core template. This template resides in templates.gas:

[t:template,n:interactive]
{
    doc = "An interactive item or object";

    aspect:is_selectable = true;
    aspect:megamap_icon	= b_gui_ig_mnu_dropped_item;
    aspect:use_range = 2;

    [common]
    {
        forced_expiration_class = normal;
        auto_expiration_class = normal;
    }

    [placement]	{  }
}
All of the components here in the interactive template eventually get inherited by the sign leaf templates in world\contentdb\templates\regular\non_interactive\signs_directional.gas.

Two things appear in this template, however, that are a bit different from the aforementioned conventions. The first is the colons appearing between multiple lines of the aspect component and its fields, as well as an empty [placement] block.

The first we call the "shorthand" form of a component block, and it's usually used when there are only 1 or 2 fields to appear in a component block. This:

    aspect:is_selectable = true;
    aspect:megamap_icon	= b_gui_ig_mnu_dropped_item;
    aspect:use_range = 2;
is exactly the same as this:
[aspect]
{
    is_selectable = true;
    megamap_icon = b_gui_ig_mnu_dropped_item;
    use_range = 2;
}
as far as the engine is concerned. You may use either convention as the GAS parser reads these in similarly.

The empty component (in this case the [placement] block) often appears in core templates, and simply states that this component must appear somewhere in a template before it can be instantiated by the engine to appear in the world. The [placement] component really doesn't appear in any templates further down in the chain, but is added to the instance of a template when that object is placed via the Siege Editor; SE automatically adds the placement component and its relevant fields. Although this component in this template appears empty, it does in fact have some default values as defined in components.gas (see [t:component,n:placement] in components.gas).

Overriding A Specialized Component Or Field
Overriding occurs when a template lower down in the tree explicitly overrides a component field value that was specified in the specialized template higher in the tree. For example, if a base template for trees (say, "base_tree") specifies a screen_name of "Tree" and a tree leaf template (say "tree_oak_01") specifies a screen_name of "Oak Tree", that leaf template's screen_name is said to override the base template's screen_name field.

Naming Conventions
<Back to Table of Contents>

Naming conventions are particularly important when dealing with templates. Two rules must be followed at all times - no two templates must have the same name, and template names must not contain spaces. As far as the naming itself goes, in general names of templates should be least specific to most specific from left to right. Consider the following tree leaf template:

[t:template,n:tree_grs_apple_01]
This line tells the engine of the data type (template), and then specifies the template's name, "tree_grs_apple_01". Again, all templates must have a unique name, system-wide. No two templates may share the same name in any of the GAS files residing in any TANKs, otherwise you will receive duplicate template errors upon loading up the Siege Editor. The engine will read in the first template it finds while parsing the GAS files and simply ignore the next template it finds that shares the same name.

Back to the name of this template… First, its "tree" designation appears, then "grs" (which stands for "grass" - a world area type), then the exact type of tree - "apple" - and then the 'version' of that apple tree. If you had three different apple trees that only differ in size, you might name them thusly:

[t:template,n:tree_grs_apple_small]
[t:template,n:tree_grs_apple_medium]
[t:template,n:tree_grs_apple_large]
Or, they could be simply:
[t:template,n:tree_grs_apple_01]
[t:template,n:tree_grs_apple_02]
[t:template,n:tree_grs_apple_03]
It is up to you how you'd like to name your templates, but following a convention such as this is highly recommended, especially if you will be creating a large amount of templates. And remember, no two templates system-wide may share the same name. This includes templates between your mod and the retail version of the game, as well as other mod groups' templates.

One of the other things that we are highly recommending to modders is, when you create new templates, preface the template name with your (or your group's) initials. For example, if we here at GPG were to create our own "mod" and were adding three totally new apple trees, we would name our three templates in the following fashion:

[t:template,n:gpg_tree_grs_apple_01]
[t:template,n:gpg_tree_grs_apple_02]
[t:template,n:gpg_tree_grs_apple_03]
Again, this is not a requirement per-se, but is strongly, highly, hugely recommended to avoid conflicting data between mods created by other members of the community. Another point to note is that the engine does not care about the naming of the GAS files that reside under the world\contentdb\templates directory. You may create as many new files named however you wish, and of course a good practice would be to preface your filenames with your initials (e.g. gpg_wpn_sword.GAS).

For more information on naming conventions and the Siege Editor, including ensuring your new templates (or rather, the game objects they define) are available for placement, please see "Appendix A: Naming Conventions & Siege Editor: Template Sorting" below.

Art Assets: Placement & Naming
In contrast to the lack of template and template file naming restrictions (besides duplicates and spaces), art resources referenced by your templates MUST follow the established conventions. The engine utilizes the art resources based on location within the art directory structure and the filenames themselves. The art files residing in the following directories:

\art\Animations
\art\Bitmaps
\art\Meshes
\art\Terrain

are named as they are for two reasons - asset management (there's a ton of 'em) and engine use. The engine uses a "Naming Key" in order to know how to parse and use the art files. Please take a moment to look at the NamingKey.NNK file located here:

\art\NamingKey.NNK

The naming key is the engine's Secret Decoder File to know where and how to reference all of the art files in the game, based on the name of the files. Each file type has a prefix that must be defined in the naming key for the engine to know where to find it. Explaining each type of art file and the various sections within the naming key is beyond the scope of this document; however, it's fairly easy to decipher using the NNK and perusing the art files. Although at first glance the naming key and it's naming conventions may appear to be nonsensical, a naming system had to be developed in order to keep track of the many thousands of art files in Dungeon Siege.

Conclusion
<Back to Table of Contents>

A template defines an object that may be placed using the Siege Editor, or instantiated by the game engine at run-time. Templates reside in a tree hierarchy, with templates lower in the tree specializing - and therefore inheriting the properties of - templates higher in the tree. Templates contain component blocks made up of fields and values. These values can be overridden by templates lower in the tree. Templates can be named in any way you like, as long as there are no spaces and no two templates are named the same. It's recommended also that template names are prefaced with a modder's or group's initials (e.g. gpg_tree_grs_apple_03).

Beyond The Scope Of…
<Back to Table of Contents>

In writing this, it became apparent that there was far more information and intricacies within the topic of templates that would fit within a single 200-level document. As you've read, the "beyond the scope of" phrase was used a couple of times - I hate that too. If enough demand (and time) is there, a 300-level document may be called for and created. Some of the topics that may be covered in such a document:

  • pcontent
  • Veteran & Elite templates - Difficulty Levels
  • Armor - how it works
  • Performance optimization
  • Game Console Command Reference

Although these things are in fact beyond the scope of this first course and are not discussed here, much of the information can be gleaned from the game files themselves. With the base of knowledge learned here, a modder should be able to piece together much of what was not discussed. The modding of games with no documentation whatsoever is testament to the abilities of the mod community!

Have fun, and happy templating!
The Gas Powered Team

Appendix A: Naming Conventions & Siege Editor: Template Sorting
<Back to Table of Contents>

In addition to the naming of the actual templates being important to avoid conflicts with other modders' templates, some naming conventions are important within the templates themselves, within certain fields. The Siege Editor is what we're concerned with here, and these fields will be discussed below.

Category Names
Game Object content (rocks, bushes, trees, a Krug, a bed, etc.) is viewed within the Siege Editor via the Category Name structure. Category names determine the location of objects within the file tree. For example, if you wished to place a bed object in a region, you'd look under Game Objects/furnishings/indoor/, as beds have a category name of "indoor." When creating new object templates, it's highly recommended that you add your object to an existing category. You do this by editing the category_name field within your template.

For example, let's look at the following tree template:

[t:template,n:tree_des_02]
{
   category_name = "trees";
   doc = "des_bamboo_tan";
   specializes = base_tree;
   [aspect]
   {
      lodfi_lower = 0.15;
      lodfi_upper = 0.3;
      model = m_i_des_tree-02;
   }
}
(taken from world\contentdb\templates\regular\non_interactive\natural_trees.gas)

As you can see, this template has a category_name of "trees". This makes it available for placement in the Siege Editor by browsing the tree and opening the Game Objects/landscape/trees/ directory.

NOTE: The category_name field is very important - if an object does not have a category name, it will not be available for placement in the Siege Editor except by doing a content search.

Available Category Names
Generally, when looking at the object tree within the Siege Editor (SE), the subfolder names are the same as the category names for those directories. The various subfolders and their categorized objects are listed below as reference:

Landscape
The following categories sort objects within the Landscape subfolders:
  • dead (all bones, bodies, carcasses)
  • foliage (shrubs, flowers, grass, misc plants)
  • trees (all trees)
  • rocks (all rocks, crystals, ore, gems)
  • misc (all miscellaneous natural items like brush, dirt, logs, branches, etc)
Furnishings
The following categories sort objects within the Furnishings subfolders:
  • indoor (all indoor furnishings like tables, paintings, beds, etc)
  • statue (all statues)
  • outdoor (all outdoor furnishings like carts, tents, tombstones)
  • sign (all signs)
  • container (all nonregion-specific containers like base barrels, crates, jugs, sarcophagi, etc)
  • lighting (all light fixtures like lamps, torches, candles, etc)
Treasure
The following categories sort objects within the Treasure subfolders:
  • treasure (all inventory treasure like gold, gems, jewelry)
  • magic (all magic spells)
  • mbook (all magic books)
  • potions (all potions)
  • quest (all quest related objects)
  • lorebook (all lore books)
Gizmos
The following categories sort objects within the Gizmos subfolders:
  • levers (all levers, buttons, etc)
  • interactive (all interactive objects like health fountains)
  • trigger (all triggers)
  • catalyst (all catalyst type AI commands)
  • target (all target type AI commands)
  • zurb (specialized commands that don't use normal jat/next scid
  • skrev (skrit event specialized commands)
  • effect (all effect commands like camera shakes)
  • nis (all NIS commands)
  • emitter (sound and SFX emitters like fire and smoke)
  • special (misc special features like shrine effects, use points, etc)
  • traps (SFX traps like fireballs)
  • elevator (all elevator gizmos)
  • 1w_generator (all regular world base generators and spawners)
  • 1w_automated (all regular world automated generators and spawners)
Doors
The following categories sort objects within the Doors subfolders:
  • doors (all normal doors)
  • use_toggle (use toggle doors that require an activate to become unlocked)
Actors
The following categories sort objects within the Actors subfolders:
  • 1w_ambients (neutrally aligned creatures like deer, cows, gremals)
  • 1w_evil_a (Primary evil races of Krug, Seck, Droog, and Goblins
  • 1w_evil_b (evil boss monsters like the dragon, fury, giant spider, etc)
  • 1w_evil_c (lesser evil races like skeltons, trog, braak, etc)
  • 1w_evil_d (individual evil monsters like golems, spiders, googore, etc)
  • 1w_character (good NPC's like guards and possible party members)
  • 1w_townsperson (good NPC townspeople)
  • 1w_shopkeep (good NPC shopkeeps)
Nodes
The following categories sort objects within the Node folder:
  • node (all node specific objects like windows, waterwheels, mill vanes, etc)
All category names that begin with "1w" also have "2w" and "3w" versions for the Veteran and Elite types of the same objects.

You might notice that weapons and armor do not have category names at all, and are missing from the above lists. The display of those objects is based solely on their template names, so when creating new templates for these types of objects it's highly recommended you either:
  1. devise (and stick to) your own template naming convention, or
  2. stay within the current naming scheme and use a naming convention similar the existing type of content.
Method 2, staying within current naming scheme, is the recommended route here. Staying within the current naming scheme confines will aid greatly in the ease with which your new objects will show up in SE's template view. When in doubt, you may use the category name of "pending" which sorts objects into the "unsorted" folder. (there is a way to add your own categories by editing se_view_db.gas, but this is beyond the scope of this document)

Doc Names
The doc value (a.k.a. "doc name" or "Development name"), is another very important template field. The value that's entered here is what is seen as the name of the object when in the Development name content display in the Siege Editor. The aim when creating a doc name is to make it as descriptive and as user-friendly as possible. Let's go back to our tree template from above:

[t:template,n:tree_des_02]
{
   category_name = "trees";
   doc = "des_bamboo_tan";
   specializes = base_tree;
   [aspect]
   {
      lodfi_lower = 0.15;
      lodfi_upper = 0.3;
      model = m_i_des_tree-02;
   }
}
As you can see, the doc field value (or "doc name", as it's commonly referred to around here) "des_bamboo_tan" is more descriptive than the template name, "tree_des_02". The doc field must always be present in your templates. If a template is missing the doc field and its value, the object will simply not have a name when you look for it within SE while in Development names content display. With some interactive objects or actors, it is preferable to use the Screen Name (field screen_name) as the Doc Name (field doc), which is generally fine. Your doc names do not have to be named uniquely, but it is generally a good idea to do so.

At times, you may create a template for an object that is intended for use in very specific situations. In this case, it's possible the object requires more information (or even instructions) on how it should be used or where it should be placed. In this case, there is an additional informational field you can use called extra_doc. If we add this field to the tree_des_02 template, it would appear as such:

[t:template,n:tree_des_02]
{
   category_name = "trees";
   doc = "des_bamboo_tan";
   extra_doc = "Only place me in dry, deserty areas";
   specializes = base_tree;
   [aspect]
   {
      lodfi_lower = 0.15;
      lodfi_upper = 0.3;
      model = m_i_des_tree-02;
   }
}
If you open the template properties of tree_des_02 within SE, you will find the extra_doc string displayed in the Documentation field, directly below the Template name. In this way you are providing more information for others who may be placing your content - either on your team, or even other modders.

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_202 Lorebooks

Lorebooks

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

What This Tutorial Will Teach You

Untank Logic.dsres

Show Me A Lore Book!

Lore Book Components

Naming Convention

Category Name

Documentation

Specialization

Texture

Screen Name

Reference

Lore Key

Conclusion

Appendix A: Conveying the Story in the Big Picture

Mouseover Names

Specialized Artwork

Audio Dialog

Text Dialog

Lore Books

Journal Entries

Story Sequences

Introduction

A lore book is an object within the game, which has the physical appearance of a book, and can be traded or manipulated between inventories. The most important aspect of the lore book is the wide format scrollable window. This allows very long text to be written within it and referred to at the player's leisure.

The advantages of creating lore books are many, as the casual nature of the books within an adventure means that the player is not forced to read large volumes of text during important events or encounters, but can instead refer to them when engaging in casual activities, or whenever detailed information is required. Normally, lore books can be discarded or sold just like any other treasure, so it is not a good idea to expect the player to keep a book in their possession to further the plot or quest. If information absolutely must remain accessible to a player during an adventure, that information may be more appropriate in the journal, which will be discussed under quests in later Siege University courses. The primary difference being that the player does not have the option to discard Journal entries if the designer does not wish it.

What You Need For This Tutorial

  • Dungeon Siege, updated to version 1.09B or later
  • A text editor. Notepad will work fine; UltraEdit-32 offers advanced functionality.
  • Untanked resource files, specifically Logic.dsres, if you did not untank in Siege U: 201.

What This Tutorial Assumes You Have Already Learned

  • Siege U: 100 - The Basics of the Siege Editor, specifically sections regarding tanking/untanking maps.
  • Siege U: 200 - Dungeon Siege Concepts and Terminology
  • Siege U: 201 - Templates

What This Tutorial Will Teach You

  • The components of a Lore Book and the steps needed to create your own
  • Additional tools used to convey the story

Untank Logic.dsres

To untank the Logic.dsres into its editable components, follow these steps:

  1. Start the Siege Editor.
  2. Select Convert .dsmap to Files... from the File Menu.
  3. For the Source File, browse to Dungeon Siege\Resources.
  4. Under "Files of type:", switch to Dungeon Siege Resource Files (*.dsres)
  5. Select Logic.dsres and click Open.
  6. For the Destination Folder, leave it at its default setting (My Documents\Dungeon Siege\Bits).
  7. Click OK.
  8. Once the untanking is completed, exit the Siege Editor.

You have now untanked the Logic.dsres file into My Documents\Dungeon Siege\Bits. File paths listed in this tutorial can be found in this directory. If you untanked to a non-default directory, you will have to look there to find the files referenced below.

Show Me A Lore Book!
<Back to Table of Contents>

There are two components to make a lore book whole: the template and the Lore Key. The template resides within contentdb, and contains the information that determines the lore book's appearance in the world, minus the actual text that appears within the book. This template will look roughly like Figure 1.1 below:

Figure 1.1 A sample Lore Book Template:
[t:template,n:book_glb_lore_goldsniffer]
{
   category_name = "lorebook";
   doc = "Goldsniffer Mags";
   specializes = base_book_lore;
   [aspect]
   {
      [textures]
      {
         0 = b_i_glb_book-lore-02;
      }
   }
   [common]
   {
      screen_name = "Remembrance of Mags";
   }
   [gui]
   {
      lore_key = lore_goldsniffer;
   }
}
(taken from world\contentdb\templates\regular\interactive\trs_book_lore.gas)

The second part of a lore book is the Lore Key, which is referred to in the template above as lore_goldsniffer. The Lore Key contains all the formatted text that appears within the book itself, and looks roughly like Figure 1.2 below:

Figure 1.2 A sample Lore Key:
[lore_goldsniffer]

{

description = "A direct descendant of the mule packs imported by
   Etan Stonebridge, 'Mags' was one of the most remarkable animals
   ever to tread the Glitterdelve's stony passages.\n\nPreferring to
   wander the darkness of the mines than be above, she often would
   chew her way free of her restraints, only to be found hours later
   standing near exposed veins of gold. In time, other miners began
   to experiment with 'Goldsniffer' Mags' gold-homing nose, discovering
   to their fortune that she possessed an uncanny knack of gravitating
   to undiscovered deposits of rare metals.\n\nBut Mags could also be
   temperamental, and sometimes demonstrated her pique by leading
   miners to drill barren shafts which the locals began to call
   'Mag Holes.' Beloved for her unusual talent, she easily would have
   been remembered in the history books of the Glitterdelve, but it was
   her actions during the Great Blast of 943 which would forever ingrain
   her into the hearts and minds of her keepers.\n\nFollowing the
   accidental ignition of unvented gases in a newly hewn coal shaft,
   the passages of the mine became a howling death trap of flame and
   flying rock for those trapped within.\n\nWhether disoriented by the
   blast, or truly perceiving what had occurred, Mags broke free from
   her restraints and plunged into the roiling black smoke of the mine,
   even as stunned dwarves and men bolted from the fury of the roaring
   catastrophe.\n\nVanishing from the sight of terrified onlookers for
   nearly a quarter hour, Mags at last stumbled from the mine, dragging
   three half-dead miners by the trailing rope of her broken harness.
   Able only to stagger a few yards from the entrance with her
   tremendous burden, 'Goldsniffer' Mags collapsed in a wheezing
   heap, never to rise again.\n\nHaving pulled the last survivors of
   the Great Blast to safety, Mags died the following morning,
   surrounded by weeping men who knew her for the one-time miracle she
   truly was.\n\nEighteen years later, the monument to her final heroism
   would be constructed at the very spot she died.";

}
(taken from world\maps\map_world\info\lore.gas)

When used together, these two components (template and key) can give properties that make them very much like a normal book you would find in the real world.

Lore Book Components
<Back to Table of Contents>

A discussion of each component broken down from Figure 1.1 and Figure 1.2 above follows:

Naming Convention
Figure 2.1 A sample Naming Convention:
[t:template,n:book_glb_lore_goldsniffer]
As discussed in Siege U: 201 - Templates, the scheme used for naming lore books can follow any convention, but consistency is the key, especially when a large number of these books are to be used in a map. The text that appears in the template is not contained within the same gas file, so if the name does not reflect the content of the book, it will be very easy to misplace the books you have created when it comes time to add them to your world.

Category Name
Figure 2.2 A sample Category Name:
category_name = "lorebook";
Category names determine the location of objects within the file tree of Siege Editor menus.

Documentation
Figure 2.3 A sample Documentation:
doc = "Goldsniffer Mags";
The name you give here is what is seen as the name of the object when in the Development name content display in Siege Editor.

Specialization
Figure 2.4 A sample Specialization:
specializes = base_book_lore;
Specialization is the template component that gives the exact properties to the object, in this case a Lore Book. Unless you want to further specialize the Lore Books you use at a template level, this is the only specialization you will ever need.

Texture
Figure 2.5 A sample Texture:
   [aspect]
   {
      [textures]
      {
         0 = b_i_glb_book-lore-02;
      }
   }
A large number of textures for Lore Books already exist if you want to create a distinctive look to accompany the contents. Those textures can be found at art\Bitmaps\Items\Global. The default texture if none are listed under [aspect] is [b_i_glb_book-lore.psd]. The magic book textures are sized identically, so nearly 30 different textures can be applied to the book you are creating. You can, of course, apply your own textures to apply to the books you create, for placement within the same directory structure.

Screen Name
Figure 2.6 A sample Screen Name:
   [common]
   {
      screen_name = "Remembrance of Mags";
   }
This is the actual text that appears on an in-game mouseover of the Lore Book you are creating. Don't forget the value of a mouseover name. In some cases that name can be more important than the contents themselves, see below for more discussion

Reference
Figure 2.7 A sample Lore Key Reference:
   [gui]
   {
      lore_key = lore_goldsniffer;
   }
This is the name used to connect the template in trs_book_lore.gas to the actual text that appears in lore.gas under the directory structure of the map you are creating.

Lore Key
Refer to Figure 1.2 above

The Lore Key is linked to the template by the Lore Key Reference block at the top.

The text block of the Lore Key can have any amount of text. Keep in mind that the size limitation of the Lore window will make anything over 2,000 characters appear rather daunting to the player, and possibly cause him or her to lose interest in your written material.

Formatting of the Lore Key block can be accomplished by adding spaces before and after text within the block, and by using the 'carriage return' character [\n] within the text itself. All other forms of formatting will be rejected by the engine when used in the Lore Key block.

To create separated paragraphs, simply use a double carriage return [\n\n] at the beginning of the next paragraph. To create large indents, just include several spaces within the body of the text.

Conclusion
<Back to Table of Contents>

Creation of the Lore Book only requires some simple editing of two separate gas files. The first file is the template, wherein properties of the book can be adjusted. The second file is the Lore Key, which contains all the text that will appear within the book.

Lore books are a very simple way to convey story and background into your world, without the additional work associated with timing of dialog and Story Sequences. Lore Books can contain an unlimited volume of text, and can be placed and transferred just like any other inventory object in game, making them an excellent choice for worlds that have a lot of story to tell.

Appendix A: Conveying the Story in the Big Picture
<Back to Table of Contents>

Conveying story in your Siegelet can be accomplished in many ways, and you do not need to use all the tools available, but should consider switching out different methods to keep it interesting for your readers/players.

In general, the primary ways to powerfully convey story are: mouseover names, specialized artwork, audio dialog, text dialog, lore books, journal entries, and story sequences. A short discussion of each follows:

Mouseover Names
An often-overlooked device, mouseovers can convey a large amount of meaning to a category. Whether it is on an item or an NPC, the name they have can quickly convey whether they are friendly or enemy, and what action should be taken with them. An example would be using different mouseover names on a normal box. If the text that appeared when mousing over the box said 'Secula's Podium', that would convey a very different meaning and required action than if it said 'A clean crate', or 'A box of explosives'. The same goes for NPCs, where 'A wounded skeleton' or 'An aging peasant' might be able to convey something that the animation or posture does not. This should not be relied on heavily, as some players will not pay attention to the mouseovers, and would therefore miss the point. Consider using these names to add flavor for the attentive players, but do not depend on them to convey all your meaning.

Specialized Artwork
Creating legible text in artwork is very difficult when all possible resolutions are taken into account. What can be conveyed are important recognizable signals like a wanted poster, or a simple nameplate. Consider using specialized artwork when you have to convey a recognizable image in your world.

Audio Dialog
The premium map will include plenty of audio dialog. Although expensive, large, and/or time consuming, this is a widely accessible way to convey story. The best places to use dialog are for grandstanding and posturing in key encounters, background narration, and with storytellers.

Text Dialog
Second fiddle to Audio Dialog, text dialog can fill many of the same roles but with much less emotion. Some of the limitations on text dialog are: the difficulty in using it for enemy encounters when a click box would be inappropriate, and in places where you want to convey story during a surprise. The more casual players can also avoid text dialog. There is no guarantee that players will actually see it as they play through.

Lore Books
Lore Books are excellent for displaying large amounts of text in a casual manner. This shares the disadvantage of avoidance that is found in Text Dialog, but can be called upon at any time. If the relevance of a book is highlighted somewhere in the story, players will be more likely to hold onto them as they play through.

Journal Entries
Tightly woven in with Quests are Journal Entries. The Journal does not have to remain specific to Quests if the designer so desires, and can instead be used to keep important points at the player's easy reference later on.

Story Sequences
Action scenes can efficiently convey emotion and/or gravity of a situation, and are a very effective way to show off some of the nicer looking areas of your world. Story Sequences will not currently function in multiplayer games, so these are in the realm of the single-player experience.

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_203a Triggers I

Triggers I

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

What This Tutorial Will Teach You

Creating a Test Map

What Is A Trigger?

The "trigger_generic" Gizmo

Placing The Trigger

Trigger Properties

Editing Our First Trigger

Some Crucial Concepts (And More Triggers)

Sending A Message (Wiring)

Catalyst Trigger (Result of Condition)

Conclusion

Introduction

Triggers are extremely flexible and are connected to many other facets of the game. Once you start understanding triggers, you will be able to create everything from a simple mood change to complex scripted sequences and Non-Interactive Sequences (in-game movies)-and you don't need to know a whit of Skrit to do it!

However, "understanding triggers" is really about understanding how much of the game works. Triggers make things happen, but your ability to use them will grow as you develop a fuller understanding of what is possible and available to you (in the form of actors, gizmos, objects, nodes, animations, effects, lights, sounds, commands, etc). This will take time, so be patient, and it never hurts to study existing maps to figure out how certain things were done. We'll try to touch on a wide range of applications in this tutorial.

What You Need For This Tutorial
  • Dungeon Siege, updated to version 1.09B or later
  • Siege Editor
  • Siege Editor Manual (included with the Siege Editor during installation)
  • A test map you can practice with. See below for instructions on creating a simple test map.
What This Tutorial Assumes You Have Already Learned
  • Siege Editor Manual (especially the gizmos and triggers section. The manual has an introduction to the subject of triggers, and we'll be making a lot more sense of it with this tutorial.)
  • Siege U: 100
  • Siege U: 200 - Dungeon Siege Concepts and Terminology
  • Siege U: 201 - Templates. This is useful in understanding the concepts of template blocks, components, and properties.
What This Tutorial Will Teach You
Pull up a chair! You are about to learn about a very powerful object in the Siege Editor (SE). Lesson 203A will start with the basic concepts behind triggers and their basic applications. This includes:

  • What Is A Trigger?
  • The trigger_generic Gizmo
  • Trigger Properties
  • Basic Conditions And Actions
  • Sending A Message
  • Catalysts And Result Of Condition
  • Some Trigger Examples
Lesson 203B will go into more depth on trigger topics, including:
  • Conditions and Boundary Checks
  • Actions and Messages
  • Multiple Trigger Instances On A Single Gizmo
  • Trigger Instances On Other Gizmos
  • Other Triggers
  • Guidelines And Errata
Creating a Test Map
Let's create a simple test map quickly in SE. This map will be extremely useful for this and other Siege U tutorials. It will load quickly and give you a flat area to test just about anything.
  1. Open SE.
  2. Create a new map. Call it "test". Leave the Advanced settings at the defaults.

  3. Create a new region. Call it "flat_area". For the Starting Terrain Node, use dungeon_2/floor/t_dgn02_flr_08x08-v0.sno.

  4. Once the region is created, add a ring of the same floor nodes around the starting node. When you are done, you should have a 3x3 square.

  5. Save.
  6. Tank the map. Select Save Map as .dsmap from the File Menu, select the "test" map, leave the Destination File set to the default, and click OK. On the map details screen, only edit the "Title", "Author", and "Copyright" fields. Click Start.
  7. Once the map is completed tanking, close SE and test the map in Dungeon Siege.
Now you have a map for any lesson that calls for a test map from now on!

What Is A Trigger?
<Back to Table of Contents>

Simply put, a trigger is a Siege Editor gizmo that makes something happen in the game when you want it to happen. To accomplish this, a trigger does two things:
  1. Tests to see whether certain conditions are true, and
  2. If true, executes certain actions.
A condition is a predetermined requirement or set of requirements that must be met before the trigger's actions will be executed. Conditions include testing whether a certain kind of object has entered, exists within, or has exited a specified area; whether the trigger has received a particular message from another object or the game itself; or both.

Actions are what the trigger does when its conditions are tested "true". Actions include the direct execution of a number of functions and the sending of messages. Messages are sent via the game's message delivery system to other objects in the game that are waiting for them, such as other triggers, gizmos, and actors.

A trigger can tell the game when it should be "tested", i.e. when exactly the game should ask the trigger whether or not its conditions are "true". The trigger can also tell the game who should be affected by the trigger's action (if the action requires a target, and the target is not known in advance. This will make more sense when we talk about "result of condition").

Finally, the trigger has a number of properties that affect how it functions in the game, such as whether or not it exists in Single Player (SP) or Multiplayer (MP) games, whether it "fires" once or as many times as its conditions are true, whether it lies dormant until activated by another event, whether it must wait a specified time until its conditions can be tested again, etc.

Triggers are crucial to creating dynamic events in the game. Here is an example list of events that triggers can help you execute:
  • Change a fade or mood for a party member
  • Start or stop a sound or special effect
  • Turn on or off lights and light effects
  • Activate or deactivate a door, chest, switch, or elevator
  • Activate or change a quest or dialogue state
  • Spawn a monster or change its alignment, or give it commands
  • Set off a chain of other triggers and gizmos for scripted events
We're going to start learning about triggers by working with the "trigger_generic" gizmo.

The "trigger_generic" Gizmo
<Back to Table of Contents>

Placing the Trigger
Let's prepare SE for our session. If you have questions about where certain buttons and functions are located, refer to the Siege Editor Manual.
  1. Start SE and load your test map for editing. It would be good to have your start position very close by so you don't have to run a long way to get to your test area in-game.
  2. Ensure you can view and edit both Objects and Gizmos.
  3. Turn on "Auto Snap to Ground" (Object Menu | Mode).
  4. Turn on "Placement Mode" (Object Menu | Mode).

  5. Ensure your Content Display mode (View Menu | Preferences) is set to "fuel block" or "development name".

  6. Go to View Menu | Debug HUDs and set Group to "none". Check all Option boxes and "Single Object". Click OK. (We're making it so you only see Debug HUD info for the object you have selected instead of all of them, all at once.)

  7. Expand the Game Objects/gizmos/triggers folders in the left window.
  8. Select the "trigger_generic" gizmo.

  9. Left-click on an empty area of your test map. A six-pointed white star should appear on the ground.
  10. Turn off Placement Mode so you don't keep placing triggers accidentally.
(From here on out, I won't specifically tell you to turn object placement on/off, or for that matter, give you instruction on routine SE operation. Feel free to use SE in a way that is comfortable while still following the tutorial.)

So there it is! You should see a white six-pointed star just sitting there on the ground-that's the trigger_generic gizmo. There should also be a green square around the trigger, which means it's currently selected. In addition, you should see some white text floating above the trigger, which is part of its Debug HUD info. For now, you should see the object name (trigger_generic), and some identifiers that SE just assigned to it… its SCID and GOID.

Trigger Properties
Right-click on the trigger and select "Properties" from the menu (or select the gizmo and press the default hotkey "V"). A large window with multiple tabs across the top will appear. These tabs appear for every object in the game, but they are not actually used for every object. For the trigger_generic, we will only ever use the first two tabs: "Template Properties" and "Trigger Properties".

The Template Properties Tab
Select the "Template Properties" tab if you haven't already. This page is similar for every object in the game. At the top of this page are several information fields, plus two checkboxes.

  1. The Template Name field shows the template name of this object, in this case "trigger_generic". Note that this is a pull-down menu. You can actually change the type of object this is by selecting a different template name from the list, but it's dangerous to do so. Let this field be purely informational and you should be fine.
  2. Scid shows the assigned SCID (Static Content IDentifier) of this object. You can't change it, but you can copy/paste the value. We will use this value a LOT.
  3. Screen name shows what text string would appear in-game if a player could select this object. Since triggers are never visible in-game, this value is empty. It cannot be changed.
  4. Documentation usually shows a sentence or two on the function of this object. These are informal and not always 100% accurate. This field cannot be changed.
  5. Finally there are two checkboxes: "Hexadecimal" and "FourCC". You can only have one of these selected at a time, or none. These boxes force the display of numbers on the "Trigger Properties" tab to be shown in hexadecimal, or in "FourCC", which is a special code specific to animation chores for actors. Selecting "FourCC" when viewing animation chores on the "Trigger Properties" tab will show them as actual words instead of a number string. When neither box is checked (default), numbers appear in their default form, either hexadecimal or real numbers.
The rest, and biggest, part of the Template Properties tab is the "Component Fields" section. This section appears for every object in the game, but the fields will be different, depending on that object's template. For the trigger_generic, we should see the [common], [gizmo], and [placement] blocks, which are generic blocks that every object has.

What we are looking at are key/value pairs defined in the trigger_generic template. The "Field Name" column shows the key, and the "Value" column defines the value. Right now, each field is showing the default values defined in the template. The last column is a description of the key/value pair.

There are only a few fields we will ever (usually) modify on the trigger_generic. They are:

[common]
  • dev_instance_text: This allows you to set a documentation string for this particular trigger instance, which is visible only in SE.
  • is_multi_player: Sets whether or not this trigger gizmo exists in a MP game (true/false).
  • is_single_player: Sets whether or not this trigger gizmo exists in a SP game (true/false).
[gizmo]
  • diffuse_color: This is an RGB value for the trigger gizmo, only visible in SE. This is helpful to visually organize groups of triggers with different functions. The value for each color component ranges from 0 to 1 (not 0-255 as found in most art programs). The default value (1,1,1) is white.
The Trigger Properties Tab
Select the "Trigger Properties" tab.

This tab is where all the work of triggering is done. There are four main sections:

  1. Triggers: The white box on the left side of the window will list every trigger instance inside this trigger gizmo. It is possible to have multiple trigger instances inside a single gizmo, all working at once. The "New" button below this window will add a new trigger instance, and the "Remove" button will delete the selected instance.
  2. Trigger Properties: This box holds a selection of checkboxes and fields that can be set for each trigger instance. More on these later.
  3. Conditions: This window will list all Conditions that you set for this trigger. For a trigger to function, it must have at least one Condition. The "Add Condition" and "Remove Condition" buttons to the right of this window are self-explanatory.
  4. Actions: This window will show all the actions that this trigger will perform when its Conditions are true. The "Add Action" and "Remove Action" buttons to the right of this window are again self-explanatory.
Editing Our First Trigger
Let's make this trigger do something very basic: when the player walks near it, a special effect will play. It will be easy to tell if it works or not, since if it doesn't work, we won't see any visible effect.

Set the Name
First we'll give this trigger a name that will be visible in SE. Go to the "Template Properties" tab in the gizmo's Properties window, left-click in the blank "dev_instance_text" value field, type "MY TEST TRIGGER" (using all-caps makes the dev name more visible in SE), and hit Enter. Notice that the "override" box is now checked. This means that this particular field has been overridden in this instance of the trigger, i.e. it is now different from the default template. If you un-check the box, the field will be reset to the template default. Leave it as-is for now, and we'll leave the rest of the fields alone as well.

Click "Apply", then "OK", and look at your trigger.

You should see the words "MY TEST TRIGGER" floating over the trigger gizmo (if not, make sure it is selected). It is good practice to always use dev_instance_text strings to label your triggers, so you and others can make sense of them later without having to open each one.

Create the Trigger Instance
Now that our trigger gizmo is placed, we need to create the actual trigger instance inside of it. This is an important distinction to make: the white-star trigger gizmo we have placed doesn't actually DO anything. Its sole purpose is to be a visual representation (or "aspect") in SE. The trigger "aspect" (a SE-only mesh object) needs to have trigger instances (logical condition/action data) inside of it to perform its function. We will find later that trigger instances can live inside any kind of object, not just trigger gizmos, but we'll stick with this for now.

Go back into the trigger's Properties and select the "Trigger Properties" tab.

We won't be able to do anything here until we've created a trigger instance. If you look in the "Triggers" window (the tall white one on the left side), you will see the word "trigger_generic" there. That is the name of this gizmo (the one we have selected), in which we will create trigger instances. Use the scroll bar at the bottom of the window to scroll right, and you will see that the "trigger_generic" line also includes this gizmo's GOID and SCID.

In the bottom left-hand corner of the window, below the "Triggers" section, are two buttons: "New" and "Remove". Click "New".

Notice that now, next to the "trigger_generic" name at the top of the window, there is a "+" (plus) sign. Click it to expand the trigger_generic's new contents. A new line, underneath the first and connected to it, now reads "trigger_0". This is the first trigger instance that we've created inside the trigger_generic gizmo.

Set the Trigger Instance's Properties
Click on "trigger_0" to select it. You must select a trigger instance to work on it, and you can only select one trigger instance at a time (even when you are viewing the properties of multiple gizmos at once).

Note that the "Trigger Properties" section of the window has now become editable! For now, let's leave them as they are. We'll go over these in detail later.

Set the Condition(s)
Click the Add Condition button. The "Add Trigger Condition" box will come up, featuring a drop-down menu. The drop-down menu lists every available Condition, and we'll go over them in more detail later. For now, since we want our trigger to activate when the player walks near it, select the condition "party_member_within_sphere" and click OK.

Inside the "Conditions" window has appeared a small spreadsheet table. This is a single Condition entry. The "Name" field should show "party_member_within_sphere". The next column shows the "Parameters" of the party_member_within_sphere condition, consisting of "Radius" and "Boundary Check". The "Value" column shows the actual values of those parameters. The parameter and value pairs are unique to each type of condition, while the rest of the columns--"Group", "Doc", and "ID"--exist for every condition. We'll cover those last columns later.

This condition checks for a party member (player's characters) within a sphere shape, centered on the gizmo itself. As such, it is a "spatial condition", i.e. the condition relies on the detection of a spatial area in the game world. The "Radius" field defines how wide the sphere actually is (2.5m radius, so 5m diameter), and the "Boundary Check" defines HOW (and WHEN) we use the sphere to check the condition.

In this case, the Boundary Check is set to "on_every_enter". This means that, every single time a party member ENTERS the sphere (crosses the boundary INTO the sphere), the condition will be tested to see if it is true. Even if the player stands inside the sphere, the condition will not be tested again until another player character ENTERS the sphere, or until he leaves and re-enters.

A trigger's actions are not executed until a condition is tested true, and in the simplest case, the action is executed at the EXACT moment the condition is tested true.

If you double-click in the Boundary Check value field, a drop-down menu will appear. These are all the boundary checks available. Leave it at "on_every_enter" for now, and we'll talk about the rest later. Feel free to edit the Radius value if you wish, but 2.5m is fine.

Click OK and look at your trigger. You should now see a red circle rotating around it. This red circle is the equator of the sphere you defined in the Radius value (if you don't see it, make sure the trigger is sitting on the ground and not slightly below it, or make sure your Debug HUD settings match those suggested at the top of this tutorial). If necessary, move your trigger to an area where your character can walk into the circle.

It is possible to have multiple conditions for a trigger instance, but the rules are very specific. We'll get to that later.

Now that the condition is set, let's set an Action!

Set the Action(s)
Re-open the trigger's properties, go to the "Trigger Properties" tab, and click the Add Action button.

Another drop-down menu appears, similar to the "Add Trigger Condition" box, except this one lists all the actions available. There are quite a number of these, and we'll go over them later, but for now let's select "call_sfx_script". This is what will play the special effect when the player enters the sphere-but never actually use this action in a real map for players. It's useful for testing and debugging, but doesn't work reliably in a MP environment. There are other gizmos to play special effects properly.

After you click OK, you will see a spreadsheet table in the "Actions" section. Similar to the Condition block, there are "Name", "Parameter", "Value", "Group", and "Doc" columns. Unique to the Action block are the "Condition" and "Delay" columns.

For now, we'll do two things:
  1. In the "SFX Script Name" value field, type "geyser_red". That's the name of a particular special effect. It will look like a red geyser shooting up from the ground with a "whoosh" sound effect.

  2. In the "Doc" column, type a description of this action in the first row field (only the first row's field is actually the Doc field). This is informative only, and does not appear in the game. Like the dev text on the gizmo itself, it is good practice to document each of your actions so you know what they are doing. For this one, type something like "activate red geyser".

Click OK. We now have a completed trigger. Every time any player character enters the sphere defined in the Condition section, a red geyser special effect will be activated (defined in the Action section). For the record, it is extremely easy to have multiple actions per condition, but we'll leave it at one for now.

Test the Trigger
Save your map. When the save dialog comes up, ensure that "Save Game Objects" is checked. If this is not checked, the changes you just made to the trigger gizmo will not be saved.

Before we look at the trigger in the game, it might be a good idea to put an object on the ground to serve as a landmark. Since the trigger gizmo is not visible in game, it might be difficult to find without some kind of identifying terrain feature. Put down a barrel, or a rug, or move the trigger next to a tree. Be sure to save objects again.

Tank your map. If you have forgotten how to do that, refer to Siege U: 100.

Open your test map in the game. Walk your character into the trigger sphere, and the red geyser sfx should play! Note that this will play each time you cross the sphere boundary INTO the sphere (on_every_enter). It does not play while standing inside the sphere or when leaving its border.

Some Crucial Concepts (And More Triggers)
<Back to Table of Contents>

In order to more fully understand the basics of triggers; let's create a few more. We'll go through the same steps as above, but more quickly--and also cover more crucial concepts.

Sending a Message (Wiring)
Concepts covered:
  • Basic wiring
  • Gizmo: activateable sound emitter
  • Condition: party_member_within_bounding_box
  • Boundary Check: on_every_leave
  • Action: send_world_message
The trigger we are going to make is a very common kind of trigger-one that sends a message. The only reason we didn't create one of these in the previous example is that this one uses two gizmos instead of one (a trigger gizmo to send the message, and another gizmo to receive it). Sending messages is one of the trigger's main jobs, and unless you are mostly setting fades and moods, you will use this all the time.

We're going to make a trigger that sends an "activate" message to a second gizmo when a player walks into a bounding box. This second gizmo will play a sound when the trigger sends the message.

Place the Objects
Open your test map in SE and place a new trigger_generic. In addition, place an activateable sound emitter nearby (Game Objects/gizmos/special/t_emt_sound_act). A warning box will appear saying "Sound emitter used in 'emt_sound_act' has no sound configured!" which is fine. Click Ignore Once until it goes away.

Configure the Sound Emitter
The activateable sound emitter's job is to play a sound. It is an "activateable" emitter because it can wait until it receives a message to play the sound, instead of starting as soon as it is loaded into the world. That message will come from our trigger.

We have to tell the emitter which sound to play, so select the emitter, access its Properties ("V"), and select the "Template Properties" tab. Notice there is a new template block in the "Component Fields" section compared to the trigger_generic: [sound_emitter_act]. Set the following values:
  • Start_on_creation = false
  • Repeat = false
  • Event_sound = quest_complete

Click OK. We've just made it so the sound will not start when the emitter loads into the world, and it won't keep repeating once it's activated. We've also told it which sound to use ("quest_complete").

Note: Sound emitters don't take .wav file names to play, but instead take a name listed in "\world\global\sounds\sounddb.gas". It's beyond the scope of this tutorial to talk about how to properly enter new sounds into the sounddb, but there are many sounds already included for use. Refer to Siege U: 210 - Sounds for more information.

Configure the trigger_generic
Open your new trigger_generic and create a trigger instance. Add the condition "party_member_within_bounding_box". Instead of a sphere, we're going to define a rectangular area around the gizmo.

Set the "Half diag X" field to "5", creating a wide rectangular shape 5m wide. Set the Boundary Check to "on_every_leave", which will cause the trigger to fire as soon as a party member leaves the bounding box (after having first entered).

Click Add Action and select the action "send_world_message". This is one of the most common trigger actions. The default message type, "we_req_activate", is the most common message to send, and many objects expect to receive it. Luckily for us, we_req_activate is the exact message we want to send… but if you're feeling brave, double-click on the "Message Type" value field to see a list of all the messages that the game's message delivery system is capable of handling. Make sure you set it back to "we_req_activate" if you accidentally select a different one.

In the "Doc" field, type "activate sound emitter". Just getting into a good habit here. (While we're at it, go back to the "Template Properties" tab and type "PLAY SOUND" into the "dev_instance_text" field. Switch back to the "Trigger Properties" tab again.)

The most important parameter in the "send_world_message" action is "Send to Object". Note that there is a hexadecimal number there: "0x00000000", or zero. This field tells the action WHICH OBJECT to send the message to, and the way it refers to that object by the object's SCID.

[Every object (not node) in SE has a unique SCID, or "static content identifier". SE uses the SCID to uniquely identify each object it knows about-the SCID is essentially its name.]

"Wire" the Trigger to the Emitter
There is a quick and easy way to grab an object's SCID:
  • Click OK to close the trigger Properties window.
  • Select the sound emitter (the red arrow).
  • Press "G" to copy the selected object's SCID to the clipboard (or right-click and choose "Copy Scid to Clipboard")
  • Re-select the trigger, go into its Properties, and paste (CTRL-V) the SCID into the "send to object" value field.
  • Click OK to close the trigger window.

If you pasted the correct SCID into the "send to object" field, you will now see a GREEN LINE arching from the trigger to the emitter gizmo. A small, animated arrow travels along the line to show which way the message is traveling. Above the midpoint of the line is listed the message name. In addition, you should see a light-blue rectangle shape around the trigger, showing the area of the bounding box.

Congratulations! You have just connected, or "wired", your first trigger! Note that if you select the trigger and the emitter in turn, you will see different "HUD info" for each. In this way you can interpret what each gizmo in the chain is doing.

Test the Trigger
Again, save objects and load the map in the game (don't forget to tank it before exiting SE!). Every time your character enters then LEAVES the bounding box, the quest update sound should play. If it doesn't, check the above steps again, and make sure the emitter is next to the bounding box (because if it's too far away, you won't hear it).

Next we talk about another kind of trigger: the catalyst trigger.

Catalyst Trigger (Result of Condition)
Concepts covered:
  • Catalysts and Result of Condition
  • Gizmo: Catalyst move command
  • Condition: party_member_within_node
  • Boundary Check: on_first_enter
  • Action: send_world_message w/ result of condition
Our next trigger will give any party member a command to walk to a waypoint when they enter a particular node. This will require the use of a new concept--the "catalyst", or "result of condition"--to solve a particular problem.

The problem is twofold:
  1. In order to tell the player to move to a new location, we have to know which player to command (since you can have multiple-character parties). However, ANY party member can walk into the node at any time, not just one. We won't know who will walk into it until they actually do, and we need a way to capture their identity so the Action knows who to interact with.
  2. Any object that is not directly placed in SE (like the starting player character, or monsters spawned from a monster generator) doesn't have a SCID. This means that we can't reference the starting character by SCID. If we can't refer to the player character by SCID, how can we send commands to it?
The answer is "result of condition".

Place the Objects
Place a new trigger_generic. Enter "WALK TO WAYPOINT" in its dev text field. Place a "catalyst move command" several nodes away, but still within the frustum (\Game Objects\gizmos\commands\ai\catalyst_commands\cmd_c_move). Copy the SCID of the move command.

All the catalyst move command does is provide a waypoint for a catalyst (the guy who walked into the trigger area) to move to. The command doesn't know who the catalyst will be, nor does it care. It can be a player, an NPC, a monster, or any actor that can move. We don't need to configure this gizmo at all, since its function is entirely dependent on the trigger's condition.

Configure the Trigger
Open the trigger_generic and create a trigger instance. Add the "party_member_within_node" condition. Set the boundary check to "on_first_enter", which will cause the trigger to fire ONLY THE FIRST TIME someone walks into our target node. Add a "send_world_message" action that sends the "we_req_activate" message, and paste the catalyst move command's SCID into the "send to object" field.

Click OK and make sure the trigger is wired to the move command. It's also a good idea to save objects at this point. Re-open the trigger and look at the party_member_within_node condition.

Sidebar - party_member_within_node
This isn't related directly to our main topic of catalysts and result of condition, but since we're trying to cover a variety of conditions in this tutorial, let's talk about party_member_within_node. The last two spatial conditions we used were tied to the gizmo's position, and created either a sphere or rectangular shape relative to it. With this condition, we aren't specifying an area around the trigger; we're specifying a particular node somewhere in the world.

There are four parameters plus a Boundary Check for this condition: "Region ID", "Section", "Level", and "Object". Rather than specifying a node's unique GUID, we will specify the region that the node appears in, and then define a series of arbitrary masks (that match existing masks already assigned to nodes) to narrow down the node range. In this way, the "node" we are detecting can actually be a single node, a number of nodes, or the entire region itself (all the nodes). This will make more sense by doing it!

With the Object Properties window still open, go to "Settings Menu | Region" at the top of the main SE window. At the bottom of the dialog that pops up is the "Region GUID". Copy this hexadecimal value to the clipboard, then click Cancel.

Go back to your Object Properties window and paste the Region GUID into the "Region ID" field of the condition. At this point, your condition is detecting any party member who enters the entire region, since the Section, Level, and Object masks are "-1" (all). We're going to narrow this down to one node, but click OK for now.

Turn on Node Editing Mode (icon on the toolbar), and click on the ground beneath your trigger (it is not crucial that your trigger sits on the very node it's referencing, but it is good form. Usually you want both to be in the frustum when the trigger fires). A blue shape defining the node should appear. Turn off Node Editing Mode.

Select "Node Menu | Properties" and select the "Fade Settings" tab. Here we see the same masks from our condition: Section, Level, and Object! These three settings are arbitrary hierarchical masks.

They are arbitrary in that the game doesn't care what numerical values you enter; it doesn't use them except to match them up to other numbers we enter elsewhere (like our party_member_within_node condition).

They are hierarchical in that "Section" is the broadest identifier (there can be multiple Sections per Region), followed by "Level" (there can be multiple Levels per Section), and then "Object" (there can be multiple Objects per Level).

Finally, they are masks in that selecting a node Section of number, say "2", will select all nodes assigned a Section number of "2", regardless of their individual Level and Object numbers. Selecting a node Section and Level of "2, 1" will select all nodes with a Section of 2 and Level of 1, regardless of their individual Object numbers.

The default values are 1, -1, -1. "-1" itself is a mask, which basically means "all values". The default values mean that this node isn't affiliated with any Section, Level, or Object assignments.

Let's set the "Section" value to "2". Click OK.

Save, and this time make sure that "Save Nodes" is checked. If it isn't, the node mask you just set will not be saved, and your trigger won't work.

Go back into your trigger, and set the "Section" value there to "2". Now your trigger will detect any party members entering any nodes in this region with a Section value of 2. Since we only gave that Section number to one node, we are now truly detecting "party_member_within_node".

Our trigger now detects players walking into our target node and the catalyst move command will get a "we_req_activate" message from the trigger. But nothing will happen, because the catalyst move command needs to know who walked into the trigger (the catalyst) in order to tell them to move.

Catalysts
Triggers have the ability to pass the GOIDs of its catalysts to the Action, which sends them along with its message to the target SCID. Confused? Let's define some terms.

Every object has a unique GOID (Global Object Identifier). The GOID is how the game keeps track of all objects in the game. It is distinct from a SCID in that a SCID is used by SE to link objects together during editing. Not all objects have a SCID, but all objects have a GOID.

The catalyst is whatever object or objects causes the trigger's condition to test true. In other words, the catalyst is the "result of the condition". In our case, the catalyst will be the party member who enters our target node.

So, when our catalyst, the party member, walks into the node, the trigger must know to collect the GOID of that party member and pass it along to the catalyst move command with the "we_req_activate" message. When the catalyst move command receives the message, it also knows WHICH object to move to its location.

Configure the Action with Result of Condition
Back at our trigger's Condition block, notice the "ID" field to the right and the value in it. The ID will look something like this: "0x4001". This ID is unique for each condition inside a particular trigger instance. Select and copy this value.

Paste the condition ID into the "Message broadcast" field of the "send_world_message" action, where it currently says "default". This ensures that the result of the above condition, the GOID of the catalyst, will be sent along with this message to the catalyst move command.

[NOTE: As of v.1.09b, there is a bug with pasting condition ID's. See below.]

[BUG: Sometimes, when creating or editing a trigger instance, the condition ID listed in the condition block will change. The change is not visible until the map is reloaded into SE. This is usually not a problem, UNLESS you've pasted that ID value into an Action's "message broadcast" field below. When the condition ID changes, the pasted value in the Action doesn't change with it, and DS/SE will throw up a scary-looking error (and the trigger won't load in the game). To fix the problem, re-open the offending trigger, copy the new condition ID, and paste it back into the "message broadcast" field, over-writing the old value. Click OK, save objects, and everything will work again. To prevent this from happening, every time you edit a trigger instance with a result of condition in it, save your work, close the editor, and then re-open that region. If the bug is present, you'll get an error, and you can go in and fix it.]

Test the Trigger
Click OK and save objects. Load the map in the game, and walk into the target node. As soon as your player crosses into the node (on_first_enter), he will receive a new move command and walk to the location of the catalyst move command. If he walks back into the target node a second time, the trigger will not fire due to the "on_first_enter" boundary check. To test your trigger again, just quit the game and re-start it.

NOTE: You may see your character walk to the waypoint, then walk back to where you originally clicked. That's because the catalyst move command's default behavior is to insert itself into the character's command queue instead of overriding everything. Nothing to worry about for now. We may cover that in more detail in a future Siege U 300 tutorial.

Usually, when a trigger is set to only fire once, you will want to check the "One Shot" box in the "Trigger Properties" section of the "Trigger Properties" tab. This ensures that the trigger gizmo will be unloaded from memory after it fires, saving computer resources. We'll talk more about this in Siege U: 203B.

Conclusion
<Back to Table of Contents>

We've covered the basic interface of the "trigger_generic" gizmo, and experimented with different kinds of spatial conditions, boundary checks, and gizmos. You should have a basic understanding of the mechanics of placing triggers and setting its properties, and have an introduction to some basic concepts.

In Siege U: 203B, we'll talk about another kind of trigger, the "message handler", and then dig into the Conditions, Boundary Checks, Actions, and Messages available to us. Finally, we'll talk about some technical issues and recommended techniques.

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_203b Triggers II

Triggers II

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

What This Tutorial Will Teach You

Overview - Spatial Conditions and Message Handlers

The Pure Message Handler

Overview

Practical Exercise

Moving On

Mixed Conditions (Message Handler with Spatial Condition)

Overview

Practical Exercise

Why Use a Mixed Condition?

Conclusion

Appendix A: A Detailed Look at the Trigger Properties Tab

Appendix B: Messages

Introduction

Here we continue the learning we began with Siege U: 203A - Triggers I. In this lesson, we'll learn about two different kinds of "Message Handler" Conditions (Pure and Mixed), and then we'll go over Conditions, Boundary Checks, Actions, and Messages in more depth.

What You Need For This Tutorial
  • Dungeon Siege, updated to version 1.09B or later
  • Siege Editor
  • Siege Editor Manual (included with the Siege Editor during installation)
  • A test map you can practice with. You should have one from Siege U: 203A.
What This Tutorial Assumes You Have Already Learned
  • Siege U: 203A - Triggers I: If you haven't read this lesson, stop right now and read it first.
What This Tutorial Will Teach You
This tutorial will be necessary in understanding the following future tutorials (numbers subject to change):
  • 204: Moods
  • 205: Fades
  • 206: Elevators
  • 207: Non-Interactive Sequences
  • 209: Quests

Overview - Spatial Conditions and Message Handlers
<Back to Table of Contents>

There are basically two kinds of trigger Conditions: Spatial, which we've learned a bit about in Siege U: 203A, and Message Handlers.

To reiterate, Spatial Conditions test to see whether a particular kind of object (party member, actor, go) is entering, is within, or is leaving a specified area (be it a sphere, a cube/rectangle, or a set of nodes).

A Message Handler tests to see whether the trigger has received a particular Message. As soon as it receives the message, it tests true. As mentioned in Siege U: 203A, Messages are sent via the game's message delivery system to and from both objects and the game engine itself.

There is a third kind of trigger Condition: one with both a Message Handler and a Spatial component together. We'll talk about these "Mixed" Conditions after we understand Pure Message Handlers.

The Pure Message Handler
<Back to Table of Contents>

Concepts covered:
  • Pure Message Handler
  • Condition: receive_world_message (we_entered_world / we_req_activate)
  • Message Check: on_first_message / on_every_message
  • Gizmo: light_enable
  • Light: Point Light
  • Delays on Actions
Overview
We're going to make a small trigger chain that turns on a light source five seconds after the trigger enters the world. We'll use two triggers, both of which will be "Pure Message Handlers".

A "Pure Message Handler" is simply an instance trigger whose only Condition is a Message Handler. Only one Condition is classified as a Message Handler, and that is "receive_world_message".

Practical Exercise

The "receive_world_message" Condition
Bring up your test map and place a single trigger_generic gizmo. Give it a trigger instance and add the Condition "receive_world_message".

This Condition looks for the message specified in the "World Message Event" parameter, and tests "true" when it is received, depending on the Message Check (similar to the Boundary Check, except we're not dealing with boundaries since this isn't a Spatial Condition). Message Checks for Message Handlers are easy, since there are only two: "on_first_message", and "on_every_message". The Message Check determines whether the Condition will test "true" on either the first receipt of the specified message, or on every receipt.

Configure the First Trigger
The desired behavior we want for this trigger is to test true every single time it enters the world. We can do this by testing for the "we_entered_world" message with an "on_every_message" Message Check.

SIDEBAR: "WE_ENTERED_WORLD": Any time you want to test to see if an object has been created, you should always test for the "we_entered_world" message. EVERY object receives this message when it has become a valid game object capable of sending and receiving messages and doing everything a valid game object can. DO NOT use the "we_created" message-the creation of an object and its entrance into the world are two separate events, and trying to trigger actions based on an object's creation will not yield good results.
Double-click on the "World Message Event" value (currently "we_req_activate"), and select the "we_entered_world" message. You'll have to scroll up to the top of the list. By the way, don't be intimidated by the amount of messages you see listed; in basic to intermediate triggering, there are only a few messages that are truly useful to you. Make sure the Message Check is set to "on_every_message".

Add the "send_world_message" Action (default message is "we_req_activate") and click OK.

Our new trigger uses the second-most common message used with a Message Handler: we_entered_world. It will now test true every single time it enters the world, and send a "we_req_activate" message to a target we haven't specified yet. Incidentally, the "we_req_activate" message is THE most common message used with a Message Handler, and not coincidentally, our second trigger will test for that.

Configure the Second Trigger
Drop down a second trigger_generic gizmo nearby. Copy this new trigger's SCID and paste it into the "send to object" field of our first trigger's Action, and click OK.

Open the new trigger again and add a trigger instance, then give it a "receive_world_message" Condition with the "we_req_activate" parameter.

Add the "send_world_message" Action, leaving the message at "we_req_activate" and the target SCID empty for now, and click OK.

Our second trigger will listen for the "we_req_activate" message sent from the first trigger, and itself will send a "we_req_activate" message to our final target-the light_enable gizmo-that we'll place next.

If you're wondering why we didn't just send an activate message to the light_enable gizmo from the first trigger, there's no reason why we couldn't have. We're using two triggers in this example, each receiving a different message, to demonstrate the two most common ways a Message Handler Condition is used: either to detect when the trigger enters the world, or to receive an activate message from another object.

Place and Wire the Light Objects
Now let's drop a "light_enable" gizmo (/Game Objects/gizmos/commands/effects/light_enable) and a Point Light (/Lights/Point Light) nearby. If you don't see the Point Light when you place it, make sure "View Lights" and "Light Editing Mode" are both toggled on via the Toolbar buttons.

The Point Light is one of only two placeable objects (the other being the Spot Light) that can affect lighting locally in a region. The Point Light sheds light in all directions in a certain radius, while the Spot Light shines in one general direction. All placed lights are ON by default unless an object like the "light_enable" gizmo modifies its properties.

Left-click and hold on the Point Light, hold down SHIFT, move it a few feet above the ground, then release. You should see the ground around the light illuminate. If you don't, be sure "Ignore Vertex Color" in Preferences is OFF. If necessary, move the light close to your triggers, then Save. When the Save dialog box appears, check both "Save Lights" AND "Save Nodes" (in addition to "Save Game Objects"). You're saving lights to save the position of the light object, but you're saving nodes so the nodes will appear illuminated in the game. If you don't save Nodes, the light effects won't be visible on node textures.

Now that we know our light will appear in the region, we need to wire it so it won't start lit. The "light_enable" gizmo basically turns light objects on and off when it receives a "we_req_activate" message. In order for the light_enable gizmo to know which light to affect, it needs to know the light's special identifier, the "Light Guid".

Right-click on the Point Light object and select "Copy Light Guid to Clipboard". Open the light_enable gizmo's properties and paste the Light Guid value into the "siege_light" field under the [light_enable] block in the Component Fields section of the Template Properties tab (whew). Also set the following flags:
  • Initially_active = false
  • Is_toggle = true

Now our light_enable will cause our light object to start the game OFF, and it will turn the light on or off every time it receives a "we_req_activate" message from our triggers. Click OK.

Finally, copy the SCID of the light_enable gizmo and paste it into the "send to object" field of the second trigger's Action.

We're not done quite yet, though. We're going to set a delay on this trigger's Action.

Set the Delay
Our second trigger is going to send a "we_req_activate" message to the light_enable gizmo every time it itself receives a "we_req_activate" message from our first trigger. However, we don't want the second trigger to send its message to the light_enable gizmo right away-we want to give that Action a delay.

The delay value determines how many seconds AFTER THE CONDITION TESTS TRUE the message will be delivered. In actuality, an Action's message is sent immediately to the game' messaging system when the Condition tests "true", but isn't delivered to the target object until the delay has elapsed.

If you look at the "Trigger Properties" tab, you might notice that there are THREE fields to set a delay! There is a "Delay" box in the "Trigger Properties" section at the top of this tab, there is a "Send delay" parameter as part of the "send_world_message" Action, and there is a "Delay" column on the right side of the Action block.

To reduce confusion and possible errors, we are only ever going to use ONE of these: the "Delay" column on the right side of the Action block. This Delay field exists in every kind of Action block, and will be very visible when looking at triggers with multiple Actions. So, to be clear:

THE OFFICIALLY SANCTIONED PLACE TO SET THE DELAY FOR AN ACTION IS THE FIRST ROW (PER ACTION) OF THE "DELAY" COLUMN ON THE RIGHT SIDE OF THE ACTION BLOCK.

The other two fields are legacy fields from previous versions of the engine. To be honest, nobody is sure if the other two fields even work, and if they do, where exactly their delay is programmed to take place. Because they cause confusion, they may be removed in future versions of SE. In order to ensure that your delays will work today and across future patches, use the field described above.

Now that we know what field to use, just plunk the value "5" in there, which will cause our message to be delivered five seconds after this trigger receives the "we_req_activate" message. Click OK.

Before we move on, notice the white text above the green "wire" connecting this trigger to the light_enable gizmo. It should now say "we_req_activate (5.00)", showing that the message has a delay of five seconds.

Double-check Our Work
Our first trigger should have a Condition of "receive_world_message" with a parameter of "we_entered_world". Its Action should be sending a "we_req_activate" message to our second trigger's SCID.

Our second trigger should have a Condition of "receive_world_message" with a parameter of "we_req_activate". Its Action should be sending a "we_req_activate" message to the light_enable's SCID with a Delay of 5 seconds. Both triggers should have a Message Check value of "on_every_message".

Our light_enable should have the Light Guid of the Point Light pasted into its "siege_light" field, have "initially_active" set to "false", and have "is_toggle" set to "true".

Save the region making sure that "Save Objects", "Save Nodes", and "Save Lights" are checked.

By the way…did you provide documentation text for each trigger and its Actions? Look at Siege U: 203A if you don't remember how. That habit will make interpreting your more complex trigger webs MUCH easier, for you and anyone who comes later.

Test the Trigger
Open your test map in Dungeon Siege - don't forget to tank it in SE before firing up DS. If the first trigger isn't already in the frustum, walk there. Five seconds after the trigger enters the world, a white light should illuminate the terrain. Walk far away so the light is completely out of the frustum, and walk back. Five seconds after the light enters the world this time, it should shut off.

An oddity: if your light is next to your start position, you may see the light ON for a split-second before the light_enable turns it off. This is a limitation of how lights work, but since most lights enter the world at the edge of the frustum, it is rarely visible to the player.

If your light doesn't work, double-check all the Conditions, Messages, and SCIDs. Make sure you used the Point Light's "Light Guid" instead of its SCID. Also make sure you can see the light in SE-if you place the Point Light too far above the terrain, the terrain may not be within the light's effect radius.

Moving On
Now that we've seen how Message Handlers work, let's talk about our last type of trigger/Condition: the Mixed Condition.

Mixed Conditions (Message Handler with Spatial Condition)
<Back to Table of Contents>

Concepts covered:
  • Mixed Conditions
  • One Shot triggers
  • Message Check: on_first_message
  • Boundary Check: wait_for_first_message / wait_for_every_message
  • Action: mood_change
The Mixed Condition is our first example of a trigger with multiple Conditions. We'll talk specifically about a single Message Handler and a single Spatial Condition within the same Condition block.

Overview
This is a slightly more advanced trigger, and requires a good grasp of everything we've covered so far to understand its uses.

The Mixed Condition tests "true", and therefore fires its Actions, only when both of its Conditions have individually tested "true". Usually, the Spatial component of the Condition will wait to evaluate itself until the Message Handler's target message is received (i.e. it gets a "we_req_activate"). It is technically possible to use Boundary Checks to achieve different effects with Mixed Conditions (see SIDEBAR, below), but we'll use them the way they are implemented in DS.

Some restrictions: a Mixed Condition can only ever have one Message Handler. There is no such thing as a Mixed Condition with multiple Message Handlers. In the case of multiple Spatial Conditions (which I don't think were used in DS), Spatial Conditions are tested in the order they appear in the Condition block, from the top down. The Message Handler Condition is ALWAYS the first Condition in the block-SE will automatically move a Message Handler Condition to the top if it is added after a Spatial Condition.

Why Use a Mixed Condition?
A Mixed Condition gives you more control over the use of a Spatial Condition: specifically, when that Spatial Condition gets tested; and by extension, who gets collected as the Result of Condition.

Normally, on a trigger with a single Spatial Condition, that trigger's Action will be executed as soon as an object interacts with the border of the spatial area, i.e. when a party member walks into a sphere. Additionally, if the Action requires a "Result of Condition" value, it will only see the object that triggered it. But what if you want to assign a mood to every party member in a specific area, like the entire region? What if you need to assign that mood to the party only AFTER they have entered the spatial area, and not when they cross into or out of it?

We're going to create a trigger sequence that assigns a mood to ALL party members in the region the first time ONE of those party members enters a bounding box INSIDE the same region.

Practical Exercise

Place the Triggers
Open your test map for editing. Go ahead and delete the gizmos you placed in the first half of this article or move to a different area of your map. Place two trigger_generic gizmos reasonably close to one another. The first trigger will be our bounding box, and the second will assign the mood to everyone in the region.

Configure the First Trigger as a One Shot
Give the first trigger the "party_member_within_bounding_box" Condition with the "on_first_enter" Boundary Check. Place it where your character can walk.

Wire a "we_req_activate" message to the second trigger.

We set the "on_first_enter" Boundary Check because, in this example, we only want to set the mood once for everyone currently in the region, but not when a second character walks into the bounding box. This essentially makes this trigger good for "one shot", and then we never need to use it again.

Unfortunately, the game doesn't know we don't need it again, and will keep a small bit of memory set aside to account for this trigger both in RAM and in every save-game hereafter UNLESS we set the "One Shot" flag in the "Trigger Properties" section of the "Trigger Properties" tab. If a trigger is set to "One Shot", it will fire once and then delete itself, freeing system resources.

It is good practice then to set the "One Shot" flag anytime you need a trigger to fire once, and only once. If your trigger has a Boundary Check like "on_first_enter", it is already a one-shot in function, so it only helps to set the "One Shot" flag.

Configure the Second Trigger
Open the second trigger, and give it the Message Handler component of our Mixed Condition: the "receive_world_message" Condition. Leave the default message as the ever-useful "we_req_activate", and set the Message Check as "on_first_message".

Because our Message Handler will only test "true" on the first reception of a "we_req_activate", it is a one-shot trigger. Set the "One Shot" flag.

Now add the spatial component. Add another Condition to the second trigger: the "party_member_within_node" Condition. Like in 203A, copy the Region GUID from the "Settings | Region" pull-down menu on the main SE window into the "Region ID" field of the Condition, and leave the rest of the fields as "-1". We are now detecting party members in every node of the region.

The Boundary Check (wait_for_first_message / wait_for_every_message)
There are really only two Boundary Checks you should ever use with a Mixed Condition: wait_for_first_message, and wait_for_every_message.

These are relatively new Boundary Checks made specifically for this type of Condition. These tell the Spatial component of our Mixed Condition (party_member_within_node) to WAIT to evaluate itself UNTIL the Message Handler portion of the Condition has been satisfied, i.e. when it receives the "we_req_activate" message. "Wait_for_first_message" will evaluate the Spatial component on only the FIRST reception of the Message Handler's target message (effectively making the trigger one-shot), while "wait_for_every_message" will allow the Spatial component to evaluate itself EVERY time the message is received.

In other words, the "party_member_within_node" condition will not check to see if anyone is within its node range until the trigger receives a "we_req_activate" message. If a party member is within the node range when the activate message arrives, the entire Condition tests "true", and the Action is fired. In addition, every party member inside the region when the trigger fires will be collected as the "Result of Condition", and Actions (like moods) that require Result of Condition can apply their effects to all of them at once.

Since we know this trigger is already one-shot, set the Boundary Check to "wait_for_first_message".

[SIDEBAR: Mixed Condition Boundary Checks]

The following is optional information about how Mixed Conditions actually work. Feel free to skip it if you promise to only use "wait_for_first_message" and "wait_for_every_message" Boundary Checks with Mixed Conditions. If you want to know why these Boundary Checks are a good idea, or want to use triggers in ways we didn't use them in DS (but are theoretically possible), read on.

The all-important factor in the behavior of a Mixed Condition is the Boundary Check of the spatial component. Here's why:

When the Message Handler component receives its target message (in this case, the we_req_activate from our first trigger), it then checks the status of the Spatial component. If the Spatial component also tests "true", the Action fires. HOWEVER: the Spatial component is independently keeping track of who has satisfied its Condition, and updates itself every time its Boundary Check is satisfied--not just when the Message Handler tests true. When the Message Handler comes along to check on the Spatial component, the Spatial component will report whether or not it has been set to "true" yet, and if "true", who satisfied the Condition. Since the Boundary Check determines when a Spatial Condition is satisfied, and who is detected satisfying it, the Boundary Check has the most effect on what our Mixed Condition does.

(Warning: the following example is somewhat theoretical. We stopped creating triggers this way, and the description may not be completely accurate.) For example, right now our Spatial component, the "party_member_within_node" Condition, has a default Boundary Check of "on_every_enter". This means that the Spatial component will detect every party member who walks into the node range-in our case, the entire region-at all times. The first time a party member walks into this region, the Spatial component will test "true" and stay "true", and it will remember the GUID of the party member who did it. Let's say a second party member comes along an hour later (for example purposes), enters the region, and then walks through the bounding box. When the second character walks into the region, it too is logged by the Spatial component as having satisfied the "on_every_enter" Boundary Check. Then, when the second character walks into the bounding box, our Mixed Condition's Message Handler receives its target message and tests "true". It checks the Spatial component, finds that it is already "true", and the Result of Condition are BOTH party members: the one who just entered the region and walked into the bounding box, AND the one who walked into the region an hour before. If the Action uses Result of Condition, like a mood, then both characters will receive the mood-even the party member who is now an hour away.

[End Sidebar]
Set the Second Trigger's Action
We now have two triggers. The first sends an activate message to the second when a party member walks through a bounding box. The second detects that message and then checks for all party members within the region.

Our second trigger needs an Action, and that Action will assign a mood to all the party members detected by the trigger.

Add the "mood_change" Action to the second trigger, and enter "map_world_path2nt_2" into the "Mood name" parameter. This mood starts white fog, snow, and music.

For the full story on Moods, see Siege U: 204 - Moods, but for now, know this: the "mood_change" Action automatically uses the Result of Condition from any and all Spatial Conditions from the Condition block. Moods cannot be assigned to anyone from a Pure Message Handler.

Double-check Our Work
First trigger:
  • Condition: party_member_within_bounding_box
  • Boundary Check: on_first_enter
  • Flags: One Shot
  • Action: send_world_message / we_req_activate to second trigger's SCID

Second trigger:
  • Mixed Condition:
    • Message Handler: receive_world_message / we_req_activate
    • Message Check: on_first_message
    • Spatial Condition: party_member_within_node with Region GUID (-1,-1,-1)
    • Boundary Check: wait_for_first_message
  • Flags: One Shot
  • Action: mood_change (map_world_path2nt_2)

Make sure to save Objects.

Test the Triggers
Start your test map. If possible, use a multi-character party just so you can see that the mood is assigned to more than one character in the region, even though a single character walked through the initial bounding box.

Walk your character into the region if he isn't there already. Note that the mood does NOT change when your character enters the region.

Walk your character into the bounding box. Immediately upon entering, EVERY party member in the region should see white fog and snow, and hear pleasant music! If we had given the mood_change action to the bounding box trigger, only the character who had walked into the bounding box would receive this mood, and not everyone in the region (even if another character was standing next to the first).

If the Message/Boundary checks were set to fire on "every" message, then every time a party member walked through the bounding box, every party member in the region would be assigned the mood.

Many of the Boss Monster encounters in DS use these types of mood triggers. Look at "dl_r1", and note what happens when the Dragon is woken up and when he dies. Another interesting use of moods and triggering is the Gresh encounter in "cf_r1".

Conclusion
<Back to Table of Contents>

Congratulations! By now, you should have at least a cursory knowledge of fundamental trigger concepts, terminology, and applications. Even though we tried to introduce you to various aspects of the game with different trigger examples, it is by far not a comprehensive treatment. The best place to glean additional working knowledge of triggers and how they're used is to look in the game, and of course experiment on your own!

If you have followed each tutorial so far, you should be well able to recognize most of the triggers used in the game. Moods, and especially Fades, might be a little foreign at the moment, and there are many, many Gizmos that you have yet to learn the properties of. Some of these Gizmos will be covered in future tutorials, and many can be understood simply by looking at their Properties.

The following two appendices are for reference; feel free to look them over when you're ready. The first section talks about everything on the "Trigger Properties" tab, including every Condition, Boundary Check, and Action. The second identifies commonly used Messages.

I hope this Tutorial has been helpful in learning about Triggers! If you've gone this far, pat yourself on the back, since we've really gone beyond a beginner's treatment of triggers into something more intermediate/advanced. Don't hesitate to ask questions online to your fellow classmates, and if you find someone having a hard time figuring this out, give them a hand!

Thanks for reading, and have fun!

Appendix A: A Detailed Look at the Trigger Properties Tab
<Back to Table of Contents>

There are four sections to the Trigger Properties tab:
  1. Triggers (white box on left side of window)
  2. Trigger Properties (box of fields and checkboxes at top of window)
  3. Conditions (middle)
  4. Actions (bottom)

The "Trigger" Section
The "Trigger" section lists each trigger instance that has been created inside the selected gizmo. The name of the gizmo is at the top of the window, and any trigger instances it contains will depend off of it with a Windows Explorer-like convention. If multiple gizmos are selected, this window will list each selected gizmo, each expandable to show their individual trigger instances.

Pressing the "New" button will add a new trigger instance to the selected gizmo (or automatically add an instance if only one gizmo is in the list) each time it is pressed.

To edit a trigger instance, click the plus (+) sign next to the gizmo name to expand the instance list, and select the instance by left-clicking on it. It is not possible to select (and therefore edit) multiple instances at once.

To remove a trigger instance, select the instance and press the "Remove" button. Removing a trigger instance will delete all Conditions and Actions associated with it.

Multiple trigger instances act independently of each other, and are functionally unaware of each other's existence. All trigger instances in a single object share the same SCID.

The "Trigger Properties" Section
Don't get this section confused with the "Trigger Properties" tab, on which this section is found. They should probably have different names, but we'll persevere for now. :)

All flags in this section pertain ONLY to the selected instance. Multiple instances can and will use these flags differently. These values will change visibly when different instances are selected.
  • One Shot: Sets whether or not this trigger instance should delete itself after firing once. This should be set for all triggers that only fire once to free up a small amount of system resources. If this is not set, DS will set aside memory in RAM and in the save-game file to track the state of this trigger. Default: OFF.

  • Single Player: Sets whether this trigger instance appears in the Single Player game. This is different than the "is_single_player" field on the "Template Properties" tab, which sets the property for the entire gizmo. If the "is_single_player" value is set to "false", it's a good idea to uncheck the "Single Player" box as well to alleviate confusion. It is much too easy to assume that the gizmo properties are the same as the trigger instance properties, and vice-versa. Default: ON.

  • Initially Active: Sets whether this trigger instance will start the game in an active state, i.e. will evaluate its Conditions and fire Actions as normal. If unchecked, the trigger instance is essentially "hibernating". Inactive triggers will become active by receiving a "we_trigger_activate" message. Conversely, an active trigger will become inactive by receiving a "we_trigger_deactivate" message. This flag can be toggled multiple times by these Messages as long as the instance exists. Default: ON.

  • Flip-flop (obsolete): This is a relic of the early trigger system. Originally, if this flag was set, a trigger could theoretically fire Actions based on whether the Condition was "true" or "false" at the time of its state change. The "Condition" column in the Action block was used solely for this purpose, determining if that Action fired on a "true" or "false" event. Nobody knows if this works anymore, since it relied on the old "constantly-polling" trigger system. Default: OFF.

  • Multi Player: Sets whether this trigger instance appears in the Multiplayer game. Otherwise similar to the "Single Player" flag. Default: ON.

  • Can Self Destruct: This should always be ON; otherwise this trigger will never leave memory ever. The checkbox is here because, at the time of this feature's implementation, it was unclear whether this value needed to be overridden by a designer. Answer: no. Default: ON.

  • Reset Duration: A delay, in seconds, after a trigger has fired before its Conditions can be tested again. Rarely used, useless for one-shot triggers. Default: 0.

  • Delay (obsolete): Do not use. Do not even look at this field. :)

  • Occupants Group: A map-unique string value used to assign this instance to a larger group of triggers, essentially forming a "meta-trigger". A master trigger will have Conditions that detect the state of each Group member. Commonly used in Fades. See the "Conditions" section later in this document for more info. Default: (empty).
The "Conditions" Section
The Conditions section lists one or more Conditions in a spreadsheet block. To add a Condition, simply press the "Add Condition" button on the right. To delete a Condition, select the Condition's "Name" field and press "Remove Condition".

Each Condition block has six columns:
  • Name: the name of the Condition
  • Parameter: Condition-specific values that define the Condition's behavior.
  • Value: Editable fields defining the actual values of the Condition's parameters.
  • Group: An arbitrary number value (Default: 0) that can split the Condition block into multiple independent Conditions. Conditions with different Group numbers are evaluated separately from one another, just as if they were in separate instances. Actions with matching Group numbers fire when their Group tests "true", and ignore Conditions from other Groups in the same instance. This is primarily a resource-saving and de-cluttering device to prevent excessive Gizmo placement. See the "Group" entry under "The 'Actions' Section" below.
  • Doc: An SE-only text field to document the Condition. Sometimes useful to list from where an expected message is coming from. Also used in auto-generated error messages.
  • ID: A SE-generated value unique for each Condition in the instance. This value is sometimes pasted into an Action's "Message Broadcast" field to send the Result of Condition with the Action.
Types of Conditions
There are two types of Conditions: Message Handlers, and Spatial Conditions. Only one Message Handler may exist per Condition, but a Condition can technically contain multiple Spatial Conditions, although this was not used in DS. Conditions are always evaluated from the top down, stopping if one tests "false". A Condition with both a Message Handler and one or more Spatial Conditions is known as a Mixed Condition, and the Message Handler is ALWAYS the first Condition in a Mixed Condition block.

All Conditions are listed below with their parameters. Boundary Checks for Spatial Conditions will appear at the end of the section.

Message Handlers:
  • Receive_world_message: the only Message Handler Condition. This Condition tests "true" on the reception of the Message specified in its "World Message Event" parameter.
    • World Message Event: A drop-down list of all the Messages in the game. Common messages used in this Condition are "we_req_activate", "we_req_deactivate", "we_entered_world", and "we_req_use". See the "Messages" section below for more info. Default: we_req_activate.
    • Qualifier: Not used, or unknown function. Default: 0.
    • Message Check: A drop-down list of available Message Checks:
      • On_first_message: This Condition will test "true" only on the first reception of the Message specified in the "World Message Event" parameter
      • On_every_message: This Condition will test "true" every time the Message in the "World Message Event" parameter is received.
Spatial Conditions:
Some of the Spatial Conditions are very similar: they test for a particular object within a particular kind of area. Generally speaking, Spatial Conditions detect objects where they meet the ground, even if the object is aerial (i.e. phraks). The kind of objects tested for are:
  • Actor: Any GO with an AI component, i.e. monsters, NPCs, and Party Members.
  • Game Object (GO): Any object mesh with a GOID, including Actors. This basically includes anything that isn't a Node. Usually used to detect non-Actor objects.
  • Party Member: Your starting character and any characters you hire, including pack mules. In MP, every player.

    The following two Conditions are identical for "Actor" and "Party Member" objects (replace the asterisk (*) with "Actor" or "Party_member"). There are "GO" versions of these Conditions, but their parameters are different-see below:

  • *_within_sphere: Detects if * enters/is within/leaves a specified radius around the trigger (depending on Boundary Check).
    • Radius: Number in meters defining the sphere radius.

  • *_within_bounding_box: Detects if * enters/is within/leaves with a specified box centered on the trigger (depending on Boundary Check).
    • Half diag X: Distance in meters from trigger center to box edge on the X axis (width). Default: 1
    • Half diag Y: Distance in meters from trigger center to box edge on the Y axis (height). Default: .5
    • Half diag Z: Distance in meters from trigger center to box edge on the Z axis (length). Default: 1

    The GO versions are as follows:

  • Go_within_sphere: Detects if a specified GO (Game Object) enters/is within/leaves a specified radius around the trigger (depending on Boundary Check). This Condition can detect a single GO by SCID or multiple GOs by template name. Use SCID Index, or Template name filter, but not both at the same time.
    • Radius: Number in meters defining the sphere radius.
    • Scid index: Copy a single SCID to detect a specific GO. Default: 0.
    • Template name filter: Enter a single template name OR multiple template names separated by commas. Will detect any GO of that template name range. Default: (empty).

  • Go_within_bounding_box: Detects if a specified GO (Game Object) enters/is within/leaves a specified box centered on the trigger (depending on Boundary Check). This Condition can detect a single GO by SCID or multiple GOs by template name. Use SCID Index, or Template name filter, but not both at the same time.
    • Half diag X: Distance in meters from trigger center to box edge on the X axis (width). Default: 1
    • Half diag Y: Distance in meters from trigger center to box edge on the Y axis (height). Default: .5
    • Half diag Z: Distance in meters from trigger center to box edge on the Z axis (length). Default: 1
    • Scid index: same as above
    • Template name filter: same as above

    There is one more GO Condition that is quite different from the rest:

  • Has_go_in_inventory: Detects if a particular GO exists in the inventory of a party member. This Condition should always be paired with at least one other Condition previous to it that tells it when to evaluate itself-either a Message Handler (evaluate when message is received) or a Spatial Condition (evaluate when an object enters an area). Otherwise, the has_go_in_inventory Condition will try to evaluate itself when it enters the frustum, which is almost never the desired behavior. In SP, the GO will be detected in any party member's inventory no matter where they are; in MP, the "player inventory" parameter determines the function:
    • Player inventory: If "any", detects the GO in any MP party member's inventory. If "local", detects the GO in the inventory of any player within the same frustum as the trigger. Default: local.
    • Scid index: Same as above. Default: 0.
    • Template name filter: Same as above. Default: (empty).

    NOTE: If your trigger doesn't work, it might be best to look at in-game examples. For working examples, search through map_world or multiplayer_world .gas files for "has_go_in_inventory".

    Finally, there are number of Conditions unique to Party Members:

  • Party_member_within_node: Detects if a party member enters/is within/leaves a particular Node or set of Nodes defined by the parameters. The Section/Level/Object values match identical values assigned to Nodes on the "Fade Settings" tab of the Nodes | Properties dialog box.
    • Region ID: The GUID of the Region in which the Node or Nodes are found (can be copied from the "Region GUID" field of the Settings | Region dialog box).
    • Section: If this value is set to anything but "-1", Nodes with an identical Section value will be selected for evaluation. "-1" means "all sections". If both Level and Object values are also set to "-1", the entire Region will be selected for evaluation. Default: "-1".
    • Level: Same as Section, except there can be multiple Level values per Section.
    • Object: Same as Level, except there can be multiple Object values per Level.

    Examples: Values listed in (Section,Level,Object) format:
    • (-1,-1,-1): all Nodes in the specified Region
    • (5,-1,-1): select all Nodes with a Section value of 5, regardless of Level or Object values.
    • (5,2,-1): select all Nodes with a Section value of 5 and a Level value of 2, regardless of Object values.
    • (5,2,10): select all Nodes with a Section value of 5, a Level value of 2, and an Object value of 10.

  • Party_member_entered_trigger_group:
  • Party_member_left_trigger_group:

    A trigger group is a set of multiple sub-triggers tied to one master trigger, effectively creating one "meta-trigger". This is done to create a large Spatial trigger of irregular shape, since the trigger group's shape is the sum of its sub-trigger's areas.

    The sub-triggers are set up with normal Spatial Conditions, and are assigned to the trigger group by entering identical string values into the "Occupants Group" field in the "Trigger Properties" section of the "Trigger Properties" tab.

    The master trigger has no value entered into the "Occupants Group" field, but is given one of the two Conditions listed above. By entering the trigger group string assigned to the sub-triggers into the Condition's "Group Name" field, the master trigger is set to "listen" to all the triggers in that group.

  • Party_member_within_trigger_group: Same as above, although this Condition is usually used as the Spatial component of a Mixed trigger, with the Message Handler portion telling the Condition WHEN to actually test for party members inside it. As such, only the "wait_for_first_message" and "wait_for_every_message" Boundary Checks should be used with this Condition.
Boundary Checks for Spatial Conditions
  • While_inside (obsolete): Do not use. Replaced by "wait_for_first_message" and "wait_for_every_message"
  • Wait_for_first_message: Used with the Spatial component of a Mixed Condition. The Spatial component waits to evaluate itself until the Message Handler component receives its target message. This only happens on the first reception of the message (trigger is one-shot).
  • Wait_for_every_message: Used with the Spatial component of a Mixed Condition. The Spatial component waits to evaluate itself until the Message Handler component receives its target message. This occurs every time the target message is received.
  • On_every_enter: The Condition is evaluated every time its target object type (Actor, GO, Party_Member) enters the Spatial area (sphere, bounding box, node, etc).
  • On_first_enter: The Condition is evaluated only the FIRST time its target object type enters the Spatial area (one-shot).
  • On_every_first_enter: The Condition is evaluated when its target object type enters the Spatial area, as long as another target object isn't already in it. When all the target objects leave the Spatial area, the next target object to enter (it's another "first enter") causes the Condition to be evaluated again.
  • On_unique_enter: The Condition is evaluated once per unique target object. The same target object cannot satisfy this Condition more than once. For example, the same party member could not satisfy a "party_member_within_sphere" Condition with this Boundary Check the second time he walked into the sphere, but a different party member, who had not yet been into the sphere, could.
  • On_every_leave: The Condition is evaluated every time its target object type leaves the Spatial area.
  • On_first_leave: The Condition is evaluated the first time its target object type leaves the Spatial area (one-shot).
  • On_last_leave: The Condition is evaluated the first time its target object type leaves the Spatial area, and there are no more target object types left within the area In other words, the LAST object in a group of one or more objects already inside the Spatial area to LEAVE the Spatial area will satisfy the Condition (one-shot).
  • On_every_last_leave: The Condition is evaluated every time a target object leaves the Spatial area, and there are no more target object types left within the area. Same as above, but not one-shot.
The "Actions" Section
The Actions section lists one or more Actions in a spreadsheet block. To add an Action, simply press the "Add Action" button on the right. To delete an Action, select the Action's "Name" field and press "Remove Action".

Each Action block has seven columns:
  • Name: the name of the Acton
  • Parameter: Action-specific values that define the Action's behavior.
  • Value: Editable fields defining the actual values of the Action's parameters.
  • Condition: Not used (related to the obsolete "Flip Flop" flag).
  • Delay: Number of seconds before this Action is executed.
  • Group: Used to match an Action with a particular Condition Group. Actions with different Group numbers only fire when Conditions with the same Group numbers are satisfied. See the "Group" entry under "The 'Conditons Section" above. Default: 0.
  • Doc: An SE-only text field to document the Action. It is VERY good practice to use this field to describe the Action taking place.
Types of Actions
There are many kinds of Actions that perform a wide variety of functions. It is beyond the immediate scope of this Lesson to fully explain each of them, especially since some of them are covered in future Tutorials. However, we will at least categorize them so you know something of what they do.

The same goes for Messages. There are literally dozens of Messages to send, but we only need to know about a few of them. We'll cover these more common messages in this next section.

Sending Messages

  • Send_world_message: Sends the specified Message to the target SCID.
    • Message Type: A drop-down list of all available Messages. Default: we_req_activate.
    • Send to Object: The SCID of the object receiving this Message. This value must be set for this Action to function. Default: 0.
    • Send Delay (obsolete): DO NOT USE. Use the "Delay" column to the right instead.
    • Message Broadcast: This field specifies whether the "Result of Condition" is sent with the Message. To send the Result of Condition along with the Message, the "ID" value from the desired Condition is pasted here. Otherwise, this field is a drop-down menu containing two values: "Default" means no Result of Condition is sent. "All_Conditions" will send the Result of Condition from ALL Conditions with the Message. WARNING: Do not use "All_Conditions" if there is a Message Handler Condition in the Condition block-only use with a Condition containing one or more Spatial Conditions. Default: default.
    • Send to: A drop-down menu containing two values: "every" and "single". If using Result of Condition, this field determines if EVERY object detected by the Condition receives this Message, or only a SINGLE object in that set. Some party commands (like "move party" (cmd_move_party)) do not function as intended if everyone in the party receives the command, instead of just one member. Default: every.
    • Message Qualifer: Obsolete or unknown function.
Moods
  • Mood_change: Changes the mood for all party members detected by the Spatial Condition. Requires a Spatial Condition in the Condition block, and automatically uses Result of Condition to assign the mood. See Siege U: 204 - Moods.
Fades and Node Properties
  • Fade_nodes_outer_local_party
  • Fade_nodes
  • Fade_node
  • Fade_nodes_global
The "fade_node*" Actions all cause nodes or groups of nodes to become visible or invisible. These Actions are used to fade the roofs off of houses when players are inside them, or fade in the next dungeon room when the door is opened. See Siege U: 205 - Fades.
  • Set_camera_fade_node
  • Set_occludes_camera_node
  • Set_bounds_camera_node
These three Actions manipulate the "camera fade", "occludes camera", and "bounds camera" flags on an individual Node. Usually used in conjunction with Fades, especially around archways and doors.

Quests and Victory Conditions
  • Change_quest_state: Can activate, deactivate, or complete a Quest. See Siege U: 209 - Quests.
  • Victory_condition_met: This Action starts the end game sequence by referring to a "Condition Name" found in the map's "victory.gas" file (i.e. "world\maps\map_world\info\victory.gas"). This was used to end the SP game when Gom was killed.
Manipulate Actors
  • Change_actor_life
  • Change_actor_mana
Fairly self-explanatory-each command can directly add or subtract from an actor's life or mana value. They can manipulate a single Actor (by SCID) or can use Result of Condition.

Other
  • Call_sfx_script: Debug use only. Fires a particular special effect. DO NOT USE in normal maps-use effect emitter gizmos instead.
  • Play_sound: Debug use only. Plays a .wav file. DO NOT USE in normal maps-use sound emitter gizmos instead.
  • Set_interest_radius: An advanced Action to specifically change a player's "interest radius", which can help game performance. In brief, objects marked as "interest items" are not rendered if the node they are placed on is outside this radius. Used in towns to avoid excessive non-visible object loading, but it doesn't work all that well since house nodes are big.
  • Set_player_world_location: This is used in MP to show those "XXX has entered YYY" messages at the top of the screen. The "World Location" value is gotten from "world_locations.gas" (i.e. \world\maps\multiplayer_world\info\world_locations.gas).
  • Start_camera_fx: The camera begins zooming in and out very quickly, focusing on go-go dancers in cages that pop out of the ground. Colored lights whirl everywhere. (Actually, this isn't used, and nobody remembers what it does.)

Appendix B: Messages
<Back to Table of Contents>

Triggers use Messages to either detect when certain events take place (by listening for them with a Condition), or cause certain events to take place (by sending them with an Action).

Actions are sent and received by almost every object in the game, AND the game engine itself. Most messages are sent automatically on a certain event; for example, every time an object enters the world, it sends a "we_entered_world" message to the game. Every time an actor dies, it sends the "we_killed" message. The game listens for these messages to keep track of what is going on, but our triggers can also listen for them to execute Actions at appropriate times. Many objects also listen for messages, since they are programmed to perform a certain function when they receive it. For example, the emt_sound_act emitter will start playing a sound when it receives a "we_req_activate". A dungeon door will open when it receives a "we_req_use".

There are dozens upon dozens of Messages that can be used with the "receive_world_message" Condition and the "send_world_message" Action. We are going to look at the most common Messages to use with triggers.

Most of these Messages can be sent via Actions, but some can only be received via the "receive_world_message" Condition. The first two are Actions specific to trigger function:
  • We_trigger_activate: This sets the "Initially Active" flag to ON for ALL trigger instances inside the target object. Inactive trigger instances that receive this message will immediately become Active and function normally.
  • We_trigger_deactivate: This sets the "Initially Active" flag to OFF for ALL trigger instances inside the target object. Active trigger instances that receive this message will immediately become Inactive and stop evaluating its Condition(s).
  • We_req_activate: This sends the generic "activate" message. Many objects are programmed to do something when this message is received (e.g. most gizmos). Triggers aren't inherently programmed to DO anything on their own when this message is received, but it is the most common message "listened" for with the "receive_world_message" Condition.
  • We_req_deactivate: The flip side of "we_req_activate". Many objects are programmed to "turn off" when this message is received (e.g. light_enable gizmos). Triggers perform no function when this message is received, but the "deactivate" message can be used as an alternative to "activate", especially when needing more than one generic message.
  • We_req_use: Many objects that interact with players (e.g. doors, chests, switches, etc) send this message when clicked on. Sending this message to such objects will usually manipulate them just as if a player had done it (e.g. doors will open, switches will throw), and trigger instances on these objects listening for this Message will fire when the objects are used. Otherwise, triggers can use this message as a third generic message when needing more than the above two.
  • We_entered_world: This Message cannot be sent, only received by the "receive_world_message" Condition. Every object sends this message when it enters the world. "Entering the world" is basically the moment that the object becomes a valid game object. This message should be used anytime you want to detect an object being created, being moved into the frustum, or otherwise entering the world. This message is commonly used to fire triggers when the game begins.
  • We_killed: Used by Conditions to detect when an Actor dies.
  • We_req_delete: Any object receiving this Message will try to delete itself from the world. Don't use on Actors, since they drop their inventory when deleted.
  • We_req_die: Ask the target Actor to die. Pretty sure this works.
  • We_req_talk_begin: When used in an Action, tells the target Actor to begin a conversation. When used in a Condition, it detects when a conversation begins.
  • We_req_talk_end: Usually used by Conditions to detect when a conversation has ended.

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_204 Moods

Moods

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

What This Tutorial Will Teach You

Overview of Moods

Mood Usage

Mood Defaults

Frequency of Mood Changes

Location of Mood Definitions

Mood Naming Conventions

A Couple of Simple Moods

Mood A

Mood B

Block and Field Names

[mood_setting*]

[fog]

[music]

[sun]

[rain]

[snow]

[wind]

[frustum]

A Practical Exercise

Four Complex Moods

Mood A

Mood B

Mood C

Mood D

Conclusion

Appendix A: EAX Settings

Appendix B: Tips

Introduction

A Mood is a block of settings that control the fog near/far distances, the fog color, the music and how often it repeats, the ambient track and how often it repeats, the weather type and density, the wind speed and direction, the color of sunlight, EAX settings, and even (in some cases) the size of the frustum.

What You Need For This Tutorial
  • Dungeon Siege, updated to version 1.09B or later
  • A text editor
What This Tutorial Assumes You Have Already Learned
  • Siege U: 200 - Dungeon Siege Concepts and Terminology
  • Siege U: 201 - Templates. This is useful in understanding the concepts of template blocks, components, and properties.
What This Tutorial Will Teach You
  • What Moods Are
  • What Each of the Blocks and Fields Are
  • When and How Moods are Used
  • Simple and Complex Examples of Moods
  • EAX Settings
  • Mood Tips

Overview of Moods
<Back to Table of Contents>

Mood Usage
Triggers are placed in the world that usually have bounding boxes or some other method of player detection. The action in the trigger is "mood_change", with a particular mood name specified. When a player walks into the bounding box, the game looks up all of the settings under that mood name and applies them to that character.

Mood Defaults
If you don't specify something in the mood, it will default to zero or off.

Frequency of Mood Changes
A new mood is used anytime that we want to change anything at all from a previous mood. For instance, if we want a new piece of music to begin playing when the player reaches a certain point in the game, then we would need to set up a trigger that would activate a mood with that piece of music specified under standard_track = "[music piece]". The next place that the music will change is when the player reaches another trigger that activates a mood with a different piece of music.

Anytime we want to change anything controlled by a mood, we have to have a new mood for it. For example, if we want the player to listen to different music at some point (while everything else stays the same), we would have to make a new mood that had everything defined the same as the previous mood except the music. So some regions have only one or two moods, while other regions have 10 or more. It depends on whether you want to change the different elements of a mood all at the same time or all at once. For example, a player could go from a grassy field in the middle of the day to a dark crypt. In this case, we would probably want to change the fog distance, the fog color, turn the wind and weather off, and change the EAX settings, the music and the ambient sounds. All of this could be done by changing to one new mood. However the player could also walk across a large field over the course of two minutes. Here we could be more subtle, changing to a mood with different wind and ambient track near the beginning, then a mood with different music and weather in the middle, and perhaps changing the fog color at the end. All of these changes would require different moods.

Location of Mood Definitions
The default location is My Documents\Dungeon Siege\Bits\World\global\moods\[map name]\[any name].gas

Any .gas file located underneath world\global\moods\ can define moods, but keeping mood files under a directory named after the map should help organization.

Mood Naming Conventions
Even though moods can be kept in different files, they are all global. This means that the game loads all moods in all mood files, and a side effect of that behavior is that no two moods can have the same name. Here are the guidelines that we came up with to keep our moods organized.

All mood names are prefixed by the map that they belong to. Even though this can make mood names longer, it will really help keep mood names unique. And since we created moods that are specific to each area on the world map, each mood will also be named after the region in which it first appears. After that, a number is used to differentiate between the different moods within a region, although it could just as easily be a descriptive word or two.

Example:
The map name is: Happyville.
The region name is: first_town.
Some possible mood names: Happyville_first_town_1, Happyville_first_town_2, Happyville_first_town_inn, Happyville_first_town_mayor_house.

A Couple Of Simple Moods
<Back to Table of Contents>

Mood A
This mood is used in map_world (The Kingdom of Ehb), in the bear cave at the beginning of the game. It has its own music, no weather, black fog fairly far away from the camera, and a "cave" EAX setting.
[mood_setting*]
{
   mood_name = map_world_bc_r1_1;
   transition_time = 1.0f;
   interior = true;

   [fog]
   {
      fog_near_dist = 40.0f;
      fog_far_dist = 50.0f;
      fog_lowdetail_near_dist = 40.0f;
      fog_lowdetail_far_dist = 45.0f;
      fog_color = 0xFF000000;
      fog_density = 0.5f;
   }

   [music]
   {
      ambient_track = "";
      standard_repeat_delay = 10.0f;
      standard_track = "s_m_Farmhouse_03";
      room_type = rt_cave;
   }
}
Mood B
This mood is used in the forest just before the crypts (also near the beginning of the single-player campaign). The fog is dark green, and there is a little bit of wind. An ambient track with creaking trees is playing to help the player feel like they are in the woods, while creepy "crypt" music is playing.
[mood_setting*]
{
   mood_name = map_world_path2crypts_4;
   transition_time = 2.0f;
   interior = false;

   [fog]
   {
      fog_near_dist = 30.0f;
      fog_far_dist = 50.0f;
      fog_lowdetail_near_dist = 30.0f;
      fog_lowdetail_far_dist = 45.0f;
      fog_color = 0xFF1e281e;
      fog_density = 0.5f;
   }

   [wind]
   {
      wind_velocity = 0.5f;
      wind_direction = 0.0f;
   }

   [music]
   {
      ambient_track = "s_e_ambient_woods_01";
      standard_repeat_delay = 10.0f;
      standard_track = "s_m_Crypts_01";
      room_type = rt_generic;
   }
}

Block and Field Names
<Back to Table of Contents>

[mood_setting*] { … }
All moods begin with the [mood_setting*] block name and are enclosed in curly braces.

mood_name
All moods need a mood_name defined. All mood names must be unique. According to the naming guidelines above, since bc_r1 was our bear cave region in the map named map_world, map_world_bc_r1_1 is the first mood in that region.

transition_time
This is how long the mood will take before it completely switches to the new values. During the transition time, the old mood will be smoothly changing to the new mood.

Example: The current mood has white fog, music track #1 and no ambient track playing. The new mood will change to a black fog, music track #2, and ambient track #2. If the transition time is 4 seconds, then this would be the progression of the mood change:
  1. 0 seconds: fog is white (0% black), music track #1 at 100% volume.
  2. 1 second: fog is light gray (25% black), music track #1 at 75% volume, music track #2 and ambient track #2 at 25% volume.
  3. 2 seconds: fog is gray (50% black), music track #1 at 50% volume, music track #2 and ambient track #2 at 50% volume.
  4. 3 seconds: fog is dark gray (75% black), music track #1 at 25% volume, music track #2 and ambient track #2 at 75% volume.
  5. 4 seconds: fog is black (100% black), music track #2 and ambient track #2 at 100% volume.
More on transition time under Appendix B: Tips.

interior
This is a true/false value that is used by the mood manager to control whether or not the fog will change color with the time of day. If interior = true, then the fog color will not change with the time of day. Further explanation is given under fog_color.

[fog] { ... }
This block contains settings that deal with fog color and distance.

fog_near_dist
This defines the distance from the camera (in meters) that the fog begins. You can still see past this point, usually up to the fog_far_dist, where the fog is 100% thick.

fog_far_dist
This is the distance from the camera (in meters) where the fog gets too thick to see through.

fog_lowdetail_near_dist
When users have their detail settings bar all the way to the left, this value will be the new fog_near_dist. If they have the slider at the halfway point, then the fog_near_dist will be the average of the original fog_near_dist and the fog_lowdetail_near_dist.

fog_lowdetail_far_dist
Same as above, except dealing with the fog_far_dist.

fog_color
The fog value is made up of four components: alpha, red, green, and blue. All of these values go from 0 - 255, but it's in hex. So that means that the scale is 0 - FF. The format for the color setting is 0xAARRGGBB, with AA = alpha, RR = red, GG = green, BB = blue. For those not familiar with hex, valid numbers in ascending order are 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F. Since alpha in fog can be treated quite differently depending on hardware and drivers, we finally ended up just setting all alpha values to maximum, so all of our fog settings have FF for the alpha. Here are some fog colors from different parts of the single player campaign:
  • Gray fog at the first farmhouse: 0xFF646464
  • Dark blue fog in the Arctic Caverns: 0xFF202028
  • Green fog in the first forest: 0xFF323F32
  • Smoky lava-lit fog in the Lava Caverns: 0xFF4F2800
  • Dusty orange fog near Fortress Kroth: 0xFFB49363
  • Sandy fog in the Desert Canyons: 0xFFB09070
The brightness of the fog will change with the time of day. This is behavior defined in the mood manager. At night, the fog color will be 50% of the defined value, morning/evening 75%, day 100%, and mid-day (noon) 125%. Since the fog actually will get brighter than you define it, I recommend having none of your color values be over C0 (C0 and higher will all get translated to FF during the time when the fog is brightest) unless you prevent the fog from changing color by setting interior = true.

fog_density
Unfortunately, this is a mostly-useless setting. It is supposed to govern how thick the fog is between the near and far dist, but most video cards don't bother to use this information. This setting is still here for the video card and video driver combinations that support it. The scale is 0 -> 1.0, which is least thick -> most thick. You'll find that most of our moods have set this to 0.5.

[music] { ... }
This block contains all ambient track and standard track settings, and the EAX setting.

room_type
This is the EAX setting. EAX, when enabled on a soundcard that supports it, will filter many of the sounds in Dungeon Siege through reverb that you specify here. If you want to make it sound like the player is in a cave, set the room type to rt_cave. The default is rt_generic, which is a setting with no reverb. All possible settings can be found in Appendix A, as well as world\global\moods\default_moods.gas.

standard_track
This is the standard track, usually used for music. As long as a file is located in sound\Music, it can be specified here. You don't need to provide the extension of the file.

standard_repeat_delay
This value is the number of seconds of silence to have after the end of the track. After this silence, the music will play again. For any looping tracks, this should be 0.

standard_intro_delay
This value is the number of seconds of silence to have before the track plays for the first time. This can be useful if you are using a piece of music that starts with a sudden emphasis, but you are transitioning into the mood for several seconds. Without an intro delay, the music would begin playing right away, but it would start out very quiet. Putting in a standard intro delay that is equal to (or longer than) the transition time will guarantee that the music will start at full volume.

ambient_track
This is simply another track that will play at the same time as the music track. It is a great place to have a small looping track that plays creaking trees, or a gentle wind sound. The game doesn't really care what you put here - you could use this to play a second piece of music at the same time as the standard music track.

ambient_repeat_delay
This value is the number of seconds of silence to have after the end of the track. After this silence, the ambient track will play again. For any looping tracks, this should be 0.

ambient_intro_delay
This value is the number of seconds of silence to have before the track plays for the first time. If you want to use the ambient track to play a second piece of music, you can use this to stagger the two pieces of music, so that the ambient piece doesn't start until the standard piece is done.

battle_track, battle_repeat_delay, battle_intro_delay
Setting these values will have no effect unless you also modify mood_manager.skrit to actually play this track. This buried feature has a possible use for advanced modders. See Appendix B: Tips for further explanation of possible uses.

[sun] { ... }
If you want to override the default sunlight color table, then you will need to do it inside a [sun] block. The way this works is just the same as in the timeofday.gas file:
   [00h00m]{ color = 0xFFA4700F; }
   [01h00m]{ color = 0xFFA4700F; }
   [02h00m]{ color = 0xFFE0A54B; }
   // etc...
There is no limit to the number of time blocks as long as they fit the proper format. For example, XXhYYm, where XX is the hour between [0-23] and YY is the minute between [0-59]). The color of the sunlight will transition smoothly between these colors. Example:
   [05h00m]{ color = 0xFFFF0000; } // red
   [12h00m]{ color = 0xFF00FF00; } // green
   [19h00m]{ color = 0xFF0000FF; } // blue
At exactly 5am, the sunlight color would be a bright pure red. Immediately after this it will begin lessening the red and gaining more green until noon, when it will be completely green. Immediately after noon, it will begin gaining blue and lessening green until 7pm, when it will be pure blue. After 7pm, it will begin gaining red and lessening blue until 5am, when it will be pure red again.

We used [sun] blocks to override the color of sunlight in our snowy areas so that there wouldn't be yellow snow.

[rain] { ... }
Rain settings go inside a rain block.

rain_density
This is how thick the rain will be. Default is 0 (not raining). The maximum is defined in config\weather.gas (we set it to 300).

lightning
This can be set to either true or false. If true, then it uses settings in config\weather.gas to determine how often the lightning will flash and how long after the flash the thunder occurs.

[snow] { ... }
Snow settings go inside the [snow] block. snow_density is the only snow setting, currently.

snow_density
This is how thick the snow will be. Default is 0 (not snowing). The maximum is defined in config\weather.gas (we set it to 400).

[wind] { ... }
Wind settings to inside the [wind] block.

wind_velocity
Velocity of the wind, in meters per second. Default is 0. If you set this above 5, you may see very strange things happen with effects that are not wind immune, if you make any. In tests, monsters that breathed non-wind-immune fire had a lot of trouble hitting players right in front of them when the wind was set to 20+. We tend to leave the wind at 5 or less.

wind_direction
Direction of the wind, specified in radians angles from the north vector. So pi/2 would be east, pi would be south, etc.

Warning! Danger! Advanced stuff! Frustum settings are really powerful and dangerous!!
It is recommended that you do not change the frustum settings unless you really know what you're doing, or you don't mind experimenting with things that can seriously change the game (and its performance) at a fundamental level.

[frustum] { ... }
If you are going to set the frustum width and height, it needs to be inside of a frustum block.

frustum_width
Measurement is meters, recommended value is 45. We chose this number after carefully weighing world detail vs. system performance. This number is the radius of the frustum, where the player character is at the middle. That means that 45 meters out from the character in all directions, the world is loaded. If this number is changed to 55, you could push the fog out farther and you wouldn't see the edge of the world, because you have pushed back the edge of the world itself. The major downside of this is that you have dramatically increased the number of things that the game has to keep in memory and actively process.

frustum_height
Measurement is meters, default is 1000. We chose this number just because it was large enough that we wouldn't have to worry about it. This number was chosen before we came up with the crazy idea of really tall towers. When we came up with towers that would have bad guys on every floor, we suddenly had too many monsters in the frustum, so we changed the frustum height to 60. Even this is pretty large; it's very unlikely that you would ever notice in-game that the frustum isn't infinite in the vertical dimension.

This is an important quirk: if you switch to a mood that does not have a frustum measurement defined, then the current frustum size will be used. This is not usually a big deal, so here's an example of a (fairly odd) case where it would actually matter. Let's say that you have changed the current frustum width to be 5. Then the player walks through a trigger boundary and activates a mood with a frustum width of 45 and a transition time of 20 seconds. So far, so good...the frustum slowly grows out to 45 meters over 20 seconds. But the player walks across a new mood trigger after only 10 seconds. The new mood trigger has no frustum size defined, so it simply uses the current frustum size. Their current frustum size is 25 meters - halfway between 5 and 45 - and it's going to stay at 25 meters because there was no frustum width defined in the new mood. If the new mood didn't account for this, and has the fog pushed out to 45 or 55 meters, the player will easily see the edge of the world, and this is a bad thing. In one of our worlds, we changed the frustum size in a few moods, so we took the precaution of putting a [frustum] block in every mood, and explicitly setting the frustum width and height to the defaults. This way, we never run into the above case, and the player should never be able to see the edge of the world.

A Practical Exercise
<Back to Table of Contents>

This is an example of writing a new mood and setting a trigger to give the mood to the player.

The first thing to do is create a new mood file, unless you already have one ready. For the purposes of our example, let's say your mod group is Dungeon Siege Fans (DSF), and your map name is LaserWorld. The mood that's needed is a dark blue fog for a futuristic indoor arena, along with some cool techno music (in a file that we'll assume you added into \sound\music\cool_techno.mp3). If you want to use a piece of music that actually exists, substitute s_m_maintheme_03 for cool_techno.

The new mood file would go in world\global\moods\LaserWorld\moods.gas. The filename can be anything, really, as long as it ends with .gas. You can have as many mood files in there as you want. Don't forget that file names cannot contain spaces.

Inside the new file will be your new mood. All of the mood settings will be inside a block that looks like this:

[mood_setting*]
{

}
First, we need a name. The name of your group is DSF, the map is LaserWorld, and the region is arena3 (let's say). Here's a good name for the mood:

mood_name = dsf_laserworld_arena3_1;
When the player arrives at the arena, we want the fog to turn blue pretty quickly. Use with a two-second transition and flag the arena as "interior":

transition_time	= 2.0f;
interior = true;
Let's set up the blue fog. Start the fog fairly far away from the player, and have a 10-meter distance from where the fog starts to where it's too thick to see through. Why? Well, it's nicer looking than having a sudden solid line of fog.

For the color, we want a dark blue fog. Use a small amount of red and green, with a larger amount of blue: 0xFF202030 will work. I pulled a value out of the air here...if you really cared about the color, it would be good to look at it in-game and adjust it as needed.

For fog_density, see the explanation in the field definitions above.

    [fog]
    {
        fog_near_dist = 40.0f;
        fog_far_dist = 50.0f;
        fog_lowdetail_near_dist	= 35.0f;
        fog_lowdetail_far_dist = 45.0f;
        fog_color = 0xFF202030;
        fog_density = 0.5f;
    }
Next...some music. We're going to use the music track "cool_techno.mp3", and it loops, so we will make sure to set the repeat delay to 0 (leaving the delay field out would do the same thing). The room type is a very large room, so an EAX value of "arena" seems like it would fit.

    [music]
    {
        standard_repeat_delay = 0.0f;
        standard_track = "cool_techno";
        room_type = rt_arena;
    }
Ta da! We now have a mood that looks like this:

[mood_setting*]
{
    mood_name = dsf_laserworld_arena3_1;
    transition_time	= 2.0f;
    interior = true;

    [fog]
    {
        fog_near_dist = 40.0f;
        fog_far_dist = 50.0f;
        fog_lowdetail_near_dist = 35.0f;
        fog_lowdetail_far_dist = 45.0f;
        fog_color = 0xFF202030;
        fog_density	= 0.5f;
    }

    [music]
    {
        standard_repeat_delay = 0.0f;
        standard_track = "cool_techno";
        room_type = rt_arena;
    }

}
The next step is to make a trigger that will give this mood to a player in the game. If you haven't read Siege U: 203A/B - Triggers I/II, then these instructions may not make sense to you - I recommend consulting that document!

Place a trigger_generic and add a spatial condition: party_member_within_bounding_box. Move the trigger around until the bounding box covers a section of the ground that the player can walk to. For the action, choose mood_change and specify dsf_laserworld_arena3_1 as the mood name. Save objects before you quit, otherwise your changes won't be saved.

Now you should be able to go into your test map, walk through the trigger, and see blue fog. Congratulations, you've made a new mood!

Four Complex Moods
<Back to Table of Contents>

Mood A
This mood is used just outside of the Utraean Peninsula's castle. It is rainy, with a moderately dark gray fog. Transition time is 6 seconds to make it start raining kind of suddenly, yet give the fog time to change without being too obvious. A [frustum] block is present, explicitly setting the frustum measurements to the defaults (not generally needed, see the above comments in the [frustum] section.
[mood_setting*]
{
   //outside the castle, rainy
   mood_name = multiplayer_world_castle_1;
   transition_time = 6.0f;
   interior = false;

   [fog]
   {
      fog_near_dist = 30.0f;
      fog_far_dist = 50.0f;
      fog_lowdetail_near_dist = 30.0f;
      fog_lowdetail_far_dist = 45.0f;
      fog_color = 0xFF505050;
      fog_density = 0.5f;
   }

   [frustum]
   {
      frustum_width = 45.0f;
      frustum_height = 60.0f;
   }

   [rain]
   {
      rain_density = 200.0f;
   }

   [music]
   {
      ambient_track = "";
      standard_intro_delay = 0.0f;
      standard_track = "s_m_CastleEhb_02";
      room_type = rt_generic;
   }
}
Mood B
This mood has darker fog, a little more rain, some wind, and lightning turned on. The transition time is short because none of the changes are very severe.
[mood_setting*]
{
   //outside the castle, stormy
   mood_name = multiplayer_world_castle_2;
   transition_time = 3.0f;
   interior = false;

   [fog]
   {
      fog_near_dist = 30.0f;
      fog_far_dist = 50.0f;
      fog_lowdetail_near_dist = 30.0f;
      fog_lowdetail_far_dist = 45.0f;
      fog_color = 0xFF404040;
      fog_density = 0.5f;
   }

   [frustum]
   {
      frustum_width = 45.0f;
      frustum_height = 60.0f;
   }

   [rain]
   {
      rain_density = 225.0f;
      lightning = true;
   }

   [wind]
   {
      wind_velocity = 0.5f;
      wind_direction = 0.0f;
   }

   [music]
   {
      ambient_track = "";
      standard_track = "s_m_CastleEhb_03";
      room_type = rt_generic;
   }
}
Mood C
This mood is used in an unusual place in the Utraean Peninsula. The player is ascending a mountain, and this is after they have climbed up out of a forest and they are about to go inside a cave. After going through the cave, they will come out in snowy terrain. So this mood has some wind and just a tiny bit of snow (there are also some snow decals on the ground in this area). The fog is dark gray, keeping nearly the same level of brightness as the previous fog, just getting rid of the forest green. The fog is also closer than normal. This is to create a feeling of being in a foggy bank at high altitude.
[mood_setting*]
{
   // This mood has a small amount of snow and gray fog
   // Music 3
   mood_name = multiplayer_world_df2ds_4;
   transition_time = 3.0f;
   interior = false;

   [frustum]
   {
      frustum_width = 45.0f;
      frustum_height = 60.0f;
   }

   [fog]
   {
      fog_near_dist = 25.0f;
      fog_far_dist = 45.0f;
      fog_lowdetail_near_dist = 25.0f;
      fog_lowdetail_far_dist = 40.0f;
      fog_color = 0xFF363636;
      fog_density = 0.6f;
   }

   [wind]
   {
      wind_velocity = 1.25f;
      wind_direction = 1.57f;
   }

   [snow]
   {
      snow_density = 30;
   }

   [rain] {} // empty [rain] block for lightning manipulation

   [music]
   {
      ambient_track = "s_e_ambient_woods_01";
      standard_repeat_delay = 10.0f;
      standard_track = "s_m_DarkForest_03";
      room_type = rt_generic;
   }
}
Mood D
This mood is on top of the mountain. It has the overridden [sun] block to prevent seeing yellow snow, and our brightest fog. There is wind, along with a wind sound in the ambient track. Note the extremely close neardist for the fog; this forces the player to view everything (including their own character) through fog, reinforcing the feeling of being of being in a foggy snowstorm. It is snowing, with moderately strong wind.
[mood_setting*]
{
   // Snowy, bright mood to be used near the top of the mountain
   mood_name = multiplayer_world_df2ds_7;
   transition_time = 5.0f;
   interior = false;

   [frustum]
   {
      frustum_width = 45.0f;
      frustum_height = 60.0f;
   }

   [sun]
   {
      00h00m:color = 0xFF000000;
      05h00m:color = 0xFF1A1A00;
      06h00m:color = 0xFF66330D;
      07h00m:color = 0xFFB39A40;
      08h00m:color = 0xFFB3B3B3;
      10h00m:color = 0xFFF0F0FF;
      12h00m:color = 0xFFFFFFFF;
      19h00m:color = 0xFFB39A9A;
      20h00m:color = 0xFF66334D;
      21h00m:color = 0xFF330D40;
      22h00m:color = 0xFF000000;
      23h00m:color = 0xFF000000;
   }

   [fog]
   {
      fog_near_dist = 5.0f;
      fog_far_dist = 40.0f;
      fog_lowdetail_near_dist = 5.0f;
      fog_lowdetail_far_dist = 35.0f;
      fog_color = 0xFFc0c0c8;
      fog_density = 0.6f;
   }

   [wind]
   {
      wind_velocity = 2.5f;
      wind_direction = 3.14f;
   }

   [snow]
   {
      snow_density = 200;
   }

   [rain] {} // empty [rain] block for lightning manipulation

   [music]
   {
      ambient_track = "s_e_ambient_wind_01b";
      standard_repeat_delay = 10.0f;
      standard_track = "s_m_Glacern_02";
      room_type = rt_generic;
   }
}

Conclusion
<Back to Table of Contents>

That concludes this Siege U section on Moods. We have covered the definition and composition of moods, where and when moods are used, and how often moods are updated. We have looked a several examples of simple and complex moods. As you can see, creating moods doesn't have to be hard. There are plenty already available, and if you want to make your own, cutting, pasting, and editing an existing mood is the easiest way to get started.

Have fun with moods and good luck!

Appendix A: EAX Settings
<Back to Table of Contents>

This is a list of all EAX settings. They are self-explanatory.

rt_alley
rt_arena
rt_auditorium
rt_bathroom
rt_carpetedhallway
rt_cave
rt_city
rt_concerthall
rt_dizzy
rt_drugged
rt_forest
rt_generic
rt_hallway
rt_hangar
rt_livingroom
rt_mountains
rt_paddedcell
rt_parkinglot
rt_plain
rt_psychotic
rt_quarry
rt_room
rt_sewerpipe
rt_stonecorridor
rt_stoneroom
rt_underwater

Appendix B: Tips
<Back to Table of Contents>

  • Transition time is really a powerful tool. You can make drastic changes in fog color much less noticeable by having them take 20 seconds. You can change music very suddenly to a loud battle song, or gently switch from one background piece to another. Anytime the player goes from inside to outside or vice versa, they will expect to see a very rapid change in the fog. Likewise, they won't be expecting any quick changes while they are marching through a forest or field. By working with these expectations you can keep mood transitions as invisible or obvious as you want.

  • Go ahead and go a little crazy with the fog colors. Black is usually required when in a dungeon, but when the player is outside, gray gets boring. If someone had told me near the beginning of the project that we would end up having red, orange, green, blue, purple, and gray fogs, I'm not sure I would have believed them. Gray works most of the time, but adding a little color to the fog can make the environments feel more realistic.

  • Sometimes, it's nice to have a lot of moods with subtle changes between them. You could use a whole series of moods with different levels of gray fog so that as the intrepid adventurer got closer to the Den of Evil, it gets darker, and darker, and darker... (This would also make it so that if they turned around and walked away, they would walk back through the mood triggers, making it lighter, and lighter...)

  • The battle_track is a third music track that can be used, but it will take a little extra work to get it to work, and you will have to deal with skrit. It was originally added so that the standard track could play while the player was exploring, and then the battle track would play while the player was fighting. Fighting is so common, however, that it became an annoyance to have two different tracks of music constantly starting up and then changing. So the battle track became an unused feature. The mood_manager.skrit doesn't even play this track...but it could. It is located at world\global\moods\mood_manager.skrit. Discussion of the mood manager is beyond the scope of this introduction to moods. If enough demand is there, a 300-level document may be called for and created.

  • The weather will have a chance of getting randomly more or less dense every 15 seconds. This behavior is defined in the mood manager. It will never go below 50% or above 150% of the value specified in the mood. The wind will also vary in speed and direction in a similar manner, never going more than +/- 50% velocity. If the rain goes over density 200 this way, lightning will be turned on. If the rain goes below density 200 this way, lightning will be turned off.

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_205 Fades

Fades

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

What This Tutorial Will Teach You

Overview Of Fades

How Nodes Are Faded

Fade Fields In Siege Editor

A Simple Example Of A Fade

Wildcards

Defining Wildcards

Using Wildcards, or Why Nodes Have 3 Fade Values

How The Siege Engine Keeps Track Of It All

Fading Tools In The Siege Editor

An Easy-To-Use SE Fading Tool

A More Advanced SE Fading Tool

Elevator Fades

Why Are Fade Triggers On Elevators Different?

How Do You Set Fades (Or Moods) On Elevators?

Common Fading Scenarios

Practical Exercise: A Two-Story House Fade

Advanced Fading Scenarios

Conclusion

Introduction

Fading is the act of making nodes visible or invisible. It is an important part of a world that never has a loading screen - anytime you go into a house, cave, dungeon, etc., certain pieces of the terrain will need to disappear to allow the player to continue playing. Likewise, when players leave a dungeon or house, the terrain will need to re-appear. All objects and actors that are on terrain that is faded out will also be faded out. An important rule to remember is to never allow player characters to stand on faded out terrain, as they will no longer see their own characters, or be able to interact with their surroundings.

What You Need For This Tutorial
  • Dungeon Siege, updated to version 1.09B or later
  • Siege Editor, updated to version 1.09B.313 or later
  • A text editor
What This Tutorial Assumes You Have Already Learned
  • Siege U: 100
  • Siege U: 200 - Concepts and Terminology
  • Siege U: 203A - Triggers I
  • Siege U: 203B - Triggers II
  • Siege U: 204 - Moods
What This Tutorial Will Teach You
  • How nodes are faded
  • How wildcards are used
  • How the Siege Engine tracks fades
  • How fades are manipulated in Siege Editor
  • Examples of common and advanced fades

Overview of Fades
<Back to Table of Contents>

How Nodes Are Faded
Fades can be done three different ways: node flags, triggers, and elevators.

Node Flags
The first method is very simple: a flag on the node can be set so that it will fade away if the node comes between the camera and the player's character. This is done by opening up the node properties when a node is selected, and making sure the checkbox "camera fade" is checked (under the "General" tab). The most common uses for this are doorway arches, or rock arches at cave entrances. If you use this, you should uncheck both "bounds camera" and "occludes camera" so that the node simply fades away and does not force the camera to move.

Fade Triggers
The most commonly used method of fading is a fade trigger. By using triggers, we can fade as many or as few nodes as we want to, and they can be anywhere in the world; the player does not necessarily have to be close to the nodes being faded.

A fade trigger needs to have a condition that is satisfied by a party member. There are three main conditions used for this: "party member within node", "party member within bounding box", and "party member within sphere". There are also conditions that deal with "trigger groups" that can be used, but "trigger groups" do not need to be used for fade triggers (refer to Siege U: 203B - Triggers II). You may see many examples of fade trigger groups in the Dungeon Siege maps, but changes near the end of the project rendered this an obsolete method of fading, and we just didn't have the time to convert them all to a more efficient method.

The fade trigger's action can be fade_nodes or fade_nodes_global. Fade_node_outer_local_party will apply the fade to everyone who was not detected by the trigger, but is otherwise identical to fade_nodes. This was added as a feature during the design process, but ended up unused. Since it has not been used and tested thoroughly, it may not work as intended. Fade_node (not to be confused with fade_nodes) is legacy and will probably be removed from Siege Editor in the future.

Fade_nodes is the most common method of fading because it is so powerful and applies only to the party members who were detected by the trigger (Result of Condition - see Siege U 203A). This is what the designer wants most of the time. Consider the entrance to a dungeon: anyone who walks down the stairs to the dungeon should see the terrain above the dungeon fade out. We do not want the party members who are still standing on that terrain outside the dungeon to have the fade applied to them - they would then be standing on faded-out terrain (a Bad Thing).

Fade_nodes_global is like fade_nodes, except that it works for everyone, even people who join the game later. This exists for one specific situation: inside a dungeon, when a player walks near an unexplored room that has node caps on it. Node caps are black nodes which, when placed over a room, prevent the player from seeing into the room before they enter it. We usually place fade triggers near the entrances to these rooms to fade off the node caps when the player's in-game character approaches them. Unlike other fades, we want these nodes to fade away for everyone, forever--even if they join in the middle of the game, or if they are standing on the other side of the world. When these other characters go into that dungeon, they should see the revealed room, and not see node caps over it as if it had not yet been discovered.

The parameters for fade_nodes_global are the same as fade_nodes. Be careful when you use fade_nodes_global; we use this for fading off node caps over dungeon rooms, and not much else. (Just FYI: The only way to undo a global node fade-out is with a global node fade-in.)

Elevators
Elevators are covered later, after more terms are defined and examples are given.

Fade Fields In Siege Editor
When you add a fading Action to a trigger in Siege Editor, there are five parameters to that Action: region GUID, Section, Level, Object, and Fade Type.

Region GUIDs
Region GUIDs are 8-digit hexadecimal numbers that are unique to each region within the map. You can see the current region's GUID by looking at "region" under the "settings" menu. You can also find a region GUID in the main.gas file under world\maps\<map name>\regions\<region name>.

Section/Level/Object
All nodes have three fading terms associated with them: Section, Level, and Object. To fade a node in or out, we have to refer to the region that the node is in (via the region GUID) and these three numbers. The labels (Section/Level/Object) are meaningless - the only thing important about them is that they give us a place to assign values to nodes, and refer back to them later. Since the names don't mean anything, when referring to these values, we simply refer to them by the three numerical values that they hold. Throughout this document, there will be sets of numbers listed like this: (5, 15, -1). This is simply an abbreviation for Section 5, Level 15, Object -1. The default setting for nodes is (1, -1, -1). While region GUIDs use hexadecimal; Section, Level, and Object values are decimal only.

Fade Type
Fade Type has several options. Fade "out:black" will fade nodes completely out. Fade "out:alpha" will fade nodes out most of the way, leaving the nodes almost transparent. Fade "in" will cause nodes that have been faded out to fade back in. The "out:instant" setting is just like "out:black" except that the nodes do not fade smoothly away, they disappear instantly. "In:instant" works the same way, fading in nodes instantly.

A Simple Example Of A Fade
A region with GUID 0x88993344 has many nodes, most of which are flagged as (1, -1, -1). There is one house in the region, and it has a roof flagged (1, 2, -1). A fade trigger could be set down in the doorway of the house with a condition of "party member within bounding box" and the action "fade_nodes." The bounding box would need to be rotated and sized appropriately (refer to Siege U: 203A - Triggers I), and fade nodes would need the following values:
Region GUID: 0x88993344
Section: 1
Level: 2
Object: -1
Fade Type: out:black
Then when the player walks into the trigger, the roof should fade away completely, letting the player see the interior of the house.

Wildcards
<Back to Table of Contents>

Defining Wildcards
So far in our discussion of fades, the "-1"value hasn't been explained. It's a little bit complicated because it has a double meaning. When nodes are flagged with -1, that value stands for "no value". So the default node flags of (1,-1,-1) really could be seen as (1,nothing,nothing). But -1 means something completely different when used in a fade trigger. When fading nodes, -1 is a wildcard. It can be read as "anything". This can be pretty powerful.

Using Wildcards, or Why Nodes Have 3 Fade Values
Using wildcards can save a lot of time, and allow you to fade a large number of differently flagged nodes with a drastically reduced number of fade triggers.

Here is an illustrative example:
A region has five houses near each other, and one of those houses has a dungeon in the basement. Most of the surface nodes in the region are flagged (1, -1, -1), and the dungeon nodes are flagged (2,-1,-1). The house roofs are flagged (1, 1, -1), (1, 2, -1), (1, 3, -1), (1, 4, -1), and (1, 5, -1). Each time a player walks into a house, they walk through a trigger that fades off the roof, and that looks how you would expect: (1, 1, -1) for the first one, (1, 2, -1) for the second, etc. When the player walks down into the dungeon, they walk through a trigger that looks a little different: (1, -1, -1). This means that the game will fade all nodes in that region that have (1, anything, anything). So with just one fade action, all of the house roofs fade away along with the rest of the nodes in the region. The dungeon doesn't fade away because it has a Section value of 2, not 1 (2, -1, -1).

However, with the power of wildcards, there are some things to watch out for. Avoid overlapping fades. This can occur when you have one group of nodes faded out (example: (1, 4, 1)), then you fade out a second group with a wildcard that contains the first group (1, -1, -1). When you fade the second group back in (1, -1, -1), the first group will also do the smooth fade-in transition, then instantly go back to being faded out. This looks bad, and can be avoided by flagging the first group differently or using a different fade that doesn't include the first group.

Wildcards can be used to group nodes together for fade purposes. Most of the time we leave all nodes within a region with a value of 1 for the <section>. That way if we add a dungeon entrance, and the player is going to go under the region, we can fade all nodes in the region with a fade trigger that fades out nodes that match (1, -1, -1).

This begs the question, "Why don't we ever use (-1, -1, -1) to fade in or out all nodes in a region?" Using all wildcards in the fade values for a region is an easy way to fade out everything in a region, even if some nodes have very weird values that you stuck in there and forgot about later.

So it appears to be a handy tool for fading...so why didn't we use it that way? Well, sometimes we do want to fade in all nodes in a region, but we can't fade them in with (-1, -1, -1) unless we also faded them out that way. We used to do this, but often an elevator or staircase would take us to the next region, and we would want to fade everything except the transition nodes, so we couldn't use (-1, -1, -1).

Other times we were dealing with regions that could be entered from multiple directions. Leaving and entering the region from one direction might work just fine with (-1, -1, -1), but entering from the other side we might only want to fade in nodes that match (2, -1, -1). In this case we would first have to fade in (-1, -1, -1), then fade out everything except (2, -1, -1) if they had gone out the first direction, and that's just not what we wanted to do. With all of the region changes that we kept doing, we eventually switched to use (1, -1, -1) to refer to the bulk of the nodes in the region, because it was more flexible.

More examples and possible uses for wildcards are given under Advanced Fading Scenarios.

How The Siege Engine Keeps Track Of It All
<Back to Table of Contents>

The way that the engine handles fades isn't completely intuitive. By default, all nodes are visible. The engine maintains a list of nodes that are faded out for each player. Every time that a player walks into a trigger that fades OUT some nodes, the engine ADDS that group of nodes to the list. Every time a player walks through a trigger that fades IN some nodes, the engine REMOVES that group of nodes from the list. This can cause some unexpected behavior when using wildcards. The nodes that are faded out and the nodes that are faded in must match Section, Level, and Object values exactly, including wildcards. Here are a couple of examples to illustrate:
  • Half of the nodes are flagged as (1, -1, -1)
  • The other half are flagged as (2, -1, -1)
  • The player walks through a trigger that fades OUT (1, -1, -1)
  • The player walks through a trigger that fades IN (1, -1, -1)
In this case, everything is fine. The nodes marked (1, -1, -1) are visible. Here is where expectations can cause confusion:
  • The player walks through a trigger that fades OUT (1, -1, -1)
  • The player walks through a trigger that fades IN (-1, -1, -1)
Some of the nodes in the region are faded out, and then the player walks through a trigger that looks like it will fade in all nodes in the entire region. But since the fade out was done with (1, -1, -1), the engine doesn't find a match for (-1, -1, -1) and ignores that fade. So the end result of this situation is that the nodes are still faded out, which is probably not what the designer intended.

Fading Tools In The Siege Editor
<Back to Table of Contents>

An Easy-To-Use SE Fading Tool
Select a node in Siege Editor and look at its Node Properties. Under the Fade Settings tab are the fields for Section, Level, and Object. There are also three buttons: Show, Hide, and Select. These buttons will perform their functions on all nodes in the region that match the fade values of the current node. This can be a great way to see what your fades will look like in-game without leaving the editor.

Example: you have built a two-story inn, and you have flagged all nodes on the second floor as (1, 4, -1). Select a node that is part of the second floor of your inn, and press the "Hide" button, and you will get to see all nodes flagged the same as that node (presumably the whole second floor of the inn) fade away, showing what the player would see after entering the front door. You can make those nodes visible again by pressing the "Show" button. We commonly use this tool to fade away a large number of nodes quickly, so that we can check the fade values on the nodes that are still visible. The "Show All Nodes" command (accessed under the Node Menu) can also be very helpful when working with fades.

There is a button on the toolbar that will hide/show all node caps in the region. This saves the designer from having to fade each group of caps separately when they want to edit the dungeon without the caps getting in the way. For those that are interested, the "Hide Caps" button simply references a node mesh list and hides all node meshes that match the "caps" list. This list is visible under the Node Menu, in Mesh List. "Caps" is one of the available groups, and node meshes can be added or subtracted on the Mesh List window.

A More Advanced SE Fading Tool
"Node Fade Groups" (under the Node Menu) can be a powerful tool for an advanced user. Using this screen is a little complicated, but can make managing your fades a lot easier in the long run. The first thing to do on the Node Fade Groups screen is to create a new group with the "New Group" button on the right. The name doesn't matter; this is something that will stay local on your machine only. Once you have made a group, select it down at the bottom from the pull-down menu "groups". Type in any value you want for section, level, or object, and check the box next to any of the fields that you entered values for. Anything unchecked will function as a wildcard, matching on any value. You can then use the "add" button to add these values to the "fade settings" window. Any time that you select "hide," "show," or "select," all nodes that match the fade settings in the window will be hidden/shown/selected. So you could have a fade group for "castle interior" and another for "castle first floor," etc. This comes in handy when you have a lot of different fade values on nodes in a region, but want to toggle the visibility of those nodes easily.

Elevator Fades
<Back to Table of Contents>

Why Are Fade Triggers On Elevators Different?
This is fairly complicated. Fade triggers work by noticing when a party member crosses the boundary of the trigger. This is true whether the trigger is "party member within node/sphere/bounding box", or even "party member entered trigger group". When players click to go somewhere, the engine figures out all of the steps that they will take to get there. The engine knows when they will cross into the trigger, even before they get there. It even sends this information to other player's computers in a multiplayer environment, as this keeps everything looking smooth for each player.

When your character is going to walk through a trigger boundary, there is a point when your computer sends out information that your character will cross the trigger boundary. At that point, you will cross that trigger boundary no matter what. If you tell your character to stop or do something else, it will first cross the trigger boundary and then follow the new commands that they have been given. This means that if we set up fade/mood triggers on elevators as per normal (like with a "party member within node" condition), the trigger boundary would be on the edge of the elevator node. So when somebody gets close to getting on the elevator, they eventually cross the point of no return, and they WILL get on the elevator...even if it leaves just before they get on it.

Why is it so bad to have someone jump on or off of an elevator just as it leaves? One case could be this: a fade trigger fades away all nodes around the elevator for all players who are currently on the elevator. If someone was just leaving the elevator, they could possibly get this fade, then make it off of the elevator on to the faded out nodes. They would then be stuck on faded-out terrain, which is very, very bad. There are similar cases where someone could jump ONTO an elevator, only to have missed some important fades, so that when he arrives at the destination, all nodes at the destination are still faded out for him. These types of situations could actually be game-breaking experiences (forcing them to reload)--something that should be avoided at all costs.

How Do You Set Fades (Or Moods) On Elevators?
Elevators have two special lines in their "Template Properties" to allow the designer to trigger fades or moods when the elevator moves. Moving1ActionInfo is used for triggering fades and moods when the elevator from its original position to its destination. Moving2ActionInfo is used when the elevator moves back to its original position. The syntax for these lines is this:
[region GUID],[section],[level],[object],[fade type];
[region GUID],[section],[level],[object],[fade type],[delay];
[mood name];
[mood name],[delay];
Any number of these fades and moods can be used, all on the same line. Just don't forget the semicolon at the end of each mood or fade. "Fade type" is just looking for values of "in", "out:black", etc., just like in the triggers. Both the fade and mood can have an optional delay (in seconds) afterwards. Here is an example:
Moving1ActionInfo = 0xA1000033,1,-1,-1,in;0xB1000012,3,4,-1,out:black,2.5;gpg_fh_r1_1;
Here's what happens: as soon as the elevator leaves, the fade and mood that don't have delays specified get applied to all players on the elevator. So the nodes in region 0xA1000033 that are flagged (1, -1, -1) get faded in. Everyone on the platform gets mood gpg_fh_r1_1. Since the second fade has a delay of 2.5 seconds, after this time passes, everyone who was on the elevator when it started moving will get the nodes in region 0xB1000012 flagged (3, 4, -1) faded out.

Common Fading Scenarios
<Back to Table of Contents>

A common fading situation is a set of stairs leading in and out of a dungeon. We usually place a fade trigger at the top of the stairs that will fade the dungeon out (no need to render a dungeon that you can't see, especially when you're leaving it), a fade trigger in the middle of the stairs to fade the dungeon in as well as the original region (to catch players going either direction), and a trigger at the bottom of the stairs to fade the original region out (for players entering the dungeon). This way the player can walk down the stairs and see the dungeon, or walk up the stairs and see the original region.

Another common fading situation is a house. We went through several different methods of fading before we settled on a "best way," so if you look through the maps that shipped with the game, you might see several different methods in place. We didn't necessarily convert all the old fading methods to the newer methods if they still worked. The new way we ended up with is putting a small fade trigger around the doorway to the house. If a player walks through this trigger, the roof fades away. If there is a closed door in the way, we usually mark the trigger as initially inactive and make the door send a "we_trigger_activate" message to the fade trigger when it is opened. Farther away from the house, fade triggers are placed so that when the player walks through these triggers, the roof fades back in.

That said, let's walk through setting up a two-story house to fade!

Practical Exercise: A Two-Story House Fade
<Back to Table of Contents>

Open up a test region. Try placing some t_xxx_flr_08x08-v0 nodes down to get a nice area to walk around. Then place the house that we will use for our fading example: t_grs01_houses_generic-c-log-lv1. t_grs01_houses_generic-c-log-lv2 goes on top of this node, and t_grs01_houses_generic-b-roof-shingle makes a good roof. t_grs01_houses_generic-c-log-lv1-door-top can go in the doorway, and t_grs01_houses_generic-c-log-stairs will work for the stairs.

Select the roof and look at its node properties. Under fade settings, set Section to 1 and Level to 2. Leave Object at -1 for all of the nodes in this example. Select the second floor and set it to (1, 1, -1). This example assumes that you don't have other nodes flagged as (1, 1, -1) or (1, 2, -1). If you have nodes flagged this way, simply substitute a different set of values for these nodes for this tutorial. Select the t_grs01_houses_generic-c-log-lv1-door-top node, open up the node properties, and go to the General tab. Turn off "occludes camera" and "bounds camera" and turn on "camera fade". Select each other house node in turn and turn off "bounds camera".

Place a trigger_generic on the ground near the house. Go into the trigger properties, and click the "new" button in the lower left to add a new trigger to the gizmo. Select the new trigger by clicking on the text that says "trigger_0" under "trigger_generic" in the left panel. Click the "add condition" button on the right side and choose "party member within bounding box" from the drop-down list. Click the "add action" button on the lower-right and choose "fade_nodes" from the list.

In the main Siege Editor window, select Region from the Settings menu, and copy the region GUID. Open the fade trigger back up and paste this value into the "region ID" field in the fade nodes action. Set Section and Level to 1, and the fade type to "out:black". Add another "fade nodes" action to the trigger, and copy the values from the other "fade nodes" action into it. Set the "level" field to 2, however, so that it will fade the roof.

Now move the trigger into the doorway of the house. Make sure that your debug HUD settings allow you to see the bounding box around the trigger, and adjust the size of the bounding box to encompass the entrance to the house. Values of 1.5, 1, 1 for the "half diag x","half diag y", and "half diag z" fields should work. Leave the boundary check on "on_every_enter". Make sure that the trigger is rotated so that the box sticks out on to the stairs in front of the house. Now when the player comes up to the doorway of the house, the second floor and the roof will fade away, letting them navigate inside the house.

Copy the entire trigger gizmo by clicking on it and choosing Edit->Copy (or pressing Ctrl-C), then paste (Ctrl-V) 3 new copies of it in front of the house. Set the "fade type" field in each of these triggers to "in", however, so that if the player walks through the bounding box, the second floor and roof will fade in. Now move the three triggers to form a "U"-shape around the entrance to the house, adjusting the size of the bounding boxes as necessary. If you don't want to bother with sizing them exactly, just set them to have dimensions of 5, 1, 0.25 each. You will have to move and rotate them so that as someone leaves the house, they have to pass through one of these bounding boxes before they have moved too far. Don't allow a "fade out" trigger to overlap a "fade in" trigger.

Copy one of these trigger gizmos inside the house, and move it up on the landing halfway up the stairs. You will probably want to hide the roof and second floor nodes within the editor to make it easier to work inside the house. Open up the trigger properties and remove one of the fade nodes actions. Make sure that the remaining fade nodes action fades off (1, 1, -1) (the second floor). Now copy this trigger gizmo and place the copy next to it on the stairs. The idea is to have one trigger fade the second floor in (when you go up the stairs), and the other trigger fade it out (when you go down the stairs).

This is accomplished by having both triggers be long enough to cover the stairs completely, but very thin (like .25 or .1 for their width). I used values of x = 1, y = .5, z = .1 for my triggers. Try placing the trigger that fades the second floor in right on the landing, and the trigger that fades the second floor out just slightly below the landing. Make sure that they cover all walkable area where they stretch across the stairs so that the player must walk through them.

Try it out! You should have a fully fade-functional two-story house.

Advanced Fading Scenarios
<Back to Table of Contents>

A castle is one of the most complex buildings to fade, as several things need to be taken into consideration. Whenever a player is on the outside of the castle, all they need to see is the outside of the castle. If the interior of the castle is faded in, the engine is doing a lot of unnecessary rendering. When a player goes into the castle, all floors above the player need to be faded away. However, the interior of each floor below them can be faded away as well, again to eliminate unnecessary rendering. With these issues in mind, this is how we divided up castles:
  • Each floor of the building should probably have all nodes flagged with the same [section], to make it easy to fade floors above the player's character.
  • On each floor, there will be "interior" areas that are far away from any outside windows or doorways. These should be flagged with their own [level] so that they are easy to fade away when the player is on a floor above them and can't possibly see these areas. The [object] flag can be set on a per-node basis when there is something like an archway on the interior of a floor of the castle that you might want to fade with a trigger.
A large tower is another interesting case. Conceptually a tower is pretty easy to deal with. If the tower has five floors, then each floor can have all of its nodes flagged with a different Level, while keeping the same Section. So the floors could be something like this: (3, 1, -1), (3, 2, -1), (3, 3, -1), (3, 4, -1), (3, 5, -1). That way if you ever needed to fade the whole tower away, you could use (3, -1, -1).

It should be noted that upon entering the tower, the trigger to fade off each floor above the player will actually need to have a separate fade_nodes action for each floor, rather than using a wildcard (when using this method). That way the fade triggers on each stairway can refer to the floor above them (fading in or out, depending on which way the player is moving), without having to deal with issues from overlapping fade values.

There are some examples of tower fading in Dungeon Siege. Regions fh_r3 and path2dm in map_world (World.dsmap) have small towers, while mega_forest_r2 in multiplayer_world (MPWorld.dsmap) has a large tower with an elevator at the bottom. Region island in multiplayer_world has a medium-sized tower with an elevator at the top!

Conclusion
<Back to Table of Contents>

Some of the concepts in this document are difficult to digest. I recommend experimenting with fades in a small, simple test map before trying anything too complicated. Setting up fade triggers in a 20-floor tower can take quite a while, even if you already have a lot of experience. Try starting with a simple field, where fade triggers will fade different pieces of the ground in and out when you walk through them. After that, try a few houses...maybe even a house with a second floor. Dungeons may or may not be difficult, depending on how many levels they have, and how many rooms have node caps.

There are plenty of fading examples to look at in map_world (World.dsmap):
  • Examples of towns: bt_r1, path2dm, nt_r1, df_r0, and dc_r1 are all regions that have houses or dwellings in them.
  • Examples of dungeons: cr_r1 is a dungeon with a large number of capped rooms, sd_r1 and sd_r2 are both dungeon regions with two floors and elevators.
There are plenty of examples in the other regions as well, but if you can understand and emulate the fading being done in these regions, you will be well on your way to becoming a Fading Master.

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_206 Elevators

Elevators

Introduction

Different Types of Elevators

Elevator Template Property Field Names And What They Mean

An Example

Practical Exercise: Create A Simple Elevator

Place The Nodes

Place The Gizmos And Levers

Set Up The Elevator Gizmo

Set Up The Levers

Test Your Elevator

Special Elevators

act_deact (pressure plate)

Hidden Stairs

Instant (Hub)

Conclusion

Appendix A: Tips

Introduction

In Dungeon Siege, an elevator is a moving node. Any node can be an elevator; it just needs an elevator gizmo to control it. The elevator gizmo contains information about the nodes and connections needed to move the elevator to the destination and back. When the elevator gizmo receives a we_req_activate message, it will move the elevator node. After the elevator reaches its destination, the next time the elevator gizmo receives a we_req_activate, it will move again.

Different Types of Elevators
<Back to Table of Contents>

There are different types of elevator gizmos, depending on how the nodes involved with the elevator are connected. We have broken up the description of the connections involved into three terms: states, connections, and nodes.

States refer to the number of different places where the elevator will stop moving. This is usually two; most elevators simply go back and forth between two places.

Connections refer to the number of doors that the elevator node will need to use to establish a union at each destination. If an elevator node lands in a space between two nodes, both of the nodes next to the elevator will need to establish a connection to allow the player to walk to them. In this case, we would want to use a two-connection elevator gizmo.

Nodes are designated blocks of distance that the elevator will be moving between. Sometimes an elevator goes between two different doors of the same node; in this case we would want a one-node elevator gizmo. Other times an elevator travels between totally separate nodes; in this case we would want to use a two-node elevator gizmo.

When viewing the elevators, make sure that you are using the "development name" view (under View | Preferences, in the "Content Display" field). Then you will see elevators named with the "2-state, 2-connection, 1-node elevator" style names.

Elevator Template Property Field Names And What They Mean
These field names have been taken from the 2-state, 2-connection, 2-node elevator. Depending on which elevator you use, you may have more or less fields to fill out, but these fields should serve to show how elevators work. If there are extra fields in the elevator you use, they will be similar to these. Often the node being used as an elevator is referred to as "elevator_node," and the node that will be used for a connection to the elevator is "connect_node". Level1 is the term used to identify the origin of the elevator, and level2 is the destination.

elevator_node: The GUID of the node being used as an elevator

elevator_door_level1: The first door that the elevator will use to connect when it returns to its original position

elevator_door_level2: The first door that the elevator will use to connect at its destination

elevator_additionaldoor_level1: The second door that the elevator will use to connect when it returns to its original position

elevator_additionaldoor_level2: The second door that the elevator will use to connect at its destination

connect_node_level1: The GUID of the node that begins connected to the elevator node

connect_door_level1: This is the door that the connect_node_level1 will use to connect to the elevator node when the elevator node returns to its original position

additional_node_level1: The GUID of the second node that begins connected to the elevator node

additional_door_level1: The door that the additional_node will use to connect to the elevator node when the elevator node returns to its original position

connect_node_level2: The GUID of the first destination node

connect_door_level2: The door that the connect_node will use to connect to the elevator at the destination

additional_node_level2: The GUID of the second node at the destination

additional_door_level2: The door that the additional_node will use to connect to the elevator at its destination

(The more complicated elevators may have more connections that need to be filled in, but they are named with names that make sense, and these fields have helpful instructions in the "documentation" field in the template properties on the same line as the field.)

duration: This important field describes how long (in seconds) it will take the elevator to move from one state to another. If this is set near 0, the node will appear to teleport, and unpredictable things can happen. This can cause bad things to occur with multiplayer games and lag, so always keep the duration on elevators at 2 seconds or more.

oneshot_nowait: Set to true if you want 'waitfortrigger = false' to happen only once; essentially making the elevator move once on its own (when it comes into the frustum)

Moving1ActionInfo and Moving2ActionInfo: These fields can be used to fade nodes or change moods for characters on the elevator. This is a very useful thing to do. If someone is inside a cave or building, and they are taking an elevator to a lower floor, it may be required to fade out the upper floor. This field would be used to apply the correct fade to the characters on the elevator. Instructions for doing this are explained in detail in Siege U: 205 - Fades.

level1_waitfortrigger: Defaults to true; set to false if you want the elevator to move without receiving a we_req_activate message when it arrives at level 1

level2_waitfortrigger: Defaults to true; set to false if you want the elevator to move without receiving a we_req_activate message when it arrives at level 2

level1_connectpaths: Defaults to true; set to false if you want keep the elevator from connecting to adjacent nodes at level 1. This would make it so that actors can NOT walk off of the elevator at this level.

level2_connectpaths: Defaults to true; set to false if you want keep the elevator from connecting to adjacent nodes at level 2. This would make it so that actors can NOT walk off of the elevator at this level.

level1_trans2_hint: Elevators can rotate on any axis when they move. This can be fun to play with...don't be afraid to try out some of these values. If you want them to rotate, specify one of these values here:
   AH_NONE
   AH_XAXIS_POSITIVE
   AH_XAXIS_NEGATIVE
   AH_YAXIS_POSITIVE
   AH_YAXIS_NEGATIVE
   AH_ZAXIS_POSITIVE
   AH_ZAXIS_NEGATIVE
level2_trans1_hint: See level1_trans2_hint

Elevators are capable of sending messages just like triggers. The four times that they can do this are labeled Level1, Moving1, Level2, and Moving2.

Level1: When the elevator returns to its origin.
Moving1: When the elevator begins moving from its original position to its destination.
Level2: When the elevator arrives at its destination.
Moving2: When the elevator begins moving back to its origin.
Each of these events has three fields associated with it: Message, Scid, and Message_Delay.

Message: the type of world event message to send. (see Siege U: 203A - Triggers I)
Scid: the receiver of the message
Message_Delay: this is the delay in seconds before the message will be sent. We put in a default value of -1 that can be used to wait for the MCP delay. This delay is roughly equivalent to the amount of time that needs to pass in a multiplayer game before the server is sure that everyone made it on or off of the elevator platform. Lag can vary this time, but it is usually around 0.5 seconds.
Material: this allows you to reference elevator sound calls listed in sounddb.gas in the elevator section. When the elevator moves, it will play this sound.

An Example
This example is taken from region ce_r2 in map_world. There is an elevator in the castle that allows the party to descend to the dungeon. A 2-state, 1-connection, 2-node elevator is used here. Two states: the elevator will either be connected to the castle (at the top), or the dungeon (at the bottom). One connection: the elevator will only connect to one node at the top, and one node at the bottom. Two nodes: the node that the elevator will connect to at the top is different than the node that the elevator will connect to at the bottom. Now it is just a matter of filling in the required values, which look like this:
   elevator_node = 0x20B459F4
   elevator_door_level1 = 1
   elevator_door_level2 = 2
   connect_node_level1 = 0x2B47E614
   connect_door_level1 = 3
   connect_node_level2 = 0xF0613FE4
   connect_door_level2 = 4
   duration = 5
   material = sd_large
It takes five seconds to move from the top connection node (0x2B47E614) to the bottom connection node (0xF0613FE4). If you look at the elevator in the Siege Editor, you can turn on Door Numbers under View Preferences and see why it made sense to use these doors for the connection in this case. (This example does not provide the Moving 1 or 2 ActionInfo, as the fades involved with the castle can be kind of complicated.)

Practical Exercise: Create A Simple Elevator
<Back to Table of Contents>

This example is provided to give you a step-by-step set of instructions that you can use to create an elevator in a test map. The nodes used will be a 24-meter high ledge with an elevator going from the base straight up to the top.

Place The Nodes
These nodes are found under Terrain Nodes/generic/ledge in SE.
  1. Fire up your test map and place this node on the side somewhere: t_xxx_ledg_24-ele-top-a.
  2. Connect t_xxx_ledg_24-ele-bottom-a Door 5 to Door 1 of the first node. If you want, you can place t_xxx_ledg_24-ele-wheels-a on Door 11 of t_xxx_ledg_24-ele-top-a to make it look better.

  3. Connect t_xxx_ledg_ele-platform-a Door 2 to t_xxx_ledg_24-ele-bottom-a Door 8. You may want to tile out some t_xxx_flr_04x04-v0 or t_xxx_flr_08x08-v0 nodes at the top or bottom just to have more room to walk around.

Place The Gizmos And Levers
  1. Place a 2-state, 1-connection, 2-node elevator gizmo (Game Objects/gizmos/elevator) on the elevator platform node.

  2. Place use_points (Game Objects/gizmos/special) on the ground next to the elevator, both at the top and bottom.

  3. Place a third use_point on the elevator platform.
  4. Place a lever_on_off_01 (Game Objects/gizmos/interactive_tools) on the ground at the top, and rotate its x-axis 270 degrees (Object Properties | Rotation tab), so that it is resting on the ground.

  5. Copy this lever to the node at the bottom.

Set Up The Elevator Gizmo Now we are going to hook up the elevator gizmo with the actual elevator nodes. First, select the elevator platform node, then click on Node Menu | Properties and copy the Node GUID from the General tab.

Select the elevator gizmo on the platform and open its properties. Select the "Template Properties" tab and scroll down to the elevator_2s_1c_1n_act_deact section.

Fill in the blanks!
elevator_node: Paste the Node GUID you copied here.

To make things simple, since the elevator can use Door 1 to connect to both the top and bottom nodes, that's what I'll use.
elevator_door_level1: 1
elevator_door_level2: 1
Switch away from the Object Properties window (Alt-Tab will work) and copy the bottom elevator node's GUID. Switch back to the gizmo's properties and continue filling in the fields.

NOTE: Sometimes leaving a Properties window open and switching the focus away can cause erratic behavior (such as the Properties window not accepting text entry) when the focus returns to it. If this happens, simply close the window and reopen it.

The elevator is starting at the bottom, so the bottom node is at "level 1". It uses Door 7 to connect to the elevator node's Door 1.
connect_node_level1: Paste the new Node GUID you copied here.
connect_door_level1: 7
Go grab the upper elevator node's GUID.

The upper node is the destination of the elevator, so it's "level 2". It uses Door 9 to connect to the elevator node's Door 1.
connect_node_level2: Paste the new Node GUID you copied here.
connect_door_level2: 9
The upper node is the destination of the elevator, so it's "level 2". It uses Door 9 to connect to the elevator node's Door 1.
What we have just done is wire the elevator gizmo to move the elevator platform from the bottom ("level1") of the cliff to the top ("level2"). We specified which doors to move between, picking the logical doors on each node.

(don't forget - GUIDs won't match since they are unique per map)

Back to the blanks...the elevator's duration can be whatever you feel like, so I'll set it to five seconds. That tells the gizmo to take five seconds to transition between the bottom and top.
duration: 5
Close the gizmo's properties -- we're done configuring it.

Set Up The Levers Now we need to set up the levers at the top and bottom of the elevator to activate it. The settings below need to be mirrored on both levers, unless specified otherwise. Go ahead and open the bottom lever's properties and scroll down to the on_off_level section.

First, let's set the reset time to be slightly longer than the elevator ride. The duration is 5 seconds, so let's set these to 5.5 seconds. That means that after you use the lever, you won't be able to use the lever for another 5.5 seconds. This should make sure that the elevator is ready to travel again. Something to keep in mind is that if you are designing a multiplayer map with levers at the top and bottom, you may want a longer delay on the levers - this would keep one player from pulling the lever so often that another player can never get off of the elevator.
reset_time: 5.5
reset_time_2: 5.5
The levers simply need to send a we_req_activate message to the elevator when they are clicked on...but they keep track of whether you are turning the lever "on" or "off". To force them to just send the same message in both states, just make sure that on_message and off_message are both set to the same message. Also fill in the Scid of the elevator gizmo so that the levers send to the correct recipient.
On_Scid: 0x00000009 On_Message: we_req_activate Off_Scid: 0x00000009 Off_Message: we_req_activate
So far, both levers are configured the same.

(don't forget - Scids won't match since they are unique per map)

Wire use_points to the levers The levers should have use points specified to make them easier to use. We use use_points with elevators to ensure that the when the player is standing ON the elevator and throws the switch to activate it, they don't run OFF the elevator. You can have multiple use_points per object and multiple objects per use_point. If that is confusing, it should become clear after this next section.
  1. Copy the bottom use_point's (not on the elevator platform) Scid.
  2. Open the bottom lever's Properties and scroll down to the "Placement" section of the "Template Properties" tab.
  3. Paste the use_point's Scid into the use_point_scid field value.
  4. Copy the elevator platform's use_point Scid.
  5. Paste the second Scid into the same field, with a comma between them.

What you should see now is the bottom lever wired to both use_points (labeled as "use point # 0" and "use point # 1"). Now that this lever has two use_points, the player's character will move to the closest use_point when the player clicks on the lever. If he is standing on the elevator, he'll move to the elevator point; if he is not on the elevator, he will stay off of it.

Repeat this for the upper lever, using the upper use_point and the same elevator platform use_point.

Test Your Elevator!
You have wired a simple elevator! Save and tank your map and test it out in Dungeon Siege. A couple of answers for questions you will probably have after testing your map:
  1. If you placed the wheel contraption (t_xxx_ledg_24-ele-wheels-a), the wheel is constantly spinning. That is because we didn't tie the elevator gizmo into its animation activation/deactivation. We didn't cover that since it was an optional feature.
  2. Using one off-the-elevator use_point for the lever can result is a silly look. If you stand on the opposite side of the lever from the use_point and click on the lever, your character will run AROUND the lever before activating it. The solution is to add more use_points to the lever to cover likely directions that the player will approach the elevator from.

Special Elevators
<Back to Table of Contents>

There are three special types of elevators that are different than the rest of the generic elevators: act_deact (pressure plate) elevators, hidden stair elevators, and instant (hub) elevators.

act_deact (Pressure Plate)
Pressure plate elevators are also named act_deact elevators because they take a we_req_activate to move to their destination, and a we_req_deactivate to move back to their origin. These were developed for pressure-plate-triggered elevators, where there could conceivably be a large number of conflicting messages going to the elevator while it is in transit (someone could be walking on and off the pressure plate rapidly.) Most elevators will ignore messages while they are moving, but these elevators will remember the last message sent to them. If the message is telling the elevator to move to where it is already going, it will ignore the message. If the message is telling the elevator to move back to where it came from, it will do that after it gets to its destination.

Hidden Stairs
Stairwell elevators (listed as "specialty hidden stairwell") have a very large number of connections in the elevator gizmo; this is intended to be used for a few very specific nodes. The nodes involved always have their doors labeled in a consistent manner. Even though there are a lot of fields in this elevator gizmo, since it was set up for one particular node, it will always use the same door connections. The best way to set this up is to copy from an example. There are examples in regions "path2crypts" and "cr_r1" in the "map_world" map. The only things you would change are duration, messaging, and node guids.

Instant (Hub)
Instant elevators (Hub/Displacer Elevators) are worthy of a 300-level Siege U doc. They incorporate fading, moods, and elevators. The designer is required to be familiar with much more of the engine than is required for a 200-level course. If enough demand (and time) is there, a 300-level document may be called for and created.

Conclusion
<Back to Table of Contents>

We have covered how elevators work, the types of elevators, and defined the fields associated with them. We walked through building a simple elevator. Finally, we briefly discussed special elevators, though not in much detail. Elevators are fairly straight-forward, so practice is the best way to learn more about them.

Happy modding!
The Gas Powered Team

Appendix A: Tips
<Back to Table of Contents>

  • If you want your elevators to be easy to use, consider placing a lever on the elevator itself, as well as a "call button/lever" at each place the elevator stops. This will ensure that any player that gets access to the elevator will be able to move the elevator.

  • When setting up fades in the elevator, remember that most elevators should remain faded in at all times for all players, so that they can see where the elevator is, even when they are not riding it.

  • Rotating elevators are often more interesting than elevators that just move straight up and down. Either use the rotation hint fields, or enter the door connection information so that the elevator node will be forced to rotate as it moves. That is, if the elevator has Door 1 on the left, and Door 2 on the right, try connecting them in reverse order at the destination. The elevator node will spin as it moves so that the doors line up correctly when it arrives.

  • If you want to have several nodes move at the exact same time, you can set up an elevator gizmo for each node, and send them we_req_activate messages all at the same time. However, you might notice that they are out of sync sometimes. This can be fixed by moving them more than once: the first time that an elevator moves, the engine creates an internal data structure that tells it where and how to move the elevator. If the engine does many of these at the same time, it could mean that it finishes with some of them earlier than others, and starts moving some elevators earlier than others. This is where the oneshot_nowait field can come in handy. If you set this to true, as soon as the elevator enters the frustum, it will move. So if you have several elevator nodes at location 1 that need to move to location 2 at the same time, consider starting them at location 2 with oneshot_nowait = true. They will move to location 1 as soon as they enter the frustum (normally at a distance too far away for the player to see). Their next transition (back to location 2) should have no problems synchronizing.

  • If you are ever working with a very tall elevator shaft, the destination of the elevator node might be out of the frustum. Consider changing to a mood with a larger vertical frustum before the player reaches the elevator, to stream in the destination. As the player walks away from the elevator, you can have them walk through a mood trigger that changes the frustum back to the normal size.

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_207 NISs

Non-Interactive Sequences

Introduction

The Typical NIS

NISs are Real-Time

NISs are Single-Player Only

What This Tutorial Will Teach You

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

Anatomy of a Non-Interactive Sequence

Overview

NIS Gizmo Properties

Making a Simple NIS

Camera Commands vs. Camera Waypoints

Issues in Camera Placement

Placing and Manipulating Cameras

Copying and Pasting Camera Objects

Technical Issues in Camera Placement

Subtitles

Subtitles are Conversations

The Narrator

The Party Wrangler

Solving the Death Problem

Solving the Fidget Problem

Using the Party Wrangler

Other Stuff to Know

Hitting ESC During the NIS

Cameras Delete Themselves

Triggering from Camera Objects

MP Versions of NIS Areas

Conclusion

Appendix A: The 19 Non-Interactive Sequences in Dungeon Siege

Introduction

The "Non-Interactive Sequence", or NIS, is our technical name for the in-game movie sequences. They are so named because they denote parts of the game where the player, sitting in front of his computer, has no control over what is happening on the screen, i.e. it's "non-interactive". These NISs were used as story devices to dramatize certain important events, and to help summarize the "story so far" at the end of each Chapter.

The Typical NIS
During the typical Dungeon Siege NIS, the screen view changes from full-screen to a cinematic wide-screen format, the user interface (UI) disappears, and the camera departs from its orbit around the selected character to move about the world. The camera focuses on the important action taking place, and scrolling text may sometimes appear at the bottom of the screen--depending on whether the NIS has a Narrator or speaking Actor. Finally, at the end of the NIS, the camera returns to the player character, the UI reappears, and the view transitions back to full-screen.

NISs are Real-Time
It is important to note that all Dungeon Siege NISs happen in real-time; they are not pre-rendered. The action on-screen is no different than at any other time in the game-the only differences are the wide-screen format and the fact that the player cannot control his characters. As such, the action on-screen is not always 100% predictable: party members may take a different path to get to their destination, monsters may wander and be standing in a different place than they were before, or they may attack a different target… it may even be a different time of day when the NIS is activated. Accounting for this variability will be a necessary skill in making NISs, and we'll touch on some ways to reduce the randomness you may encounter in your creations.

NISs are Single-Player Only
It is also important to note that NISs in Dungeon Siege occur solely in the Single Player game. NISs don't work in the Multi-Player game, so if a map is meant to be played in both Single and Multi-Player, a Multi-Player non-movie version of the encounter is usually required. Luckily, this is relatively simple to do, and the MP encounter can usually be the SP encounter without camerawork.

What This Tutorial Will Teach You
All in all, making a NIS is actually pretty simple--it's what happens DURING the NIS that is the hard part. In this tutorial, we'll focus mainly on making the NIS: what gizmos to use, how to set the cameras, the parameters involved, and other relevant issues. The stuff that happens in FRONT of the cameras will be mostly up to you for now, and can be as simple or as complex as you have the ability to create.

What You Need For This Tutorial
  • Dungeon Siege (of course)
  • Siege Editor
  • Siege Editor Manual (siege_editor_manual.htm comes bundled with Siege Editor.)
  • A test map you can practice with. You can create your own in the Siege Editor; a large, flat square will do.
What This Tutorial Assumes You Have Already Learned
  • Siege U: 100
  • Siege U: 200 - Dungeon Siege Concepts and Terminology
  • Siege U: 203A - Triggers I
  • Siege U: 203B - Triggers II
  • Siege U: 208A - Non-Player Characters. This is useful in learning about conversations. Conversations behave a little differently during a NIS, and while we touch on those differences here, we don't cover conversations in full.

Anatomy of a Non-Interactive Sequence
<Back to Table of Contents>

Overview
There are four gizmos that form the backbone of NIS construction, found in SE under /Game Objects/gizmos/commands/nis:
  • cmd_enter_nis
  • cmd_camera_command
  • cmd_camera_waypoint
  • cmd_leave_nis
The structure of a NIS is basically a chain. The start of the chain is the "enter nis" gizmo (cmd_enter_nis), which takes control away from the player and forces the wide-screen view format the first time it receives a "we_req_activate" message. The "enter nis" gizmo is linked to one or more "camera commands" (cmd_camera_command) in sequence, which defines the actual path the in-game camera will take during the NIS. The end of the chain is the "leave nis" gizmo (cmd_leave_nis), which returns the camera to its original location above the player and returns the view format to full-screen.

The "camera waypoint" (cmd_camera_waypoint) is an extension of the camera command-it must be linked AFTER a camera command, and cannot exist by itself. We'll talk more about these later (see Camera Commands vs. Camera Waypoints below).

NIS Gizmo Properties
Before we actually create a NIS, let's go over the properties of each gizmo, found on the "Template Properties" tab.

Enter NIS (cmd_enter_nis)
  • next_scid: (SCID) This will be the SCID of the first camera command in the NIS sequence. This should never be the SCID of a camera waypoint. Default: 0. (SCID)
  • widescreen_delay: (# seconds) Time in seconds it takes to go from full-screen to wide-screen mode. 0 is instant. Default: 1.
  • widescreen_enabled: (true/false) Determines whether wide-screen mode is used for this NIS. If "false", the NIS plays in full-screen mode. Default: "true".
  • widescreen_fov: (# degrees) Angular field-of-view during the NIS. The default FOV in-game (non-NIS) is "60". Default: 80.
  • widescreen_width: (0.0-1.0) Normalized screen width during wide-screen mode. A value of "1" is 100% screen width, whereas a value of ".5" is 50% screen width. Default: 1.
  • widescreen_height: (0.0-1.0) Normalized screen height during wide-screen mode. A value of "1" is 100% screen height, whereas a value of ".5" is 50% screen height. This is what makes the black bars at the top and bottom of the screen. Default: ".75".
  • select_catalyst: (true/false) If true, the catalyst of the NIS-start trigger is selected (exactly as if the player had selected him), which centers the frustum on his location. This prevents NISs from trying to view potentially unloaded space. Default: false.

    NOTE: This is an important flag. To see why, create a NIS that starts from a party_member_within_bounding_box trigger, then approach it with 2 characters. Select the first character, and click beyond the bounding box trigger, so he'll eventually walk through it. Then select ONLY the second character, and walk in the opposite direction! As soon as the first character walks into the trigger, the NIS will start, but since the SECOND character is selected (with the frustum centered on him), the NIS cameras may be looking into unloaded space, or the space will unload during the NIS, hanging the game. Setting this flag to "true" will cause the game to force-select the character that walked into the trigger (the party_member_within_bounding_box), assuring that the NIS will exist completely in the frustum.

  • game_speed: (-1, 0-1) Forces a particular game speed during the NIS, where "1" is normal game speed, and "-1" does not change game speed. Values less than "1" can result in "Matrix"-like slow-motion sequences. Forcing a game speed of "1" is usually desired, since it will avoid timing errors when players are running at a game speed other than 1x and you are using time-critical speed-locked elements like .wav files (e.g. narration) in the NIS.

    NOTE: Camera commands have a "game_speed" value that overrides this one, and their default is "1". Unless all cameras are set to "-1", the value set here will have no effect. You can either change all the camera game_speed fields to your desired value(s), or set them all to "-1" and set your desired speed here… your choice. Default: 1.

  • music_volume: (0-127) Volume of the music track during the NIS. Default: 127.
  • music_fade_time: (# seconds) Duration in seconds for the music to fade to the new volume level. Default: 1.
Camera Command (cmd_camera_command)
  • next_scid: (SCID) The SCID of the next camera command or waypoint in the sequence. Do not leave at "0", since the NIS will stop at this camera view and never end, forcing a reboot. Default: 0.
  • duration: (# seconds) How long this camera command is active. If there are camera waypoints (cmd_camera_waypoint) AFTER the camera command, this duration determines how long the entire sequence (camera command + waypoints) is active. Default: 5.
  • order: (cor_pan / cor_snap) This field determines how this camera will be transitioned TO from the PREVIOUS camera. "Cor_pan" indicates that the view will slide in a straight line from the previous camera position to this one, over a number of seconds equal to the "duration" field. "Cor_snap" indicates that the view will "snap" immediately to this position, and then hold this position for a number of seconds equal to the "duration".
  • game_speed: (-1, 0-1) Identical to the same field in the "enter nis" gizmo, except this field overrides the "enter nis" gizmo for the duration of this camera command only. Default: 1.
Camera Waypoint (cmd_camera_waypoint)
  • next_scid: (SCID) The SCID of the next camera command or waypoint. Default: 0.
Leave NIS (cmd_leave_nis)
  • normalscreen_delay: (# seconds) Time in seconds it takes to go from wide-screen to full-screen mode. 0 is instant. Default: 1.
  • duration: (# seconds) Time in seconds the camera view pans from the last NIS camera view to the location of the player camera before the NIS began. Default: 2.
  • music_fade_time: (# seconds) Time in seconds it takes for the music level to return to normal. Default: 1.
Making a Simple NIS
Let's put together a simple NIS to become familiar with the motions involved. In this example, we'll use two camera commands to move the view around the player character. We'll leave all values at their defaults, but you're more than welcome to experiment with them.

As previously mentioned, the NIS begins when the "nis enter" gizmo receives a "we_req_activate" message. This message can technically come from anywhere, but because we want our cameras to focus on the player character, we need to know where the character will be-so we'll send an activate message from a "party_member_within_sphere" trigger.

Place the Gizmos
1. Open your test map and place the following gizmos nearby:
  • trigger_generic
  • cmd_enter_nis
  • cmd_camera_command (x2)
  • cmd_leave_nis
Wire the Trigger
2. Place the trigger_generic somewhere your character will walk.
3. Open the trigger_generic's "Template Properties" tab and edit the following:
a. dev_instance_text: As mentioned in the Trigger tutorials, it is always a good idea to label your triggers for later comprehension. Since this trigger will kick off our NIS, enter "START NIS" here.
b. is_multi_player: Set to "false". NISs don't exist in MP, so this trigger gizmo doesn't need to exist (and take up resources) in MP either.
4. Open the trigger_generic's "Trigger Properties" tab and do the following:
a. Create a new trigger instance.
b. Trigger Properties: Uncheck Multi-Player. We're doing this for sanity only. Because the gizmo's "is_multi_player" flag is "false", this trigger instance won't exist in MP. However, it is all too easy to look at these trigger instance flags and assume that they match the gizmo's settings. Always set these flags accurately, and you won't regret it when you're sifting through dozens of triggers in a complicated sequence.
c. Trigger Properties: Check the One Shot flag. All NISs are essentially one-shot since camera commands delete themselves on completion. Since you can't run the NIS again in the same session, there's no reason for this trigger to be hanging around (again, taking up resources).
d. Conditions: Add the "party_member_within_sphere" condition with a Radius of ".5". This ensures we know exactly where the player character will be standing when the NIS begins. The Boundary Check should already be "on_first_enter" since it's a one-shot trigger.
e. Action: Add the "send_world_message" / "we_req_activate" Action, and copy the SCID of the "cmd_enter_nis" gizmo into the "Send to Object" field. Label this action with something like "start nis" in the "Doc" field.
5. Click OK.
6. Save Objects (Save the game with "Save Game Objects" checked).
Now we have our trigger gizmo that kicks off the NIS sequence. When a party member walks into that tiny circle, the "enter nis" gizmo is activated. Now we need to define what we see during the NIS, and that will be accomplished by placing our cameras.

Place the Cameras
There are multiple ways of placing and adjusting cameras (see Issues in Camera Placement below), but we'll start with the easiest way.
7. Move both camera commands near the trigger gizmo, so they will be visible for the next step.
8. Zoom your view of the world close to the trigger gizmo, looking down at it from a 30-45 degree angle. Center the trigger gizmo in the SE's view area, not the center of your monitor. This will be the center of your first NIS camera view.
9. Select the first camera command, and right-click on it. Select "Set Game Object to Camera". The camera command gizmo will snap its position and orientation to the exact position/orientation of your current view.
10. Use the mouse wheel or camera view commands to zoom the view back. You will see the camera command gizmo in the exact place your view was before you zoomed.
NOTE: If you saw the camera snap but don't see the gizmo when you zoom out, you may have run into a small bug. Mouse over the area where the camera should be, and you'll see a selection outline. Left click and hold on it, and move it slightly right or left. If the camera gizmo doesn't appear, increase the distance you move it. Sometimes you will have to zoom far away from the camera gizmo and move it a little for it to appear. In-game NISs may not work correctly if some camera gizmos are present but not visible in SE.
11. Now do the same with the second camera. Move your view to the desired camera location/orientation, select the second camera command, right-click on it, and select "Set Game Object to Camera". The camera will snap to the location of your current view.
12. To double-check the view through any camera, select then right-click on it, and select "Set Camera to Game Object". Instead of snapping the gizmo to the camera view, the camera view will snap to the position/orientation of the gizmo. Any subsequent movement of the camera view will snap it back to its original location (i.e. it will not stay at the location of the gizmo).
Link the NIS Cameras
Our two cameras are now placed. All we need to do now is link them so our NIS knows in what order to use them. Right now, both cameras have default "duration" values of 5 seconds and "order" values of "cor_pan", so the NIS will last at least 10 seconds and will consist of the camera view panning from one camera to the other.
13. Select the "enter nis" gizmo (cmd_enter_nis), right-click on it, and select "Link Object".
14. Move the mouse over the first camera (cmd_camera_command) in the sequence. You should see a tiny black "chain link" icon next to the mouse cursor.
15. Left-click on the first camera command to "link" the "enter nis" gizmo to the first camera command.
16. Select the "enter nis" gizmo again, and you will see a green line connecting it to the first camera, with an animated arrow running from the cmd_enter_nis to the cmd_camera_command.
NOTE: All this really did was copy the SCID of the camera command into the "next_scid" field of the "enter nis" gizmo. It's a graphical and easier way to copy and paste the SCID for NIS gizmos.
17. Select the first camera command, right-click on it, select "Link Object", then left-click on the second camera command.
Finish the NIS by Linking to the "Leave NIS" Gizmo
18. Link the second camera command to the "leave nis" gizmo (cmd_leave_nis).
NOTE: Every NIS MUST end with the cmd_leave_nis gizmo. If not, the game will never transition out of wide-screen mode, the UI will never return, and the player will not be able to control anything. If the player hits ESC, the screen will go black while it tries to resolve all the actions of the NIS in compressed time… but since the last camera command isn't linked to anything, the game will seemingly hang on the black screen. Either way, the player must forcibly break out of the game or reboot.
19. Save Objects.
Test the NIS
Our NIS chain is now complete. Make sure you have saved (with objects), and load up your test map in the game. Walk your character to the party_member_within_sphere trigger and step inside.

If you left all gizmo properties at defaults, the following should occur:
  • 0 seconds: NIS mode is engaged. The UI disappears, the screen begins to transition to wide-screen mode, and player control input is suspended. The first camera command is activated.
  • 1 second: Wide-screen mode is fully engaged. (The "widescreen_delay" field on the cmd_enter_nis gizmo is 1 second.)
  • 5 seconds: The view has smoothly panned from the position of the camera when the NIS was entered to the first camera command position (the first camera command has a delay of 5 seconds and a "cor_pan" order. Because there is no previous camera gizmo to pan from, the view pans from the location of the player camera view at the point the NIS began.) The second camera command is activated.
  • 10 seconds: The view has smoothly panned from the position of the first camera to the position of the second camera (the second camera command has a delay of 5 seconds and a "cor_pan" order). In addition, we begin to transition back to full-screen, since the cmd_leave_nis is the next gizmo in the sequence.
  • 11 seconds: We have returned fully to full-screen mode. (The "normalscreen_delay" value on the cmd_leave_nis gizmo is 1 second.)
  • 12 seconds: The camera view has returned to the same location it was in when the NIS began, and the UI has reappeared. Player now has control of his characters. (The "duration" value on the cmd_leave_nis is set to 2 seconds.)
So the full duration from when the NIS began to when it ended was 12 seconds. As you can see, the full duration is calculated from the durations of all camera commands PLUS the duration on the cmd_leave_nis, which determines how long we transition back from NIS to normal mode.

Didn't Work?
If your NIS didn't work, check the following:
  • Make sure you're playing in SP mode.
  • Make sure all gizmos and trigger instances exist in SP mode.
  • Make sure the trigger gizmo is using a "party_member_within_sphere" condition, and make sure this sphere is actually touching the ground.
  • Make sure the trigger gizmo is sending a "we_req_activate" message to the cmd_enter_nis gizmo.
  • Make sure the NIS chain is linked correctly (cmd_enter_nis -> first cmd_camera_command -> second cmd_camera_command -> cmd_leave_nis)
  • Make sure that all NIS gizmos are in the frustum.
Camera Commands vs. Camera Waypoints
At first glance, it might not be obvious why camera waypoints are useful. In fact, after you use them, it STILL might not be obvious why they are useful. In further fact, we only used them once in Dungeon Siege: in the Chamber of Stars "activation" NIS (map_world/gd_a_r1).

The camera waypoint essentially adds multiple camera positions (waypoints) to a single camera command gizmo, all of which share the same parameters of the "parent" camera command. To elucidate:

Camera waypoints MUST be appended to a camera command, and the ENTIRE chain (camera command + all appended waypoints) share the same "duration". The entire chain also shares the same "order", which, to have any visual effect, must be "cor_pan" (if the entire chain is set to "cor_snap", then the NIS view will immediately snap to the last waypoint in the chain, which negates the purpose of the chain).

The duration of the entire sequence is divided evenly between each gizmo: the camera command and its waypoints. So, if you have a camera command plus 4 waypoints, and the duration of the command is 5 seconds, then each gizmo in the chain is allocated a 1-second duration (command + 4 waypoints = 5. Five objects divided by 5 seconds is 1 second per object). The motion between such waypoints won't look smooth and continuous unless the command and waypoints are exactly equidistant from each other; otherwise, if the distances are different, the view will speed up and slow down in its quest to arrive at each gizmo at 1-second intervals. You can see this yourself by placing a cmd_camera_command and 4 cmd_camera_waypoints roughly equidistant from each other and running the NIS, then wildly changing the distances between the gizmos-some short, some long.

So why use a camera waypoint?

The biggest value in waypoints is in editing long camera sequences that contain cameras at equidistant separation. In the Chamber of Stars NIS, the camera slowly pans around the inside of a circular room, constantly focused on the center. The nine cameras that comprise this sequence are at even distances from each other for the circumference of the room. The timing of this circular sequence depended entirely on events happening in the Chamber, which were always changing during development. Every time the timing changed, the durations on 9 cameras had to be changed, which also pushed the timing off on the cameras further down the chain in this ~80 second NIS. Having to constantly change 9 camera durations, and also having to individually count them to re-figure the total duration of the entire NIS became tiresome. Changing the circular sequence to use a single camera command, with a single duration, and multiple waypoints, simplified editing just a little bit, and was slightly more efficient.

That was a lot of text to explain a small increase in efficiency for a rare NIS case. If you can find better uses for the camera waypoint, don't hesitate to let us know. : )

Issues in Camera Placement
<Back to Table of Contents>

One of the most difficult tasks in making NISs is placing the cameras "just right". As of now (SE v1.09b), our tools for placing and fine-tuning camera views leave a little to be desired. Placing cameras may be a bit clumsy and frustrating at first; but like anything else, if you stick with it, you'll develop your skills and learn your own tricks to make it work for you.

Placing and Manipulating Cameras
There are three basic ways to place and manipulate camera position:
  1. Match View: Snap the camera command gizmo to the current camera position/orientation (Set Game Object to Camera).
  2. Eyeball: Physically move/rotate the camera with mouse/keyboard and/or adjust pitch/yaw/roll with the "Rotation" tab of the camera's Object Properties sheet.
  3. Define: Directly enter values into the "orientation" and "position" component fields on the camera's "Template Properties" tab.
Each one of these approaches has their benefits and drawbacks.

Match View: Set Game Object to Camera
This method consists of moving the SE camera view to the desired location using your mouse, then selecting a cmd_camera_command gizmo, right-clicking on it, and choosing "Set Game Object to Camera". The camera gizmo will snap to the exact location and orientation of your current SE camera view.

Controls
  • Right Mouse Button (Hold): Move camera view on the horizontal plane (forward/back, left/right)
  • SHIFT + RMB (Hold): Move camera view on the vertical plane (up/down)
  • Middle Mouse Button (Roll): Zoom camera view in/out.
  • Middle Mouse Button (Hold): Rotate the camera around a fixed point in space. Where this fixed point is actually located is sometimes a mystery.
  • Camera Controller: This tool can be found under the "View" pull-down menu, allowing more accurate movement of the SE camera.
Pros:
Using the mouse is by far the easiest way to get the "perfect" position for a single camera, since all you need to do is snap a camera command to your view with the "Set Game Object to Camera" command. It's basically WYSIWYG (What You See Is What You Get). Note that the actual view in-game will be slightly higher and wider than the view in SE, especially since the cmd_enter_nis gizmo changes the FOV from 60 to 80 by default.

Using the Camera Controller can offset some of the difficulties in placing multiple cameras after the first (see below).

Cons:
Using the mouse is the least precise method of placing multiple cameras. That is, if you need to place your next two cameras at the same exact height as the first, or at the same pitch angle but 20 feet forward and turned 90 degrees to the left; this is a difficult (if not impossible) way to do it. It is impossible to select another object immediately after the snap, since you are now "inside" the camera object, and are always clicking on it no matter what. You also can't hold down the right mouse button to pan the camera away, since you will be right-clicking on the camera object, bringing up its menu. You will usually have to use the mouse wheel to zoom out, or hold it down to rotate away, but since the rotation origin can be close or far away, you are rarely positioned well enough to place a second camera.

Using the Camera Controller may assist in precisely placing multiple cameras in relation to the first, but otherwise it is a time-consuming way to choose a camera view.

Bugs:
The "Invisible" Camera: When snapping a camera object to the view location in SE, sometimes the camera object will be invisible after the snap, or it will only be visible when the SE camera is at a certain angle relative to it. If you run the NIS with a camera in this state, it may not function correctly-the duration may be incorrect, it may be pointing in a completely different direction, or it may travel along a different track than intended. To fix this, mouse over the position of the "invisible" camera, or, if you don't know its exact location, draw a box over the area you think it's in. You will see a wireframe selection box when the camera is selected, even if the gizmo itself is not visible. Left-click and hold on the selection box, and move it slightly to the right or left. If the gizmo suddenly becomes visible, it is "fixed", and you can return it to its original location. Sometimes you may have to zoom far away from the camera gizmo, then select and move it a little bit, or sometimes you may have to move the camera gizmo all over the screen before it appears. Once it appears, SE knows the camera is there, and the problem is corrected.

NOTE: Some gizmos, including cameras, will vanish if the SE camera gets too close to them. This is normal, and doesn't indicate an "invisible camera" problem.

Eyeball: The Mechanical Method
This method consists of using the keyboard/mouse and the "Rotation" tab of the camera's Object Properties page to maneuver camera objects into position. To check the view of the camera, you can select the camera object, right-click, and choose "Set Camera to Game Object".

Controls
  • Left-Click and Drag: Move the object on the horizontal plane (forward/back, left/right)
  • SHIFT + Left-Click and Drag: Move the object on the vertical plane (up/down, left/right)
  • Arrow Keys (Movement Mode + Rotation Mode ON): Left/Right controls YAW, Up/Down controls PITCH. SHIFT + Up/Down controls ROLL.
  • Arrow Keys (Movement Mode ON + Rotation Mode OFF): Left/Right/Up/Down nudges camera on the horizontal plane. SHIFT + Up/Down nudges camera on vertical plane.
  • Rotation tab: This tab is found on an object's Properties page. There are 4 different ways to set the pitch/yaw/roll of the object here. You can drag indicators around the circle, you can use the arrow buttons, you can use the drop-down list to select a number, or you can directly enter a number into the fields.
    The Rotation Snap Increment setting determines how many degrees of rotation are added per button press, AND keyboard arrow keypress.
Pros:
This is the easiest way to fine-tune the location/orientation of a camera object once it has been placed. You can tweak the values a bit, look through the camera to check position, and tweak it again. You won't screw up what you're doing if you move the SE camera view.

Cons:
The biggest problem is the fact that pitch/yaw/roll isn't measured from the front of the camera, like an airplane, but from a fixed vector in space. If you set the pitch/yaw/roll to 0/0/0, the camera will point to this vector. All pitch/yaw/roll values entered will always be relative to this vector, and not the current vector of the camera. This makes it extremely difficult to apply your desired adjustments to the camera object if it is pointing in a wildly different direction than the zero-vector.

Define: The Mathematical Method
This method consists of explicitly defining the position and orientation values found on the camera gizmo's Properties sheet. The above two methods ultimately provide values for these fields anyway, so sometimes it is easiest to directly enter values or copy/paste them from another camera.

Orientation: This field has a 4-number value. According to James Loe, one of our engineers: "The 4 numbers represent a quaternion. You won't be able to type numbers in yourself and have them make any sense. Or probably even understand anything that comes out." Given that, how can we use this field? By copy/pasting the orientation value from another camera object. If your second camera needs to have the exact same orientation as the first, copy/pasting this value is as exact as you can get.

Position: We can actually edit this field with some knowledge, and it is documented well on the tab. Each object has an XYZ position (in node-based meters) relative to a particular Node.

Pros:
Absolute precision on orientation and position.

Cons:
Directly editing orientation and position can be time-consuming and not very intuitive. Can only use "orientation" by copy/pasting from another object's "orientation" field.

Bugs:
When copy/pasting an orientation or position value into a new camera, something odd happens: the gizmo aspect (the actual camera mesh) will jump to the new location/orientation, but the selection box stays where it is. To further edit the camera in this session, you must use the selection box, not the visible camera object. To fix, save, quit SE and reload the region; and both the camera object and its selection box will be in the correct place.

Copying and Pasting Camera Objects
There are two other useful techniques that bear mentioning:
  1. Copy/Pasting
  2. Drag Copying
Copy/Paste
Performing the standard copy/paste on a camera object can be useful, but it's a little buggy. When you copy (CTRL-C) a camera object, and then paste it (CTRL-V), the new camera object will appear directly above the spot on the ground where the cursor rests, AT THE EXACT HEIGHT AND ORIENTATION AS THE FIRST CAMERA...usually.

Sometimes the new camera will be located at the same height, but will be turned 90 or 180 degrees from the original. Sometimes the new camera will appear so far above or below the ground, you'll think you didn't paste it. Just be aware of these issues when using copy/paste and it should remain a useful operation.

Drag Copy
"Drag Copy" uses the default key "C" to 'clone' the object in place, on exactly the same spot. You can then move this clone relative to the first object. This is highly useful for placing secondary cameras with the same orientation as the first, with a specific relative position.

However, "C" is too easily pressed for me, sitting as it does next to "V", which is the default key to bring up an Object's Properties. Therefore, you might find it safer to assign Drag Copy to "ALT+C" or something similar (View | Keyboard Shortcuts). Otherwise, you may accidentally populate your region with multiple superimposed copies of objects without realizing it.

The only real problem with Drag Copy is that, after you clone the first object, you usually still have the first object selected-NOT the new clone. This can screw up your wiring if you're not careful. Pressing the "Objects - Rotate Selection" key (Q) will cycle your selection through multiple objects under your cursor, allowing you to select the clone.

Technical Issues in Camera Placement
Now that we know how to place camera objects with impunity, it would help to be aware of some technical issues and guidelines. We'll touch on five subjects:
  1. Game Performance and Immersion (Showing the Horizon/Sky)
  2. Fog Distance
  3. Out-of-Frustum Cameras
  4. Linking Cameras Across Regions
  5. Sound
Game Performance and Immersion (Showing the Horizon/Sky)
During a NIS, you never want to show more objects on the screen than the engine was intended to handle. The easiest way to "get in trouble" with this is by flattening your camera angles toward the horizontal when outdoors. The effect of this is to exponentially increase the number of objects the engine is obliged to render, drastically slowing performance. A similar case occurs when the camera is looking down at the ground from a high elevation. When outdoors, it is best to use similar camera angles to the normal camera system.

In addition, certain camera angles will destroy a sense of immersion in the world:
  • There is no horizon. When looking horizontally, the camera will see the mood's fog that obscures the edge of the frustum.
  • There is no sky. When looking on an up angle, the camera will again see fog.
  • Many objects are not textured on their under-side, simply due to the fact that they were never intended to be looked at from below. These textures instead appear transparent.
Lots of fog doesn't look very good (unless it's the intended effect...but fog is never interesting). Also, breaking the illusion of the world by showing the transparent underside of an object should be avoided at all costs. When indoors, there is a lot more leeway with camera angles, especially if there is a real ceiling (like the Chamber of Stars).

Fog Distance
Every mood defines the neardist (near distance, or 1% fog) and fardist (far distance, or 100% fog) of the fog. The neardist is the distance, in meters, from the selected character to the beginning edge of the fog.

When entering a NIS, the neardist/fardist is calculated from the position of the camera, not the character, so the fog travels WITH the camera. This is nice in that you don't have to worry about moving the camera into fog, but you DO have to worry about showing the edge of the frustum. Since the frustum is still centered on the selected character(s), the fog will no longer hide its edge if you get near it with the camera.

You also have to worry about moving the camera too far above the terrain or away from a particular object, since those objects will eventually be covered by the fog. This is easily fixed, however, by using different moods with larger neardist/fardist values inside a NIS.

The Fog Bug: Finally, there is an unfortunate bug with NISs and the neardist/fardist calculation, drastically reducing their values. The net effect is that the fog seems MUCH closer during a NIS than during normal gameplay. This can be fixed by calling a different mood during the NIS that has larger neardist/fardist values, if necessary.

Out-of-Frustum Cameras
When the NIS begins, the frustum is centered on the selected player character. It is okay to have camera objects in the NIS chain placed outside this frustum, AS LONG AS THEY WILL BE INSIDE THE FRUSTUM AT THE TIME OF THEIR ACTIVATION. This can occur when the player characters are commanded to move during the NIS, effectively moving the frustum.

If a camera object is outside the frustum when the previous camera is finished, then the NIS will hang, exactly as if the previous camera did not have a "next_scid" value (i.e. it's not linked to anything). Essentially, this is true, since the next camera is unloaded and does not yet exist in the game world.

Linking Cameras Across Regions
As long as a camera object is in the frustum at the time of its activation, it doesn't matter what region it is in. However, since SE can only work on one region at a time, linking the two cameras that cross the region boundary is slightly more work:
  1. Open the second region in SE and copy the SCID of the second camera.
  2. Close the second region and open the first region, and paste the second camera's SCID into the first camera's "next_scid" field.
  3. Save Objects.
SE may give you an error every time you re-open the first region, since the first camera is linking to a SCID that doesn't exist in this region. This is a normal error, but doesn't actually point to something broken in this case.

Sound
During normal gameplay, the "virtual ear" that determines the volume/location of a particular sound effect is relative to the party members. Volume is determined by how close the effect is to the party, and location is determined by where the effect is playing relative the camera's view of the party (e.g. if the sound origin is to the left of the party, as viewed by the camera and NOT their facing, the sound plays in the left speaker).

During a NIS, the "virtual ear" becomes the camera itself. 99% of the time, this is the desired behavior, and what you see on screen and what you hear will seem to match. However, you might find yourself in a situation where you have a long shot of the action, and a loud noise is supposed to play from a sound emitter near the party. Instead of hearing the loud noise, you may barely hear it or not at all, since the camera is too far away from the sound origin. In these cases, you may have to move the sound emitter or create a new one at the position of the camera. This was done during the Gom NIS for the sound of the elevator hitting the ground (map_world/gom2).

Subtitles
<Back to Table of Contents>

Subtitles are the scrolling blocks of text that appear at the bottom of the screen during certain NISs, like the intro NIS at the very beginning of the game, or the Merik NIS.

Subtitles are Conversations
Subtitles are extremely easy to accomplish, because they are simply normal conversations--the exact same kind of conversations that appear when you talk to any NPC in the game. The only difference between a normal conversation and a conversation that produces subtitles in a NIS is that the NIS conversation has the "nis = true;" flag contained in its [text*] block. For an example of this, see the [conversation_narrator] block in "\world\maps\map_world\regions\fh_r1\conversations\conversations.gas"

What usually happens is that an Actor (some kind of NPC), with a NIS-only conversation assigned to him, receives a "we_req_talk_begin" message from a trigger instance. Sending this message to an actor with a conversation is the equivalent of clicking on a speaking NPC in the normal game. When the actor receives this message, the conversation .wav file begins playing and the conversation text begins scrolling at the bottom of the screen. The speed of the text scroll is determined by the "scroll_rate" value in the conversations.gas file.

For more information on conversations, see Siege U 208A: Non-Player Characters.

The Narrator
Late in the Dungeon Siege development process, NISs were added that were narrated by an off-screen voice. When it came time to add subtitles, we were in a bind, since there was no easy way to add subtitles to the NIS without an actor and conversation, and there was no time to implement a code solution.

So, the "Narrator" was born. The Narrator is an invisible actor who simply provides a source for a conversation, which prints text to the screen and plays the voice file. Since the character narrating Dungeon Siege is the King of Ehb, we added the "narrator" template underneath "base_king" in the npc_king.gas file.

When placing the Narrator in SE, you will see a gizmo aspect, not the King mesh. Otherwise, running a conversation with the Narrator is exactly the same as any other actor.

The Narrator was used at the end of each Chapter (except Ch9), and at the very beginning of the game (fh_r1). See fh_r1 for a typical example of how the Narrator is used.

The Party Wrangler
<Back to Table of Contents>

Solving the Death Problem
There is a major problem with NISs that we haven't talked about yet. The problem lies in the fact that NISs are just normal gameplay with a wide-screen view, no UI, and lack of player control. Because it's normal gameplay, the party members can still attack monsters, monsters can still attack party members, and the packmule can run all over the earth. In addition, the party members can DIE during a NIS, and the player is powerless to help them.

Obviously, you should never, ever put the player in a position where he has to watch his characters get hurt or die when he can't do anything about it.

To solve this problem, the Party Wrangler was created (/Game Objects/gizmos/commands/specialized/cmd_party_wrangler). To avoid combat and character death during NISs, the Party Wrangler does the following:
  • Changes the party's alignment from "Good" to "Neutral".
  • Issues a "stop" command to all party members and monsters.
Neutral creatures do not attack Good or Evil creatures, and Good/Evil creatures do not attack Neutrals. As soon as the party is changed to Neutral alignment, new monsters will not try to attack them. The "stop" command stops all party members and monsters from fighting, and because the party is Neutral, neither the party nor the monsters will re-engage the other.

To return the party's alignment to "Good", the Party Wrangler is sent a "we_req_deactivate" message, usually at the end of the NIS.

Nearly every NIS in Dungeon Siege uses the Party Wrangler to cease hostilities for the duration of the NIS.

Solving the Fidget Problem
A less immediate problem was the fact that party members, when not attacking, run a "fidget" animation. This sometimes gives them the effect of looking bored or aloof, which is usually not a problem EXCEPT when they are supposed to be listening to a very important speaker, like Merik or the King. When the King is making his grand speech, and the NIS camera is panning around his cell, it looks extremely odd to see Farmgirl put her hand on her hip, tilt her head and look off to the side, like she couldn't wait until he was done with his yammering.

Instead of creating a "standing still" animation for every character and explicitly running it during NISs that require listening, the Party Wrangler was given the ability to "freeze" characters in place. When done correctly, party members will be frozen looking directly forward, seemingly attentive to the speaker. The King NIS in "/map_world/cd_r1" is a good example of this.

Using the Party Wrangler
The Party Wrangler actually has three Properties, for three functions. Each function is activated as soon as the gizmo receives a "we_req_activate" message. Each function is immediately deactivated on reception of a "we_req_deactivate" message:
  • neutralize: (true/false) If true, changes the party to Neutral alignment.
  • freeze: (true/false) If true, freezes the party's current animation in place.
  • invisible: (true/false) If true, makes the party invisible. This was never used in Dungeon Siege.
The Party Wrangler is an extremely easy gizmo to use, but is also very dangerous. If you make the party "Neutral" and never change them back, the party will play the entire rest of the game unable to attack or be attacked. If they are never unfrozen, they will appear to "skate" everywhere in the world. This, obviously, breaks the game. :) Caution is advised.

In almost every NIS, the Party Wrangler is activated at the same time as the cmd_enter_nis gizmo. It is deactivated at the very end of the NIS, AFTER the final delay of the cmd_leave_nis gizmo (if the Party Wrangler receives its deactivate at the same time the cmd_leave_nis gizmo is activated, there is usually a 1-2 second "delay" while the camera finishes transitioning back to the pre-NIS point. The player still has no control during this delay).

There are many examples of the Party Wrangler being used in Dungeon Siege's NISs, but "/map_world/fh_r3" has a very simple implementation.

Other Stuff to Know
<Back to Table of Contents>

We've covered most of the information necessary in understanding NISs, but there's a couple items you should know that don't fit anywhere else.

Hitting ESC During the NIS
NISs can be "bypassed" by hitting the ESC key. When the player does this, the screen turns black for a second or two, and the game resumes after the NIS would have ended.

What really happens, from an engine perspective, is that the NIS keeps running, but is no longer rendered to the screen. The game pushes through the NIS as fast as it will go, and tells the game that .25 seconds elapsed each frame, even though it doesn't render any of them. When the NIS reaches the cmd_leave_nis gizmo, rendering resumes.

Cameras Delete Themselves
This was mentioned earlier, but just to be clear: cmd_camera_command gizmos automatically delete themselves after finishing. This makes it impossible to re-use camera objects again for the same or a different NIS.

Triggering from Camera Objects
As you should already know, a trigger instance can live inside any object, not just trigger_generic gizmos-including cameras. If you would like to trigger an Action from a cmd_camera_command gizmo, add a "receive_world_message" Condition that listens for the "we_camera_command_done" message. Every camera sends this message when it is finished, i.e. when its duration has elapsed.

Several Dungeon Siege NISs trigger events from camera objects to ensure correct timing. The intro NIS does this (/map_world/fh_r1 and fh_r1a), and so does the Gom NIS (/map_world/gom2).

MP Versions of NIS Areas
If your map will be played in both SP and MP modes, you'll need a MP version of what is a NIS in SP. What form this takes is up to you. Some NISs in Dungeon Siege (like the camera pan over Castle Ehb) has no MP "version", since it was unnecessary. None of the narrated NISs had a MP counterpart (game intro plus all Chapter ends). What WAS necessary was to include MP versions of NISs that forwarded the plot in some way, like Merik, the Goblin Inventor, and Gom. In MP, the events that take place attempt to mimic the events in SP as closely as possible, or at least contain the most important elements.

Creating a "MP version of events" is just making sure, through triggers, that what needs to happen will happen even through no NIS is taking place. You may need to have trigger gizmos that are only SP or only MP, or you may need trigger gizmos that exist in BOTH SP and MP but have trigger instances inside it that are either SP or MP.

For examples of this, see the following NISs (locations listed in Appendix below): Intro, Gloern, Merik, Goblin Inventor, Gresh, King, Swanny, Chamber of Stars, Gom.

Conclusion
<Back to Table of Contents>

We've covered everything we're going to cover in this lesson on Non-Interactive Sequences. You should have a basic understanding of building NISs and some of the related issues and concepts. By far, the best things you can do to further your understanding of the practical workings of NISs is to study each NIS in Dungeon Siege, and experiment on your own. Following this section is an Appendix that lists the location of all 19 NISs in the game, each of which has its unique properties. Even though you will encounter many gizmos and configurations you may not yet be familiar with, they will reveal themselves to you with investigation.

Remember that there are FAR more possibilities for NISs than have been explored so far in Dungeon Siege. While the 19 NISs in the game are certainly good to learn from, you might find much better ways to create your own. New technologies may emerge to make NIS-building even easier and more powerful. Never hesitate to share what you learn with other aspiring movie-makers!

The 19 Non-Interactive Sequences in Dungeon Siege
<Back to Table of Contents>

All NISs take place in the map_world map ("Kingdom of Ehb"). The regions where these NISs appear and the trigger SCID(s) involved are listed below. You can search for the SCID in the designated region to find the start of the NIS.
  1. Game Intro / Norick (fh_r1, fh_r1a): Narrator hints at backstory, Norick speaks and dies. Begins when player is detected by party_member_within_sphere trigger (s:0x01C0078D in fh_r1).

  2. Chapter 1 End (fh_r3, bt_r1): Narrator. Player enters Stonebridge. Begins when player enters a bounding box trigger (s:0x01E006BF in fh_r3).

  3. Gloern (path2dm): Krug kill dwarves, Gloern appears and fights. Begins when player enters a bounding box trigger (s:0x03300144).

  4. Chapter 2 End (nt_r1): Narrator. Player enters Glacern. Begins when player enters bounding box trigger (s:0x0270064D).

  5. Merik (lc_r5): Player frees Merik from the ice crystal. Begins when crystal (s:0x04B001B5) is struck AND after a bounding box (s:0x04B001B1) is entered.

  6. Chapter 3 End (lc_r5): Narrator. Player continues after freeing Merik. Begins when bounding box (s:0x04B001E9) is entered AND Merik NIS is finished (see previous).

  7. Goblin Inventor Start (gi_r3): Goblin Inventor appears, gives speech, and emerges with the Robosuit. Begins when player enters bounding box (s:0x05100037).

  8. Goblin Inventor End (gi_r3): Goblin Inventor suit is destroyed, and Merik's staff is revealed. Begins when Robosuit is killed (s:0x05100020) AND when player enters a node range (s:0x05100263).

  9. Chapter 4 End (gi_r3_b): Narrator. Player leaves the Goblin Inventor Hideout. Begins when player enters bounding box (s:0x0530010B).

  10. Gresh (cf_r1): Gresh taunts the party then summons a bunch of skeletons. Begins when player enters bounding box (s:0x00900566). This NIS is interesting because it changes the mood for the entire region, including the mood triggers at the region borders. Killing Gresh flips the mood triggers back to the original configuration.

  11. Chapter 5 End (cf_r1): Narrator. Player enters Fort Kroth. Begins when player enters bounding box (s:0x0090053E).

  12. Chapter 6 End (dc_r1): Narrator. Player leaves the Droog Village. Begins when player enters bounding box (s:0x00A00962) AND after player has talked to Nonataya (s:0x00A0000A).

  13. Castle Gates (ce_r2): Player arrives at Castle Ehb. Begins when player enters bounding box (s:0x0460086E).

  14. King/Chapter 7 End (cd_r1): King has long speech, drops Star Key. Begins when door (s:0x05B00188) to King's cell is opened AND player is in bounding box (s:0x05B00368).

  15. Swanny (gd_a_r1): Swanny the Mad Jailer is released, speaks, and attacks. Begins when button (s:0x01F00257) to lower Swanny's cage is pushed AND the cell door (s:0x01F002FA) is broken.

  16. Enter Chamber of Stars (gd_a_r1): Short introduction to the Chamber. Begins when player enters bounding box (s:0x01F0032E).

  17. Chamber Activation (gd_a_r1): Long sequence where the Chamber powers up and manifests treasure chests. Begins after all 4 statues (s:0x01F0037A, s:0x01F00363, s:0x01F00364, s:0x01F00365) are turned, and player is inside bounding box (s:0x01F00360).

  18. Chapter 8 End (gd_a_r1): Narrator. Player leaves the Chamber. Begins when player enters bounding box (s:0x01F0032F) AND Chamber Activation NIS has finished (see previous).

  19. Gom (gom2): Player descends elevator to meet Gom, Gom gives his speech and attacks. Begins when player enters bounding box (s:0x002001BD).

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_208a NPCs

Non-Player Characters

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

Definition: What is an NPC?

NPC Templates

Overview

Skills

Appearance

NPC Animations

Overview

Practical Exercise - Animation Configuration

FourCC Codes

Talking

Overview

Practical Exercise - Add a Conversation

Multiple Conversations for a Single NPC

Stores

Overview

Inventory Shopkeepers

Packmule Sellers

Conclusion

Introduction

NPCs, or Non-Player Characters, are a category that includes all townspeople, shopkeepers, and hirable type actors. The following tutorial will lead you through the template files and conventions that apply to these, how to mix and match skins and hairstyles to create your own NPCs, and some tips and tricks on adjusting behavior and animations.

What You Need For This Tutorial
  • Siege Editor
  • Dungeon Siege
  • Siege Editor Manual (siege_editor_manual.htm comes bundled with Siege Editor.)
  • A test map you can practice with. You can create your own in the Siege Editor; a large, flat square will do.
What This Tutorial Assumes You Have Already Learned

This tutorial assumes that you have already read the previous Siege University courses, especially Siege U: 201 - Templates, and are familiar with basic Siege Editor operations.

Definition: What is an NPC?
<Back to Table of Contents>

NPCs, or Non-Player Characters, are Actors who are not initially controllable by the Player(s). NPCs include Townspeople, Guards, Shopkeeps, and Hirables (which we generally refer to as potential party members, or PMs). Each type of NPC is defined in their templates through their base specializations and individual characteristics.

There are three major types of NPCs:
  1. Invincible, non monster-attackable townspeople (who range from 'filler' characters found in safe locations like towns to worldly traders camping in the most dangerous of locales)
  2. Invincible, monster-attackable guards (who typically wear armor, wield weapons, and are found stationed at strategic or military locations)
  3. Killable, monster-attackable potential party members who can be found anywhere at all.
There are a few template blocks which differentiate these and allow them to possess vastly different functionality.

Figure 1 - NPC types
 |  | monster- |
 | invincible | attackable |
type 1 | X |  | townspeople, filler characters
type 2 | X | X | guards
type 3 |  | X | potential party members (PMs)

NPC Templates
<Back to Table of Contents>

All NPC templates can be found under "Bits" in \world\contentdb\templates\regular\actors\good\ and in \world\contentdb\templates\regular\actors\good\NPC\. It would be a good idea to look through the files and see if you can recognize some of the NPCs you encountered in Dungeon Siege. Take a look at the example Townsperson template below:

Figure 2 - A basic NPC template
[t:template,n:mp_townfolk_female_01]
{
   category_name = "1W_townsperson";
   doc = "mp_townfolk_female_01";
   specializes = base_npc_fg_multiplayer;
   [aspect]
   {
      [textures]
      {
          0 = b_c_gah_fg_skin_22;
          1 = b_c_pos_a1_059;
      }
   }
   [common]
   {
      screen_name = "Ella Riverstarn";
   }
   [inventory]
   {
      custom_head = m_c_gah_fg_hlmt_head-02;
      [equipment]
      {
         es_feet = bo_bo_le_light;
      }
   }
}
(Taken from \world\contentdb\templates\regular\actors\good\npc\npc_fg_based.gas - mp_townfolk_01)

Overview
The major differences between NPC types are in what they specialize and the various template blocks which allow them to take on different capabilities. The following sections cover common NPC template components. You are encouraged to look through the specified template files while reading below.

Townspeople, Shopkeeps, and Guards
Townspeople, Shopkeeps, and Guards all specialize base_npc_*, where * = fb for farmboys, fg for farmgirls, and df for dwarves. The key block is the [mind] block containing combat rules that determine how monsters will react when the NPC is in their sight range.

Figure 3 - base_npc_* [mind] block
[mind]
{
   actor_may_attack = false;
   actor_may_be_attacked = false;
}
(Taken from \world\contentdb\templates\regular\actors\good\npc\npc_fg_based.gas - base_npc_fg)

To make an NPC into a Guard, the above false combat rules are changed to true. As a general rule, Shopkeeps should never attack or be attackable, since this can cause unexpected shopping problems. Shopkeeps who are allowed to die will drop all of their shop inventory on the ground, which considerably throws off the tuning and balancing of an area.

Hirables
Hirables, or potential party member NPCs specialize base_pm_*. The key blocks within these base specializations are the [store] and [water_effects] (splashes while walking through water) components:

Figure 4 - base_pm_* important components
[store]
{
   can_sell_self = true;
}
[water_effects]
{
}
(Taken from world\contentdb\templates\regular\actors\good\pm_fb_based.gas - base_pm_fb)

These are fairly self-explanatory: to hire an NPC, they must first be able to sell themselves, and [water_effects] allows them to generate splashes when moving through water. Hirables need to have the cost of hire defined as well. This is done by adding a gold value to their [aspect] block, with the gold amount being set to what they cost to hire:

Figure 5 - Gold value
[aspect]
{
   gold_value = 98880;
}
Of course, if the Hirable is free, then the gold_value should be set to zero.

Skills

NPCs who can attack have an additional component within their [actor] component: the [skills] block. This is what defines the skills and stats the actor possesses, which determines their offensive capabilities. Take a look at the [actor] block below for an example attackable NPC.

Figure 6 - An attack-enabled NPC's template
[actor]
{
   [skills]
   {
      dexterity = 8, 0, 0;
      intelligence = 4, 0, 0;
      strength = 15, 0, 0;
      melee = 15, 0, 0;
      ranged = 10, 0, 0;
      combat_magic = 2, 0, 0;
      nature_magic = 1, 0, 0;
   }
}
(Taken from world\contentdb\templates\regular\actors\good\NPC\npc_fb_based.gas)

There are three values possible for each skill field. They are:
Skill_name = experience level, experience points, starting level;
Usually you will want to tweak a Hirable NPC's skills so they are the appropriate level for the region difficulty they are found in. To change the NPC's abilities, simply modify the skills and stats to the values you prefer. If you do not want them to have any levels in a particular skill, delete the skill's entry from the block and it will automatically be set to base levels. Keep in mind that Strength, Intelligence, and Dexterity have a base starting level of 10, which means any additional skill you add is incremental above that. For example, by adding an 8 to the dexterity line above (dexterity = 8, 0, 0;), the NPC will have a total of 18 dexterity (10 (base level) + 8 (added in [skills] block) = 18 (new total dexterity level)).

I highly suggest that you look through components.gas (found in \world\contentdb\components\) to gain a better understanding of manipulating actor abilities. It is very easy to unintentionally create an NPC possessing skills that could never be obtained through normal play. If you want to maintain balance, try to adjust your NPCs' skills along the lines of the existing Hirables in pm_fb.gas, pm_fg.gas, and pm_df.gas.

Appearance

When creating a new NPC variant, there are four appearance options you can adjust: skin, clothing, mesh, and head shape. All of these are defined within the [aspect] or [inventory] blocks:

Figure 7 - Ulora's art asset information
[aspect]
   {
      model = m_c_gah_fg_pos_a1;
      [textures]
      {
         0 = b_c_gah_fg_skin_16;
         1 = b_c_pos_a1_026;
      }
   }
   [common] { screen_name="Ulora"; }
   [inventory]
   {
      custom_head = m_c_gah_fg_hlmt_ulora;
   }
(Taken from \world\contentdb\templates\regular\actors\good\pm_fg_based.gas - Ulora)

Skins
The NPC's textures are defined within the [textures] block. Texture 0 is always the NPC's skin, which refers to the facial features and skin color. All of the available skin textures can be found in \art\bitmaps\characters\ and are spread between the Good_A_Heroes, Good_A_NPC, and Good_B_NPC directories. All skin textures are of the format "b_c_*…*skin_##." The texture name may seem confusing at first, but it follows the content directory structure. For example, b_c_gah_fg translates to Bitmap_Character_GoodAHero_Farmgirl. Example skin texture: b_c_gah_fg_skin_16.

Clothing
The second texture, or texture 1, is the NPCs' clothing texture. Clothing texture variants are located in \art\bitmaps\characters\body_armor\. Each of the different clothing types requires a slightly different model. Any clothing texture you define within the template becomes the NPCs' 'underwear,' which will always be the base texture for that NPC, and will become visible if they die and drop all other equipment they might be wearing over it. Example clothing texture: b_c_pos_a1_026.

Meshes: Bodies
NPC Meshes, or models, define the shape of the body and are adjusted once you have chosen the skin and clothing. For Ulora above, she uses a farmgirl skin, and thus requires a farmgirl model. Since she also uses the a1 clothing style, her model is of the a1 style as well. Thus, her model is m_c_gah_fg_pos_a1, or Mesh_Character_GoodAHero_FarmGirl_POSture_Armor1. Clothing textures can usually be applied to any Hero-type NPC (Farmgirl, Farmboy, Skeleton, and Dwarf) so long as you match up the Model to the Armor type. Feel free to mix and match these, so long you keep the types the same! An example mesh is m_c_gah_fg_pos_a1.

Meshes: Heads
Farmgirl and Farmboy NPCs have a number of different hairstyles to choose from. While all of the following head meshes can be chosen for any farmboy or farmgirl character type, not all skins support all meshes. If one skin doesn't look right with a particular head style, either change the head or change the skin until you find a combination that you like. All custom head meshes are defined in the [inventory] block within the custom_head field.
Farmgirls
Long ponytail: default head
Poofy short hair: m_c_gah_fg_hlmt_head-02
Loose hair: m_c_gah_fg_hlmt_head-03
Very short hair: m_c_gah_fg_hlmt_head-04
Short ponytail: m_c_gah_fg_hlmt_head-05

Farmboys
Short hair or bald (depends on skin used): default head
Short hair with tails: m_c_gah_fb_hlmt_head-02
Long hair: m_c_gah_fb_hlmt_head-03
Mohawk: m_c_gah_fb_hlmt_head-04
Pony tail: m_c_gah_fb_hlmt_head-05

Note: Dwarves and Skeletons do not have any additional head types.
NPC Animations
<Back to Table of Contents>

Overview

Configuring NPCs to perform movements other than basic standing around is a frequently requested feature, so I will go into detail on how to set this up. Before reading further, take a minute to load SE and open your test map, if you haven't already.

Practical Exercise - Animation Configuration

The "cmd_animation_command" Gizmo
To make NPCs display any animations other than their defaults, you need to use a Command Gizmo: cmd_animation_command. This is a Gizmo that allows you to define animation codes to change an NPC's current animations. These can be used either to change the NPC's default animation in that instance (for example, Tarish sitting on a log) or to play a particular animation at a specific time (for example, Rusk rolling out of bed when the party approaches). Bring up your test map and expand the Game Objects tree to \gizmos\commands\specialized\. Select cmd_animation_command and place it in the map.

Setting the Scene
First, you need an Actor. Farmboy-type actors have the most variety. Expand the Game Objects tree to \actors\good_characters\townspeople\regular\ and place mp_townfolk_m_02 in the test map. We don't want the NPC sitting on air, so expand the Game Objects tree to \furnishings\indoor and place a chair_wood_ornate behind the NPC. You'll probably need to make some fine-tuning adjustments to the chair's placement, but first you want to "wire" up the cmd_animation_command and do a quick test run in game. (Note: if the object list names aren't what you expect, make sure your content display options are set to development name, changeable in the Preferences window)

Wiring
Now you're ready to "wire" the animation: i.e., fill in the information that allows the gizmo to communicate with the NPC. Copy the NPC's SCID by selecting the NPC and either right-clicking and choosing "Copy scid to clipboard" or hitting "g" on the keyboard. Next, open the cmd_animation_command's template properties by selecting it and either right-clicking and choosing Properties, or hitting "v" on the keyboard. Paste the NPCs SCID into the client_scid field within the [cmd_animation_command] block. There are a number of other fields that can be adjusted as well, but for now let's keep it simple. The "Animation" field is where you put in the FourCC animation code that defines the animation you want to play. Since we want to make the NPC sit, type "sit2" into the Animation field. We want the animation to begin as soon as the NPC is loaded in the world, so change initially_active to True.

Test It
Now, save the region (make sure Objects are checked). Next, save the Map as a .dsmap so you can run it in the game. Close the editor, launch Dungeon Siege, and take a look at the NPC in the game. If he does not appear to be sitting directly on the chair, go back to SE and move the chair to a better position. If the NPC seems to be clipping through the bottom of the chair too much, change the chair's size by adjusting the scale_multiplier, found in the chair's template properties at the bottom of the [aspect] block.
TIP: You can save a lot of time testing maps by launching the game directly to the map and region you're interested in. To do this, create a shortcut to Dungeon Siege and open its properties. In the Target field, add a space after the path information and add map=mapname to go directly to the map, and teleport=n:nodeguid to jump to any node of your choice. The Target should now look something like: "C:\Program Files\Microsoft Games\Dungeon Siege\Dungeon Siege.exe" map=test_map teleport=n:0x98a4b01c. By double clicking the altered shortcut to launch the game, you'll bypass the front interface. Don't forget to put the Dungeon Siege cd in the drive first!
FourCC Codes

The exact codes available for each hero NPC type are located in their base specialization template in the chore_misc block. The following are the most useful FourCC codes:
Farmboys
sit1: sitting on a chair with arms resting on a table
sit2: sitting on a chair with hands on knees
sit3: variation of sit2
bed4: sleeping on a bed
bed5: sleeping on a bed variation
bed6: sleeping on a hammock
bed7: sleeping on a hammock variation
hoe1: hoeing animation
talk: talking/social animation

Farmgirls
sit1: sitting on a chair
talk: talking/social animation
hoe1: hoeing animation

Skeletons
talk: talking/social animation
hoe1: hoeing animation

Note: Be aware that the sitting and sleeping animations are not intended to be used if the NPCs are wearing a robe or dress since those meshes don't actually have legs or anything else underneath.
TIP: By default, when viewing an object's Template Properties in the Siege Editor, fields are displayed in hexadecimal format. When viewing cmd_animation_command's template properties, you will want to check the FourCC box located in the upper right portion of the Object Properties window. This allows FourCC codes to display in a legible format.
Talking
<Back to Table of Contents>

Overview

While not all NPCs are required to talk, most of the time it is desirable for them to do so. Shopkeeps and Hirables must be able to talk in order for them to do their jobs - selling goods or the use of their sword arm, for example. Each of the NPC types shares two important components that give them the ability to talk: the [conversation] component, and the [mind] block with a talk job. All talking NPCs must have the empty [conversation] component included either in their leaf template, or referenced in their specialization:

Figure 8 - The conversation component
[conversation]
{
}
All talking NPCs must also have a talk skrit AI job defined in their [mind] block as well, either within their template or regional instance. NPCs with simple, single conversations can use the basic talk skrit:

Figure 9 - The [mind] block with the basic talk skrit
[mind]
{
   jat_talk = world\ai\jobs\common\job_talk.skrit;
}
If you want to have an NPC with multiple conversations based on various conditions, you must create a custom talk job for them that specifies what the conditions are and what conversation they should use. This is covered in Siege U: 208B - Talk Skrits.

Practical Exercise - Add a Conversation

Adding simple conversations to NPCs is fast and easy, thanks to Siege Editor's Conversation Editor. In the following section, we'll create a two-line conversation for the sitting Farmboy. Open the Siege Editor and load your test map from above.

Adding Conversations via Siege Editor
To add a conversation to an Actor, you must select an actor who has the [conversation] block within their template. Most of the "mp_*" type NPCs found in \actors\good_character\townspeople\regular\ are conversation-ready. If you try to add a conversation to a non-conversation-ready NPC, SE will generate a warning.

Select the mp_townfolk_m_02 actor you previously placed. With the NPC selected, either right click and select Region Conversation Manager or choose Region Conversation Manager from the Tools Menu. This brings up the Region Conversation Manager window, which allows you to create, edit, and remove conversations.

To add a Conversation to the NPC, click the New button and either name the conversation, or select an existing one. When naming conversations, they must be unique within that region only. Create a new conversation called "conversation_test" and click OK. Now it's time to edit the conversation's text. Select conversation_test from the Existing Conversations list and click Edit. You can now create or edit the text lines that make up the conversation, as well as add Goodbye Samples that will play when you close the NPCs dialogue window in game.

Adding Text
Now that the conversation name has been defined, it's time to add the actual text dialogue that will appear when you talk to the NPC in game. To add a line to conversation_test, click the New button. <No text added yet> will appear under the Text heading. Click on this string to select it and click the Edit button. The Conversation Text Editor will open and you can now add dialogue. Type "It's great to sit down." into the box. For the most basic conversation type, you can stop here; click OK to get past the next windows until you are back to looking at the scene in the editor again.

Add a Talk Skrit
NPCs must have a talk skrit defined in order to open a dialogue box in the game. Select the NPC and open his Template Properties. Scroll two-thirds of the way down the [mind] block until you find the jat_talk field. Paste in the basic talk skrit path: "world\ai\jobs\common\job_talk.skrit".

Take a minute to go through the save processes and test the NPC in the game. When ready, continue reading for more advanced text settings and adding a second text block.

Text Settings
When creating or editing text, there are a number of settings you can choose to adjust.

Text Scroll Rate: This number determines how rapidly the text scrolls within the dialogue window if it takes up more than one window. 1.0 is the fastest, 10.0 is the slowest.

Order: Each conversation can contain multiple dialogue blocks, or lines. Each line will be played in its own dialogue window depending in the order you specify. You are not required to give a text block an order - if the text block does not have an order, it will be played more than once. If there are multiple text blocks within a conversation and none of them have an order, the blocks will be played randomly every time you initiate dialogue with an NPC. When assigning orders, the first line you want to be read first is assigned order 0. The second is order 1, third 2, and so on. Note: if every block within a conversation has an order, no text will appear at all if you talk to the NPC again after the last line has played.

Audio Sample: Drop down to choose the audio dialogue that will play when you talk to the NPC.

Button Choice: These are non-custom buttons directly supported by the UI. Important note: you may only have one of these per text block. They are:
Buy_packmule: Adds a button for purchasing a packmule
More: Adds a button for advancing to the next text block in the conversation
Potential_member: Adds a button for hiring an NPC to join your party
Shop: Adds a button that opens up the shop
Activate Quests: This allows you to activate a quest when the line is played. Quest-related topics will be covered in Siege U: 209 - Quests.

Complete Quests: This allows you to complete a quest when the line is played. Quest-related topics will be covered in Siege U: 209 - Quests.

Custom Buttons: There are two custom buttons you can add to the in game dialogue window. Button 1 appears on the far left side of the window, under the text area, while Button 2 appears just to the right of Button 1. These can be given any text string that will fit on the button, allowing you to create dialogue trees. However, you must create a custom talk skrit to use the buttons since there's no other way to define what conversation should be played when someone clicks on them. The Value field allows you to assign a unique ID to each button, which is then referenced in the custom talk skrit. Creating custom talk skrits that give the buttons in game functionality will be covered in a future Siege U course.

Continuing where we left off above, let's finish configuring conversation_test's text. For the text block ("It's great to sit down.") change the Text Scroll Rate to 3 to make the line scroll faster. Set the Order to 0 so it is the first conversation played. From the Button Choice menu, select More. Click OK.

Now let's add the second line to the conversation. Click the New button to create a new line, select it, and click Edit. Type "I'd do anything for a tasty piece of jerky." Again, change the Text Scroll Rate to 3. Since we want this line to play anytime someone talks to this NPC after the first line has been spoken, we want to leave the Order value blank. Click OK to close the Text window. Click OK to get past the next windows until you are back to looking at the scene in the editor again. Go through the Save processes again, close the editor, and try talking to the NPC in the game!

Multiple Conversations for a Single NPC

Setting up multiple conversations or custom buttons is more difficult and requires custom talk skrits. Multiple conversations for a single NPC are needed when you want to have an NPC respond differently according to changing conditions, or if you want the NPC to respond to you on a more specific level. For example, say a townsperson NPC is looking for a particular fabled sword. If your party does not have the sword, you expect the NPC will merely tell you that she is always on the lookout for such an item. However, if someone in your party already possesses the sword, the NPC should respond very differently! The key to creating 'smart' talking behavior in NPCs is by writing a custom talk skrit for that NPC.

Talk Skrits
Writing a custom talk skrit isn't as difficult as it sounds. A beginner's guide to writing custom talk skrits is covered in Siege U: 208B - Talk Skrits.

Stores
<Back to Table of Contents>

Overview

There are two kinds of Storekeepers: those who sell inventory (such as weapons, armor, spells, potions, etc.) and those who sell Packmules. Inventory storekeepers are configured to be tuned to the difficulty of the area they are placed in by utilizing randomized content, which is available through the Parameterized Content, or Pcontent system. This tutorial will cover the basic features of this system and will allow you to customize a shopkeeper.

Inventory Shopkeepers

Shopkeepers are configured in both their templates and conversations.

Templates
There are three main components that define a Shopkeeper: the [actor] block (containing their portrait information), the [store] block (containing item markup information), and the [inventory] block (which defines what the NPC sells).

Shopkeepers have a large interface window through which their inventory is available. The upper right side also contains a portrait of their face. The facial textures are in Photoshop format and can be found in \Art\Bitmaps\Gui\In_game\Icons\. They are prefixed by "b_gui_ig_i_ic_c_*"

Figure 10 - The [actor] block
[actor] {portrait_icon = b_gui_ig_i_ic_c_foulton;}
(Taken from \World\Contentdb\Templates\Regular\Actors\Good\NPC\npc_blacksmith.gas - blacksmith_zabar_treseck)

The [store] block tells the game that the NPC is a storekeeper and also defines how much the store's prices are raised over the base item values. The item markup value plays a role in game tuning. It is highly recommended to leave this at 2.

Figure 11 - The [store] block
[store]
{
   item_markup = 2;
}
(Taken from \World\Contentdb\Templates\Regular\Actors\Good\NPC\npc_blacksmith.gas - blacksmith_zabar_treseck)

If the NPC sells potions, the [item_restock] block is added within the [store] block. [item_restock] tells the game that the contents should fully restock when the tab is clicked. Generally, potions and basic spells are listed within [item_restock], but any sellable item template can be called here. The format is templatename = number to restock.

Figure 12 - [item_restock]
[store]
{
   item_markup = 2;
   [item_restock]
   {
      potion_health_medium = 7;
      potion_mana_medium = 5;
      scroll_resurrect = 10;
      spell_fireshot = 4;
   }
}
(Taken from \World\Contentdb\Templates\Regular\Actors\Good\NPC\npc_fg_based.gas)

The [inventory] block contains all of the actual sellable content the shopkeeper has to offer. This is contained within the [store_pcontent] component, and is sorted into blocks for each different kind of inventory. For an example, take a look at the following blacksmith store inventory:

Figure 13 - The [inventory] block
[inventory]
{
   [store_pcontent]
   {
      full_ratio = 1;
      [armor]
      {
         [all*]
         {
            il_main = #body/23-40;
            max = 7;
            min = 7;
         }
      }
      [shields]
      {
         [all*]
         {
            il_main = #shield/3-40;
            max = 10;
            min = 10;
         }
      }
      [weapons]
      {
         [all*]
         {
            il_main = #melee/14-18;
            max = 10;
            min = 10;
         }
      }
   }
}
(Taken from \World\Contentdb\Templates\Regular\Actors\Good\NPC\npc_blacksmith.gas)

Before configuring a storekeeper's inventory, it is important to understand the basics of the Pcontent system.

Pcontent
The [pcontent] block is a set of "parameterized content" that is randomly generated, based on parameters that you supply. With the [store_pcontent] block - a subset of the Pcontent system - the game engine adds Pcontent to the shopkeeper's inventory (when that actor is loaded) by selecting from groups specified in the [store_pcontent] block. When loading an actor with a [store_pcontent] block, the game engine will either select all of the group's entries as candidates for inclusion or will choose one from the list.
  • If an "all" group is selected, then all of the items and groups within its block are available as candidates for adding to inventory. If a subgroup has a "chance" defined, then that will be evaluated to decide whether or not to include the group.

  • If a "oneof" group is selected, then one of its items or groups is selected based on probability. By default, all have the same chance of being selected (for example, a group with 3 items would give each item a 33% chance of selection), but by assigning "chance" values to the subgroups, you can change this. The total chance for a group must be <= 1.0. Note that a total chance of < 1.0 means that it's possible that none of the items will be selected and the entire group will be ignored.

  • When a group is selected, then min/max are used to specify how many of the items will appear, with a range from min to max chosen randomly.
Figure 14 - The [store_pcontent] block
[store_pcontent]
{
   full_ratio = 0;
   [tabname]
   {
      [all*|oneof*]
      {
         il_main = template_name;
                      //OR
         il_main = #pcontent_name/power_min-power_max;
         min = 20;
         max = 20;
         chance = 0.2;
      }
   }
}
(Taken from \World\Contentdb\Components\components.gas)

Each block can contain the full_ratio field, which is a 0.0 - 1.0 float that determines how full the tab may be. It defaults to the 1.0 or the setting in the store_pcontent block. Upon load, the store will repeatedly query its pcontent system, adding items until it reaches the full_ratio. Setting full_ratio to 0 will disable this repeated-add feature.

The [tabname] block directly correlates to the in-game store interface tabs and determine which tab the inventory appears on. They are:
[armor], for body armor, helms, boots, and gauntlets
[weapons], for swords, staffs, maces, etc
[shields], for shields
[magic], for spells
[misc], for spellbooks and jewelry
Now that you understand a little more on how to adjust pcontent, a word of caution: store inventory is yet another feature that greatly affects game balance, and it might be a good idea to copy and paste existing store inventory blocks from appropriately-tuned pre-existing NPCs until you get the hang of balancing your own store content.

Conversation
A shopkeeper without a conversation can't sell anything at all, so first thing you'll want to do after creating your new shopkeeper's template is to load up the Siege Editor. You already know how to place the NPC and create new conversations and text blocks, so do this part next. Once the conversation is ready, edit each text block within the conversation to choose Shop from the Button Choice menu. Voila, that's all you need to do!

Packmule Sellers

Creating Storekeepers who sell packmules is relatively simple. The process involves a mix of templating the new storekeeper and placing a Party gizmo in SE to link the mules and storekeeper together.

Templates
Packmule Sellers only require one additional component above the base NPC setup: the [store] block. This is the same component found in Inventory Shopkeepers' templates, and just as straightforward to setup. For an example, look at Fretch's template below:

Figure 15 - Fretch
[t:template,n:fretch]
{
   category_name = "1w_character";
   doc = "fretch";
   specializes = base_npb_fb;
   [aspect]
   {
      [textures]
      {
         0 = b_c_gah_fb_skin_19;
         1 = b_c_pos_a1_060;
      }
   }
   [common]
   {
      screen_name = "Fretch";
   }
   [conversation]
   {
   }
   [store]
   {
      item_markup = 2;
   }
}
(Taken from \World\contentdb\templates\regular\actors\good\npc\npc_fb_based.gas - Fretch)

Party Gizmo and Editor
Mule sellers are unique in that they are an NPC who sells NPC mules. Once the sale has taken place, the NPC mule is automatically transformed into the party-member Packmule, complete with a new model and texture to give them saddlebags. While the behind-the-scenes changeover isn't something that requires modifications to occur, the shopkeeper and mules are linked together via the Party gizmo and the Party Editor.

Tutorial
The Party gizmo is an editor gizmo in the shape of a person. It can be found under gizmos\commands\specialized\party_npc. With your test map open, take a moment to place one in your region. Note: until recently, the party gizmo was not listed in the SE object lists. If you are using an older Update version than 1.09.2, you may need to perform a content search for party_humanoid by choosing the Content Search function found in the Object menu.

Now, you need a merchant seller and mules. First, place Fretch in the test map next to the Party gizmo. He can be found in \actors\good_characters\guards_and_pms\regular\fretch. Next, place two mules for him to sell, found in \actors\ambients\regular\mule. Base mules have a default gold value of 200, however this can be changed depending on the difficulty of the area. To change the base sale price, adjust the gold_value field within the [aspect] block. You may also want to adjust the screen names of the mules to better identify them.

Open the Object Menu and select the Party Editor. This will open the Party Editor window, where you can assign Fretch and his mules to the Party gizmo. Select the scid of the Party gizmo from the Party Name menu. From the Available Actors listing, select Fretch and click the Add button. Finish assembling the party by adding both of the mules in the same manner. Click the OK button to close the window.

Finally, add a conversation to Fretch, and make sure all text blocks contain the buy_packmule Button Choice. Don't forget to add a talk job ("world\ai\jobs\common\job_talk.skrit" is fine).

Now that setup is complete, Save the region (make sure Objects is checked), save the map as a .dsmap, and test it out in the game.

Conclusion
<Back to Table of Contents>

You should now have a better understanding in what makes NPCs tick, from templating them, customizing their appearance and animations, to making them talk and offer both items and mules for sale. Keep this information in mind while reading through Siege U: 208B - Talk Skrits and Siege U: 209 - Quests.

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_208b Talk Skrits

Talk Skrits

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

Overview

What A Talk Skrit Looks Like

Methods For Choosing Conversations

Boolean Values

Quest States

Inventory Items

Custom Buttons

Giving And Taking Items From The Player

Advanced Examples

Conclusion

Appendix A: Tips

Introduction

This is intended to be a short guide that will allow the novice "skritter" to get into existing talk skrits and make some modest modifications. This will allow you to set up NPCs to use the different conversations created in Siege U: 208A - Non-Player Characters. You can choose to play different conversations based on quest states, Boolean variable states, inventory items, and more.

What You Need For This Tutorial
  • Dungeon Siege
  • Siege Editor
  • Siege Editor Manual (siege_editor_manual.htm comes bundled with Siege Editor.)
  • A passing familiarity with programming logic, including: what variables are, and how if/else blocks work.
What This Tutorial Assumes You Have Already Learned
  • Siege U: 208A - Non-Player Characters: This will teach you how to create conversations for Non-Player Characters (NPCs)

Overview - Why Do Talk Skrits Exist?
<Back to Table of Contents>

Talk skrits tell an NPC what to do when the player talks to them. The skrit tells the NPC how to animate, which conversation to choose, and when to start talking. Since it is a skrit, we can also do more advanced things like delete items, give items to the player, change Boolean values, etc.

What A Talk Skrit Looks Like

This is what talk skrits look like, in brief. Don't let this scare you too much; you won't have to worry about (or change) most of what you find in a talk skrit.

/*
<opening comments; usually giving the purpose of the skrit>
*/

<variable definitions>

startup state STARTUP$
{
}

//////////////////////////////////////////////////////////////
//	explanation of event xxx

event xxx$(...)
{
   <this code will be executed when the event happens>
}

//////////////////////////////////////////////////////////////
//	Do more stuff
state xxx$
{
   transition
   {
      ...
   }

   event OnEnterState$
   {
      ...
   }
}

//////////////////////////////////////////////////////////////
//	Here's the good stuff
state Talk$
{
   transition
   {
      -> Exiting$:	OnWorldMessage( WE_REQ_TALK_END ) =
      {
         <this tells the skrit what to do when the player
         is done talking to the NPC>
      }
   }

   trigger OnWorldMessage$( WE_ANIM_DONE )
   {
      <this block is used to keep the NPC animating>
   }

   event OnEnterState$
   {
      <here is all of the logic for choosing the conversation>
      TryAnim$('talk');
   }
}

//////////////////////////////////////////////////////////////
//	finish up
state Exiting$
{
...
}
As you can see, talk skrits are divided into different states. State Talk$ is probably the only thing that you will want to change. In fact, all changes will occur inside event OnEnterState$. This is where you can run logical checks on Boolean variables, quest states, inventory items, etc. and choose a conversation based on the results.

Here is an example of a state Talk$ block from job_talk_ibsen.skrit. This is a simple example, where the NPC has two possible conversations. If the Boolean value "talked_to_overseer" is true, the first conversation is chosen. If it is not true, then the second conversation is chosen, and the Boolean is set to true, ensuring that the first conversation will be chosen in the future.
//////////////////////////////////////////////////////////////////////////////
//	Here's the good stuff
state Talk$
{
   transition
   {
      -> Exiting$:	OnWorldMessage( WE_REQ_TALK_END ) =
      {
         if( m_Go$.Mind.ActorAutoFidgets )
         {
            StartFidgetIfRequired$();
         }
      }
   }

   trigger OnWorldMessage$( WE_ANIM_DONE )
   {
      TryAnim$('talk');
   }

   event OnEnterState$
   {
// This is the part that we really care about for our example:
      if ( GameAuditor.GetDb.GetBool( "talked_to_overseer" ) )
      {
         m_Go$.GetConversation.RSSelectConversation( "conversation_ibsen_2" );
      }
      else
      {
         GameAuditor.GetDb.SetBool( "talked_to_overseer", true );
         m_Go$.GetConversation.RSSelectConversation( "conversation_ibsen" );
      }
      m_Go$.GetConversation.RSActivateDialogue();
      TryAnim$('talk');
   }
}

Methods For Choosing Conversations
<Back to Table of Contents>

The primary goal (that we are concerned with) inside of the talk skrit is to choose the appropriate conversation. This is done via two functions:
m_Go$.GetConversation.RSSelectConversation( "conversation_name" );
m_Go$.GetConversation.RSActivateDialogue();
RSSelectConversation chooses which conversation the NPC will use. Calling RSSelectConversation without supplying a conversation name will cause it to default to the first conversation specified in the [conversation] block in the actor.gas file for the NPC. When RSActivateDialogue is called, the NPC will start talking. The most common criteria for deciding what the NPC should say are Boolean variable values, quest states, and inventory items.

Boolean Values

Boolean variables can be named anything, and are useful for keeping track of whether or not the party has done certain actions. Boolean variables can be set via a "set bool" trigger, and are often wired with other triggers near quest-specific areas in the game. Boolean values can also be accessed with these functions:
GameAuditor.GetDb.GetBool( "variable_name" );
GameAuditor.GetDb.SetBool( "variable_name", true/false );
Running GetBool on an uninitialized boolean returns a "false" value. Otherwise it returns the value of the variable.

Here is an example from job_talk_sanctuary.skrit:

else if ( GameAuditor.GetDb.GetBool( "turn_world_red" ))
{
   m_Go$.GetConversation.RSSelectConversation( "conversation_keeper_red" );
}
Quest States

Perhaps the most common use of talk skrits is to cause NPCs to say different things at different stages of a quest. See Siege U: 209 - Quests for an explanation of quests and their stages.
Victory.GetQuestOrder( "quest_name" )
Victory.IsQuestActive( "quest_name" )
Victory.IsQuestCompleted( "quest_name" )
GetQuestOrder will return the stage of the quest as an integer. IsQuestActive and IsQuestCompleted will return true or false. This is an example from job_talk_sanctuary_keeper.skrit:
else if ( Victory.IsQuestCompleted( "quest_flooded_sanctuary" ))
{
   m_Go$.GetConversation.RSSelectConversation( "conversation_keeper_complete" );
}
This is a fictional example for GetQuestOrder:
else if (Victory.GetQuestOrder( "bobs_neat_quest" ) == 2)
{
   m_Go$.GetConversation.RSSelectConversation( "conversation_bob_quest_part_2" );
}
Inventory Items

It can be very useful to check and see if the party has a particular item in their inventory. Here is the function:
UIPartyManager.DoesActivePartyHaveTemplate( "item_template_name", m_Target$ )
This function returns the GOID of the object, unless the object is not found, in which case it returns a value equal to Goid.InValidGoid. The GOID is useful if you want to directly manipulate the object. If you only care whether or not the party has the item, you can compare the result of the function to Goid.InValidGoid; if the party doesn't have the item, the comparison will return true.

Here is an example that incorporates checking for an inventory item along with a bit of variable manipulation (example taken from job_talk_ardun.skrit):
int books$ = 0;

if ( Goid.InValidGoid !=
      UIPartyManager.DoesActivePartyHaveTemplate("book_glb_lore_fedwyrr_1", m_Target$))
{
   books$ += 1;
}

if ( Goid.InValidGoid !=
      UIPartyManager.DoesActivePartyHaveTemplate("book_glb_lore_fedwyrr_2", m_Target$))
{
   books$ += 1;
}
In this example, the comparison is done with the "not equal" ( != ) operator, so that the result will be true if the party does have the item. (We are checking to see if the result is valid.) This particular NPC is used for a quest where the player has to collect books. This skrit counts how many of the relevant books the player has.

Custom Buttons
<Back to Table of Contents>

If you have a custom button available for the player to press (refer to Siege U: 208A), it will have a value associated with it. This value is the name of a Boolean variable. (Since this is true, the value associated with each button should be unique within your map. Otherwise you could have one NPC's button affect another NPC's conversations. The way to check and see if they pressed the button is to test that Boolean variable. If they pressed the button, it will have a "true" value. You will have to manually set the value back to "false" after testing the variable. If you don't do this, then every subsequent conversation with the NPC will act as though the player had just pressed the button.

Here is an example from job_talk_guard_captain_el.skrit of a check to see if a button has been pressed, and then setting the bool back to false afterwards. The crazy name for the Boolean variable (d_0x03200da1) is a naming convention that we came up with internally - when an NPC has a button for "directions", we would use "d_" and the SCID of the actor for the Boolean value, thus keeping it unique within the map. This naming convention is certainly not mandatory, but since SCIDs are unique within a map, this made it easy for us to keep the Boolean values unique.

if( GameAuditor.GetDb.GetBool( "d_0x03200da1" ) )
{
   m_Go$.GetConversation.RSSelectConversation( "zconversation_directions" );
   GameAuditor.GetDb.SetBool( "d_0x03200da1", false );
}

Giving And Taking Items From The Player
<Back to Table of Contents>

It is possible to drop an item for the player using skrit. This is useful as a reward for finishing a quest. Explaining the skrit involved is beyond the scope of this document, but you can still cut and paste using an example, and inserting the template name of the item that you wish to drop for the player. The following is an example of dropping an item near the player. Just replace "template_name_of_object" with an actual template name.
SiegePos SpawnPos$;
GoCloneReq cloneReq$ = MakeGoCloneReq( "template_name_of_object" );
SpawnPos$ = m_Target$.go.placement.position;
cloneReq$.StartingPos	= SpawnPos$;
If( m_Target$.Go.Hasmind )
{
   if(AIQuery.FindSpotRelativeToSource(m_Target$.Go, .25, .5, 2.0, m_Target$.Go.Mind.TempPos1))
   {
      cloneReq$.StartingPos = m_Target$.Go.Mind.TempPos1;
   }
}
cloneReq$.SnapToTerrain = true;
GoDb.SCloneGo( cloneReq$ );
The following is an example of dropping gold. Just replace the "9999" at the end with the amount that you actually want to drop.
SiegePos SpawnPos$;
GoCloneReq cloneReq$ = MakeGoCloneReq( "gold" );
SpawnPos$ = m_Target$.go.placement.position;
cloneReq$.StartingPos	= SpawnPos$;
If( m_Target$.Go.Hasmind )
{
   if(AIQuery.FindSpotRelativeToSource(m_Target$.Go, 1, 1.5, 2.0, m_Target$.Go.Mind.TempPos1))
   {
      cloneReq$.StartingPos = m_Target$.Go.Mind.TempPos1;
   }
}
cloneReq$.SnapToTerrain = true;
Goid gold$ = GoDb.SCloneGo( cloneReq$ );
gold$.Go.Aspect.SetGoldValue(9999);
It is also possible to delete an item out of the player's inventory. This can be done when an NPC mentions that they are "taking" an item from the player. The following is an example of deleting something out of the player's inventory. Just replace "template_name_of_object" with an actual template name.
goid temp_object$ =
UIPartyManager.DoesActivePartyHaveTemplate("template_name_of_object", m_Target$);
temp_object$.Go.Parent.Mind.SDoJob(MakeJobReq(JAT_DROP, JQ_ACTION, QP_FRONT, AO_REFLEX, temp_object$));
PostWorldMessage(WE_REQ_DELETE, temp_object$, temp_object$, .1);

Advanced Examples
<Back to Table of Contents>

Here is an example (taken from job_talk_guard_captain_cr.skrit):
else if
(
   (Victory.GetQuestOrder( "quest_zabar_hovart" ) == 4 )
   &&
   (Goid.InValidGoid !=
   UIPartyManager.DoesActivePartyHaveTemplate("stone_glb_gavel_01", m_Target$))
)
For the first check to return true, the player would have to be on step four of the quest named "quest_zabar_hovart". For the second check to be true, the party would have to have the item with template name "stone_glb_gavel_01" in their inventory. The logical "and" operator ( && ) is used here to make sure that both comparisons are true before moving on.

This example is from job_talk_sanctuary.skrit:
if (
   ( Victory.IsQuestCompleted( "quest_flooded_sanctuary" )) &&
   ( !GameAuditor.GetDb.GetBool( "sanctuary_keeper_gave_reward" ))
)
This will return true only when the quest is completed, and the reward has not been given. The first line is fairly simple; it will return true when the quest is completed. However, the second line has a "!" in front of it, which is the logical "not" in skrit (just like C). Thus, the second comparison would return true if the bool is not true. The logical "and" is used to tie these comparisons together, just as in the example above.

This final example is a series of checks found in job_talk_kelti.skrit. This is one of the longer talk skrits, and serves as an example of what kind of checking is possible within a skrit. For completeness, the entire "event OnEnterState" block is included here. This example will have comments throughout, explaining what is going on:
event OnEnterState$
{
   #only( game )
   [[
      if( GameAuditor.GetDb.GetBool( "d_0x032007b5" ) )
      {
         m_Go$.GetConversation.RSSelectConversation( "zconversation_directions" );
         GameAuditor.GetDb.SetBool( "d_0x032007b5", false );
      }
This is checking a Boolean variable, then if it's true, setting the bool back to false after choosing the conversation.
      else if ( GameAuditor.GetDb.GetBool( "turn_world_red" ) ) {
         m_Go$.GetConversation.RSSelectConversation( "conversation_kelti_red" );
      }
Again, checking a Boolean variable:
      else if ( Victory.IsQuestCompleted( "quest_townstones" ))
      {
         m_Go$.GetConversation.RSSelectConversation( "conversation_hub_complete" );
      }
This checks to see if a quest is done:
      else if
         ((!Victory.IsQuestActive("quest_elddim"))
         &&
         (!Victory.IsQuestActive("quest_townstones"))
         &&
         (Goid.InValidGoid ==
         UIPartyManager.DoesActivePartyHaveTemplate("stone_glb_elddim_01", m_Target$)))
      {
         m_Go$.GetConversation.RSSelectConversation( "conversation_kelti" );
      }
This gets a bit more complicated. It is checking to see if both quests are not active (note the "!" operators). It is also checking to see if the party does not have a specific item (it is comparing against Goid.InValidGoid).
      else if
         ((Victory.IsQuestActive("quest_elddim"))
         &&
         (Goid.InValidGoid ==
         UIPartyManager.DoesActivePartyHaveTemplate("stone_glb_elddim_01", m_Target$)))
      {
         m_Go$.GetConversation.RSSelectConversation( "conversation_kelti_local" );
      }
This is checking to see if the quest is active, and the party does not have a specific item:
   else if ( ( Victory.IsQuestActive( "quest_elddim" )) &&
      (Goid.InValidGoid !=
      UIPartyManager.DoesActivePartyHaveTemplate("stone_glb_elddim_01", m_Target$))
      &&
      (Goid.InValidGoid ==
      UIPartyManager.DoesActivePartyHaveTemplate("ring_utraean", m_Target$)))
      {
Now the skrit is checking to see if the quest is active, the party does have "stone_glb_elddim_01", and the party does not have "ring_utraean".
      m_Go$.GetConversation.RSSelectConversation( "conversation_kelti_quest" );
      if ( !GameAuditor.GetDb.GetBool( "kelti_conversation_quest_one" ) )
      {
         GameAuditor.GetDb.SetBool( "kelti_conversation_quest_one", true );
      }
Checking to see if a bool is false (note the "!"):
      else
      {
         if ( !GameAuditor.GetDb.GetBool( "kelti_conversation_quest_two" ) )
         {
Again, checking to see if a bool is false:
     GameAuditor.GetDb.SetBool( "kelti_conversation_quest_two", true );
     // The second time that this conversation gets played,
     // this item is supposed to be given to the player
     SiegePos SpawnPos$;
     GoCloneReq cloneReq$ = MakeGoCloneReq( "gen_ring_utraean" );

     SpawnPos$ = m_Target$.go.placement.position;
     cloneReq$.StartingPos	= SpawnPos$;

     if( m_Target$.Go.Hasmind )
     {
       if(AIQuery.FindSpotRelativeToSource(m_Target$.Go, .25, .5, 2.0, m_Target$.Go.Mind.TempPos1))
        {
           cloneReq$.StartingPos = m_Target$.Go.Mind.TempPos1;
        }
      }
     cloneReq$.SnapToTerrain = true;
     GoDb.SCloneGo( cloneReq$ );
    }
   }
  }
   else if ( Victory.IsQuestActive( "goto_crystwind" ) ||
           ( Victory.IsQuestCompleted( "goto_crystwind" ) &&
              ( Victory.IsQuestActive( "goto_elddim" ) ||
                Victory.IsQuestActive( "goto_elddimb" ))))
   {
This is the first time we see the "||" operator used. It is a logical "or", just like in C. So this check will be true if the "goto_crystwind" quest is active. It will also be true if "goto_crystwind" is completed and either "goto_elddim" or "goto_elddimb" is active. When dealing with several comparisons like this, it can be helpful to insert white space and parenthesis to make it more readable.
      if( Victory.IsQuestActive( "goto_elddim" ) )
      {
         Victory.RSCompletedQuest( "goto_elddim", m_Target$ );
      }
Checking to see if a quest is active:
      if( Victory.IsQuestActive( "goto_elddimb" ) )
      {
         Victory.RSCompletedQuest( "goto_elddimb", m_Target$ );
      }
Checking to see if a quest is active:

      m_Go$.GetConversation.RSSelectConversation( "conversation_kelti_goto" );
      give_item$( m_Target$ );
   }
   else if ( ( !Victory.IsQuestActive( "quest_elddim" )) &&
      (Goid.InValidGoid !=
      UIPartyManager.DoesActivePartyHaveTemplate("stone_glb_elddim_01", m_Target$))
      &&
      (Goid.InValidGoid ==
      UIPartyManager.DoesActivePartyHaveTemplate("ring_utraean", m_Target$)))
   {
This is checking three things: "quest_elddim" is not active, the party does have "stone_glb_elddim", and the party does not have "ring_utraean".
      m_Go$.GetConversation.RSSelectConversation( "conversation_kelti_stone" );
      if ( !GameAuditor.GetDb.GetBool( "kelti_conversation_quest_one" ) )
      {
         GameAuditor.GetDb.SetBool( "kelti_conversation_quest_one", true );
      }
      else if ( !GameAuditor.GetDb.GetBool( "kelti_conversation_quest_two" ) )
      {
         GameAuditor.GetDb.SetBool( "kelti_conversation_quest_two", true );
      }
      else if ( !GameAuditor.GetDb.GetBool( "kelti_conversation_quest_three" ) )
      {
         GameAuditor.GetDb.SetBool( "kelti_conversation_quest_three", true );
      }
      else if ( !GameAuditor.GetDb.GetBool( "kelti_conversation_quest_four" ) )
      {
         GameAuditor.GetDb.SetBool( "kelti_conversation_quest_four", true );
         // The fourth time that this conversation gets played,
         // this item is supposed to be given to the player
Here are several checks of Boolean variables, all with the "!" operator, so they are checking to see if the variable is false.
         SiegePos SpawnPos$;
         GoCloneReq cloneReq$ = MakeGoCloneReq( "gen_ring_utraean" );

         SpawnPos$ = m_Target$.go.placement.position;
         cloneReq$.StartingPos	= SpawnPos$;

         if( m_Target$.Go.Hasmind )
         {
            if(AIQuery.FindSpotRelativeToSource(m_Target$.Go, .25, .5, 2.0, m_Target$.Go.Mind.TempPos1))
            {
               cloneReq$.StartingPos = m_Target$.Go.Mind.TempPos1;
            }
         }
         cloneReq$.SnapToTerrain = true;
         GoDb.SCloneGo( cloneReq$ );
      }
      else
      {
         m_Go$.GetConversation.RSSelectConversation("conversation_kelti_goto");
      }
   }
   else if ( ( !Victory.IsQuestActive( "quest_elddim" )) &&
      (Goid.InValidGoid !=
      UIPartyManager.DoesActivePartyHaveTemplate("stone_glb_elddim_01", m_Target$))
      &&
      (Goid.InValidGoid !=
      UIPartyManager.DoesActivePartyHaveTemplate("ring_utraean", m_Target$)))
   {
      m_Go$.GetConversation.RSSelectConversation( "conversation_kelti_stone_ring" );
   }
This checks to see if the quest is not active, and the party does have both items:
      else if ( ( Victory.IsQuestActive( "quest_elddim" )) &&
         (Goid.InValidGoid !=
         UIPartyManager.DoesActivePartyHaveTemplate("stone_glb_elddim_01", m_Target$))
         &&
         (Goid.InValidGoid !=
         UIPartyManager.DoesActivePartyHaveTemplate("ring_utraean", m_Target$ )))
      {
         Victory.RSCompletedQuest( "quest_elddim", m_Target$ );
         // finish quest, since they actually have the ring already
         m_Go$.GetConversation.RSSelectConversation( "conversation_kelti_stone_ring" );
      }
This is checking to see if the quest is active, and the party does have both items:
      else
      {
         if (!Victory.IsQuestActive("quest_elddim"))
            {report.generic("No quest_elddim for you!\n");}
         if (Victory.IsQuestActive("quest_elddim"))
            {report.generic("You have quest_elddim!\n"); }

         Report.Error( "TELL SARAH: Kelti doesn't have a conversation set up for
            this condition!  Sarah will need to know what quests you have,
            what states they are in, and what quest items you have
            (or don't have)." );
         m_Go$.GetConversation.RSSelectConversation("conversation_kelti");
      }
This "else" block is only here for legacy reasons. If you follow the logic above, you will see that this block should never even be reached. However, during development (when not all logic is in place), blocks like this can be very handy to print out error messages that will only be seen if there is a problem.
      TryAnim$('talk');
      m_Go$.GetConversation.RSActivateDialogue();
   ]]
}

Conclusion
<Back to Table of Contents>

We have covered what talk skrits are and how they function. We have covered several methods of playing different conversations (Boolean variables, quests states, and inventory items), custom conversation buttons, giving/taking items from the player, and some advanced functionality. All of these will help you with adding in functional conversations to your mod and assist you with setting up quests in Siege U: 209 - Quests.

Appendix A: Tips
<Back to Table of Contents>

Most of the syntax should look familiar to those who have been exposed to C++. An exception that you may have to worry about is this block:
#only( game )
[[
   ...
]]
This block is used to insulate code from Siege Editor. Since the game engine is actually running in the editor, skrits get loaded and interpreted when you run the editor. Since it is the editor, however, not all functions are accessible, so this just tells the editor to ignore that block (the game will still read it like it normally does). If you are using functions that look inside the target's inventory, or you are getting errors about invalid functions when you load the editor, try insulating the offending code in this block.

Skrit allows you to do some amazing things. Take a look around the *.skrit files and if you see something you like, try copying and pasting it into your own skrit file.

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_209 Quests

Quests

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

Definitions

What is a Quest?

What is a Chapter?

Quests

Overview

Practical Exercise - Create a Quest

Tools

Chapters

Overview

Practical Exercise - Create a Chapter

Conclusion

Introduction

What You Need For This Tutorial
  • Dungeon Siege, updated to version 1.09B or later
  • Siege Editor
  • Siege Editor Manual (included with the Siege Editor during installation)
  • A test map you can practice with.
What This Tutorial Assumes You Have Already Learned

  • 203A - Triggers I
  • 203B - Triggers II
  • 208A - NPCs
  • 208B - Talk Skrits
  • Are familiar with basic Siege Editor operations.

Definitions
<Back to Table of Contents>

What is a Quest?
A Quest is any Journal entry that has both an active and complete state. Quests are often tasks assigned by NPCs and usually aid in storytelling. Quests can be sorted under Chapter headings, or can appear in the order in which they are activated.

What is a Chapter?
A Chapter is a Journal entry that has only a single state, and serves to organize Quests into categories. New Chapters are generally displayed on the screen as a picture that fades away after a few seconds.

Quests
<Back to Table of Contents>

Overview
Simple quest creation can be done completely within the Siege Editor, while more complicated quests involving NPC behavioral or conversation changes require additional work creating skrits in a text editor. There are four typical quest subtypes:
  1. Courier quests: You are instructed to take an object or message to another place or NPC
  2. Kill quests: You must kill a specific NPC or variety of NPCs
  3. Travel quests: You must journey from one place to another
  4. Puzzle quests: You must solve a riddle or puzzle.
If you want quests to play a large role in your world, it's generally a good idea to reward players in some way for completing a quest.

Practical Exercise - Create a Quest
The first step in creating a quest is deciding what the purpose of the quest is. Let's begin with a basic courier type quest set in a friendly environment. First, we need storyline to supply a reason for sending the player on the quest: a townswoman has an urgent message for her sister, and if you would be kind enough to act as courier, she's sure her sister would be grateful. In editing terms, this means we want to assign a quest when you speak with the first sister, and complete the quest when you speak with the second sister.

Place the Props
Open the Siege Editor and load your favorite basic test map. Now, we need some props. Let's use two female NPCs (NPC 1: mp_townfolk_female_01 and NPC 2: mp_townfolk_female_02) and a basic chest (chest_cherry_ornate, found in Game Objects\furnishings\containers\misc) for the reward. Place NPC 1 at one end of the map and NPC 2 a distance away. Place the chest next to NPC 2.
TIP: When setting up quest participants, it is useful to label them in the dev_instance_text field to make their purpose more obvious within SE. For an example: Open mp_townfolk_female_01's object properties and scroll down to the [common] block. In the dev_instance_text field, type, "QUEST - NPC 1." Now, when you select the NPC again, you'll see the dev_instance_text floating above her head, making her easily recognizable (so long as you have the Single Object Debug HUD option enabled).
Create the Quest
Within Siege Editor, open the Quest/Chapter Manager, found in the Tools Menu. Click the Add button and type in a name for the quest: sisters_message. Click OK and you will now see the quest listed under the Quests box.

Configure the Quest
The game needs to know more about the quest than just its name: where and how does it appear in the in-game Journal, and what sound plays when it appears? Select the quest and click the Edit button to configure it. Under Screen Name, type the name you want to have displayed in the Journal: A Sister's Message. For now, leave the Quest Image field blank. From the Victory Sample dropdown, pick the sound file you want to play, usually s_e_level_up_quest. Skip the Award Skrit and Chapter options for now.

Add a Quest Update
Click the Add button in the Quest Updates box. Select the [none] entry that appears in the Description field, and click the Edit button. The Quest Update window will open, allowing you to configure the actual information that describes the quest in the Journal: what step is it, who assigned it, what it entails, and what NPC conversation addresses it? Similar to Conversation Orders, Quest Steps begin with 0 for the first step and increment from there. Set the Step to 0. Check the "Is this a required update?" checkbox. This specifies whether or not quest steps can be skipped. In our case, such a short, local quest would be confusing if the first step was skipped. Next, add the Description, or the text directions that will appear when the player highlights the quest in the Journal: Deliver Ella's message to her sister Fwynn Gilverstych. In the Speaker field, type NPC 1's screen name: Ella Riverstarn. Leave the Conversation Address blank for now - we'll fill that in later. Click OK through the next windows to get back to the main Editor window.
TIP: When adding Quest Update Descriptions, try to make the description informative as well as short. Generally, the key information you want to get across is What (they are supposed to do), Who (they are supposed to interact with), and Where (they are supposed to go). Our example above is vague in the Where category since we're only dealing with a test map, but in a real level we'd flesh it out more with Fwynn's location as well.
Add a Conversation to NPC 1
Now that our NPCs are in place and we have a quest to assign, we need to add dialogue to explain the situation and activate the quest. Select NPC 1, give her the basic talk job, and create a conversation for her with the Region Conversation Manager. Call it conversation_ella, and add a text block. Add the story text:
"Hail, friend! Your arrival is well-timed! You see, my sister, Fwynn Gilverstych, is leaving on a journey today to join her husband in a distant town. It's her first trip alone, and the Fortune Teller gave me a special reading - it seems my sister will come upon ill-luck unless she wears her lucky amulet at all times! I'd tell her the news myself, only I sprained my ankle on the way back from the Fortune Teller's home, and I'll never make it in time before my sister leaves. With your two good legs, you could certainly catch her if you leave right now. Please help! I'm sure she'll reward you generously for your time."
Do not give this block an Order, since we want it to play every time NPC 1 speaks. Check the Quest Dialogue box found in the lower left corner of the Settings area to mark this text as quest-related.

Activate the Quest
To make the sisters_message quest become active (and display in the Journal), we must activate it in the text block that refers to it. Click the Activate Quest button found in the upper right corner of the Settings area. In the resulting Activate Quests window, select sisters_message from the Available quests dropdown. Select the 0 step from the Step dropdown, and click the Add button. Close all of the conversation windows until you are back to SE's main window.

Add a Conversation to NPC 2
Select NPC 2, give her the basic talk job, and create a conversation for her with the Region Conversation Manager. Call it conversation_fwynn, and add a text block with the dialogue:
"Oh dear, can't you see I'm busy? I really can't talk now, must finish packing - what? You just spoke with my sister Ella? You mean the amulet Ella made for me all those years ago? I will be sure to find it, then. I'm already late enough as it is; it certainly wouldn't do to invite an even greater disaster! Thank you, friend, I am most grateful for your help. Here, take a peek inside this chest and see if there's anything you'd like."
Do not give this block an Order, since we want it to play every time NPC 2 speaks.

Complete the Quest
To make the sisters_message quest become completed, or finished (and display in a faded shade in the Journal), we must complete it in the text block that refers to it. Click the Complete Quest button found in the middle right side of the Settings area. In the resulting Complete Quests window, select sisters_message from the Available quests dropdown, and click the Add button. Click OK and close all of the conversation windows until you are back to SE's main window.

Add the Quest Dialogue Address
If you want the in-game Journal function, "Show Dialogue" to have an effect, you need to point the quest update to the corresponding conversation. To do this, open the Quest/Chapter Editor, select the sisters_message quest and edit it. Edit the Step 0 Quest Update. From the Conversation Address drop down, choose conversation_ella. Click OK until you are back to the main window. Save the region and try loading it in the game.

Advanced Suggestions
The above quest is very simple and could be enhanced in a variety of ways by creating custom talk skrits for both NPC1 and NPC2. For example, if this was a real quest, you'd probably want to include a few more dialogue options. NPC 1 should really play one conversation for when you first meet her and acquire her quest, and an entirely different "thank you!" conversation for when the quest is complete. NPC 2 should play a different conversation for if you talk to her before receiving the quest, and you might want her to drop the reward item instead of pointing you to a chest. Or, you could make the chest "use_toggle," which is an option that allows a chest to be locked (and display a message of your choice when used) until you send it an activate request. You could also have NPC 1 give you the amulet to bring to NPC 2, and use a custom button to actually facilitate the transfer.

While we skipped adding a quest image to the Journal in the tutorial above, this is something you can easily do. First, look at the existing quest images found in Bits\art\bitmaps\gui\in_game\menus\ for examples. Then, create your own in your favorite graphics program and save it in .psd format. To make it appear in the Journal, just type the file name without the extension in the Quest Image field in the Quest Editor window.

Tools
Quest activation and completion are not limited to conversations and talk skrits. The trigger action, change_quest_state solely manipulates quests. There are also a few other useful features such as use_toggle objects, the check_quest gizmo, changing the selectability or visibility of an object, depending on quest state, and the trigger conditions go_within_bounding_box and has_go_in_inventory.

Change_quest_state Trigger Action
Quests can be activated, updated, and completed at any time or place in a game using the change_quest_state trigger action. To use this, place a generic trigger with any condition and the change_quest_state action. Type the quest's name in the Quest Name Value field, specify whether you want to activate, deactivate, or complete the quest in the Quest State Value drop down, and if the quest is being activated, specify the Quest Activation Order in its Value field. In trigger terms, updating and activating a quest are the same thing. Note that you cannot activate or update a quest without specifying a Step. By default, the Step is 0 - i.e., the first step of the quest. If you want to update an existing quest, activate it to the desired Step.

The change_quest_state trigger action is particularly useful when entering an important area (i.e.- across the threshold of a room the target is in for a "Kill" quest) when picking up a quest-related object, or when an object dies. Some of the more useful trigger conditions typically used with the change_quest_state action are below:
Party_member_within_bounding_box: If you have a quest that includes an area containing something important to the quest, include a quest step that changes the quest description in that area. Use a single-shot party_member_within_sphere or party_member_within_bounding_box condition so that when the character enters the trigger area, the quest updates to the next step. For example, if the Quest had originally told you to travel to the fourth floor of the Governor's house to locate an item, when the character actually enters the fourth floor, you could update their quest to now tell them to find the item sitting on the table in the library.

Receive_world_message - we_picked_up: Regarding quests that updated to a new step when an item is picked up, add a single-shot trigger to the actual item. To do this select the item and open its properties. Click on the Trigger Properties tab and click the New button to add a trigger. Now, go about configuring the trigger as you would an actual trigger gizmo. Use the receive_world_message condition with the World Message Event "we_picked_up" - i.e., the trigger is waiting until the object is picked up before enabling the action to occur. For example, when you pick the book up in the example above, the quest step advances to a new description and tells you to return the book to the quest-giver.

Receive_world_message - we_killed: Quest changes that occur when an object or actor dies are similar to the we_picked_up message. As above, this is a receive_world_message trigger. The World Message Event "we_killed," tells the game to update the quest after the object has been killed. For example, if you have a Kill Quest that requires you to kill an evil wizard, upon the wizard's death the quest would complete.

Note: You might notice there are other World Message Events that refer to object death, such as the we_anim_die and we_req_die messages. While these may at first seem acceptable to use, they are not multiplayer-friendly messages and will not function under certain conditions. When used with a server-based action such as change_quest_state, the we_killed message is generally dependable regardless of whether the player triggering it is a client or server.
Use_toggle Doors and Chests
Use_toggle objects are a class of objects that:
  • Can be used at all times but can have either a locked and unlocked state
  • Play a screen message while locked, but do not open
  • Must receive a we_req_activate message to become unlocked and thus can be opened
Essentially, use_toggle objects start out locked and tell you that they're locked until they are specifically told to unlock. This locking feature combined with a screen message makes use_toggle objects very useful to involve in quests and puzzles. For example, you could create a quest/riddle dungeon that utilizes use_toggle doors with messages in a riddle format. When a player tries to use the door, the riddle is displayed as a message and a quest could be assigned in the Journal. Use_toggle doors and chests are especially powerful in restricting access to quest areas or content only to those who actually have the quest.

Existing use_toggle doors can be found under Game Objects\doors\use_toggle. Each of these has been created to display a specific message while locked. Since you'll probably want to add your own message, you will need to template a new door containing your message. Look in Bits\world\contentdb\templates\regular\special\door.gas for examples of use_toggle doors. Take a look at the following example use_toggle door template:
[t:template,n:door_glb_crypt_use_toggle]
{
   specializes = base_door;
   doc = "MP_abc_r1_use_toggle";
   category_name = "use_toggle";

   [aspect]
   {
      model = m_i_glb_door-crypt-01;
      [textures]
      {
         0=b_i_glb_door-crypts-01;
      }
      material		 = stone;
   }
   [body]
   {
      [chore_dictionary]
      {
         chore_prefix = a_i_glb;

         [chore_open]
         {
            skrit = transition;
            anim_files: 00 = door-crypt-01-open;
         }
         [chore_close]
         {
            skrit = transition;
            anim_files: 00 = door-crypt-01-open;
         }
      }
   }
   [messages]
   {
      [locked]
      {
         screen_text = "This door appears to be
         magically sealed with a glyph bearing
         the Mark of Elddim.";
      }
   }
   [door_basic]
   {
      use_toggle		= true;
      oneshot 		= true;
      delete_on_open	= true;
   }
}
The important blocks in the above example are the [messages] block, which contains the actual screen message displayed when the door is used while in its locked state, and the [door_basic] block, which contains the use_toggle field set to true. To create a new use_toggle door, all you need to do is copy the existing door template, add the use_toggle=true; field to the [door_basic] block, and add the [messages] block containing the [locked] component with the screen text you want to display.

An example from Dungeon Siege is the Utraean Peninsula "Hovart's Folly" Quest. The Gavel of Convening is a quest item that is required to advance to the next step of the Hovart's Folly Quest, and it is found in a room inside the Ancient Crypt (abc_r1). However, that room is only accessible to players who actually have the Hovart's Folly quest in their journal already. It is blocked by a use_toggle door that displays the message, "This door appears to be magically sealed with a glyph bearing the Mark of Elddim" if it is used in the locked state. However, if the player does have the Hovart's Folly quest, then the door will be unlocked when they come across it. This is facilitated through the use of the check_quest command gizmo.

Check_quest Gizmo
The check_quest gizmo is a specialized command found in Game Objects\gizmos\commands\specialized. Upon activation, it will check for a specific quest and state and send an activate or deactivate depending on the result. By placing a party_member_within_bounding_box conditional trigger across a choke point (doorway, hallway, etc) and configuring it to send a we_req_activate message to the check_quest gizmo, you can easily control the circumstances of an area based on quest state. To configure the check_quest gizmo, place it in a region and open its Template Properties. There are six adjustable fields within the [check_quest] block; usually you will want to enter the quest name, state, and scid you want to send a message to.

Continuing from the Dungeon Siege example above, when the player passes through one of the bounding boxes near the Gavel room, the trigger activates the check_quest gizmo to look for the Hovart's Folly quest. If the result is true, then the check_quest gizmo sends an activate request to the use_toggle door blocking the room and voila, the door is unlocked.

Cmd_selection_toggle Gizmo
Dungeon Siege's Kingdom of Ehb adventure maintained a forward-moving theme throughout the experience. This meant that we did not generally encourage backtracking, thus Quests often began and ended in two different locations. At times, we wanted to create the illusion that the same NPC who assigned the quest was also the one who completed it, but without requiring you to return to his original location.

Since it wasn't feasible to have the same NPC actually travel to the new location, instead we used two instances of the same character, with the second invisible until the appropriate time. A great example of this is the quest to Clear Glitterdelve Pass. A town guard in Stonebridge assigned the quest, asking you to open the damaged gates from the other side. After passing by the gates yourself and verifying that they were indeed blocked, you then traversed Wesrin Cross and emerged on the other side of the gate, cleared the rubble blocking it, and opened it to find the very same town guard there waiting to thank you. This was all accomplished via the cmd_selection_toggle gizmo, which allows you to toggle all or one of an object's selectability, visibility, usability, and invincibility.

The cmd_selection_toggle gizmo is a specialized command found in Game Objects\gizmos\commands\specialized. While this gizmo began as a simple selection toggle, it has since expanded to include visibility, usability, and invincibility as well. The word, "selection" is used most frequently in its Template Properties descriptions, but the term is interchangeable with any of the other features, depending on which ones you are toggling. To configure the cmd_selection_toggle, place it in a region and open its Template Properties. In the [cmd_selection_toggle] block, insert the scid of the object to be toggled, the messages that need to be received to change the toggle, what type of toggles (selectability, usability, visibility, and/or invincibility) to enable, and the starting state of the object when it enters the world. The object may enter the world according to three possible starting states: 0 for if you do not want it changed from its default state, 1 for if you do want it to be toggled (ie, unselectable, unusable, invisible, or invincible) and 2 for if you do not want to be toggled (ie, selectable, useable, visible, or damageable). From the example above, the first town guard was placed in Stonebridge, and a second town guard was placed next to the Glitterdelve gates - with his starting state set to 1 for invisibility. As the party neared the exit of Wesrin Cross, they walked through a trigger bounding box that sent a we_req_deactivate to the cmd_selection_toggle gizmo - thus by the time the party emerged outside and destroyed the rubble in front of the gate, the guard was visible on the other side.

Go_within_bounding_box or go_within_sphere Trigger Condition
The go_within_bounding_box trigger condition is very useful in determining if a quest object is located in a specific area. For example, if a quest-related Chalice had to be placed on an altar in order to complete a quest, then you would use a go_within_bounding_box trigger condition. You then have the choice to have the trigger look for the Chalice either by its SCID (if it had been previously placed somewhere in the world), or by its Template Name (if it had been generated as loot from a monster's death, for example). If you use the trigger action change_quest_state as well, as soon as the player drops the Chalice on the altar, their quest would be updated as well.

Has_go_in_inventory Trigger Condition
The has_go_in_inventory trigger condition is used to detect if a party member is currently carrying a specific object in their inventory. Similarly to the go_within_bounding_box trigger condition, you choose whether to watch for the object by its SCID or template name. There is also a third option to choose which players' inventory to look in, either players just on the area local to the trigger, or any players in the game world. Note: this condition is not standalone and must always be paired with a party_member_within_bounding_box condition, which should be added to the trigger first. Has_go_in_inventory is very useful in unlocking areas that require a key. For example, the Trial of Gallus Easter egg area of the Utraean Peninsula world is hidden behind a secret door. The Trial of Gallus riddle book is the key to unlocking the door, so only players currently carrying the book are able to reveal the entrance.

Chapters
<Back to Table of Contents>

Overview
Chapters consist of two parts: the Chapter Journal entry, and the Chapter Interface graphic. The Chapter Journal entry is created within SE via the Quest/Chapter Manager, while the Chapter Interface graphic should be created in your favorite 2d graphic program and saved as a *.psd (Photoshop format) file. Chapters are used to organize the quests and storyline.

Practical Exercise - Create a Chapter

Create a New Chapter
Launch SE and open your quest test map from earlier. Open the Quest/Chapter Manager and create a new Chapter (chapter_1) in the Chapters section. Click OK to accept the name. Now that the Chapter is created, Edit it to adjust its settings and Updates. Give it a Screen Name: Chapter 1. You may create your own Chapter Image, or use an existing one such as "b_gui_ig_mnu_jnl_chapter_01." This image is simply a picture of the Roman numeral "I". In the Chapter Updates section, Add a new one. Edit it and set its Step to 0. Give it a description - this text will serve as the dialogue part of the Chapter, and will display in the upper right corner of the Journal when the Chapter is highlighted. Type in: The first step upon the journey through my Test Map. Click OK through the windows until you are back at the base Quest/Chapter Manager window.

Assign a Quest to a Chapter
Select the sisters_message quest you created earlier and click Edit. In the Settings area, click on the Chapter dropdown and select chapter_1. Click OK to close that window and the Quest/Chapter Manager window.

Create the Chapter Interface
The Chapter Interface is similar to a template file. It is a gas file that defines the Chapter interface element, and most importantly, identifies what the chapter art is. Chapter interface art is usually .psd file of white text with a black drop shadow applied. Existing textures are stored in art\bitmaps\gui\nis\. Once your texture is created, turn it into an Interface by adding it to ui\interfaces\chapters\chaptername\chaptername.gas. Within the chaptername.gas file, paste the following:
[chaptername]
{
   b interface = true;
   centered = fade_window;
   [t:window,n:fade_window]
   {
      f alpha = 1.000000;
      b common_control = false;
      common_template = ;
      i draw_order = 1;
      rect = 181,111,437,367;
      texture = b_gui_nis_ch01;
      //change this to the name of your chapter texture
      uvcoords = 0.000000,0.000000,1.000000,1.000000;
      wrap_mode = clamp;
      pass_through = true;
      topmost = true;
      [messages]
      {
         onanimcomplete = notify(fade_chapter);
      }
   }
}
In the "texture = " field, be sure to change "b_gui_nis_ch01" to the name of your customized chapter graphic. Changing any other fields is not recommended. These are various properties that tell the game placement and behavior of the Interface, but these traits remain the same in most circumstances.

Activate the Chapter Interface
Now that you have a Chapter Interface, you can display it in the game. Expand the Game Objects tree to gizmos\special and place an Interface Fader gizmo.

Select the Interface Fader gizmo and open its template properties. Under the [interface_fade] block, find the interface field and type in the chaptername from the chaptername.gas file you created. In the duration field, type in the length of time, in seconds, that you want the chapter interface to display onscreen before fading. Now that the Chapter Interface is setup, place a generic trigger near the region's starting positions. Set it up to be a bounding box or sphere that encompasses all of the starting positions, and wire it to send a we_req_activate to the Interface Fader gizmo.
TIP: In Dungeon Siege, we used a Chapter Interface duration of 7 seconds.
That's all there is to it! If you test it out in the game, you'll see the chapter graphic appear as you enter the world. When you talk to NPC 1 (Ella), you'll receive her quest neatly sorted under the Chapter 1 heading in the Journal.

Conclusion
<Back to Table of Contents>

After completing this tutorial, you should now be comfortable creating, updating, and completing Quests and organizing them into Chapters. By experimenting with different scenarios, you should be able to continue to broaden your skill in creating stories with quests, chapters, gizmos and a few actor and object props. For the more complicated situations, use the knowledge you gained from Siege U: 208B - Talk Skrits to combine multiple conversations and custom buttons with quest and object detection and manipulation.

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_210 Sounds

Sounds

Introduction

Placing Sounds with Siege Editor

Sound Emitters and Sounddb.gas

Using Sound Emitters

Configuring a Sound Emitter

Material Sounds

Adding Materials and Material Sounds

Material Event Rules

Voice Event Sounds

Appendix A

Making New [sounddb] Elevator Material Entries

Sound Effect Descriptors ("SED"s)

Sound Priority

Introduction

In this document, we will look at how sound works in Dungeon Siege. Topics covered will include placing sounds with Siege Editor, and understanding Material and Voice sounds. Readers interested solely in placing sounds in maps need only be concerned with the first section dealing with that topic. That section assumes a basic understanding of placing objects using Siege Editor and configuring the template properties of placed objects.

Readers who continue on to the topics of Material and Voice sounds should be familiar with the issues discussed in Siege U: 201 - Templates.

Throughout the document below, we will be looking at a file named sounddb.gas. You can find this file in the directory \world\global\sounds\. This document is largely focused on sound and sound related files found in the directory \sound\Effects\.

An appendix has been included covering the topics of how to make elevator material sounds, Sound Effect Descriptors (SEDs), and sound priority.

Placing Sounds with Siege Editor
<Back to Table of Contents>

Sound Emitters and Sounddb.gas
Within a region you may place game objects that will act as sources of sound. These objects, called sound emitters, are gizmos, which can be placed and configured in Siege Edit. Sound emitters play sounds not by referring to a sound filename itself, but by using a name given to a sound sample in the [global_voice] block of the file sounddb.gas (found in \world\global\sounds\).

An entry in the [global_voice] block of sounddb.gas will contain an "event name" that is used in a sound emitter, and a corresponding sound sample filename of a .WAV file (not including the file extension) in \sound\Effects.

An example [global_voice] sound entry, taken from sounddb.gas:
env_lakeside: * = s_e_env_lakeside;
In the above example, "env_lakeside" is the event name that we will reference in a sound emitter, and "s_e_env_lakeside" is the name of a .WAV found in \sound\Effects.

Using an existing sound, then, involves configuring a sound emitter to play a sample by referring to the sample's event name. If you wish to add a new sound to use in your map, simply add that sound sample (in .WAV format) to \world\global\sounds\Effects\ and make a new entry in [global_voice] using the format:
event_name: * = sample_name;
Using Sound Emitters
There are two types of placed sound emitter objects available. These are emt_sound, and emt_sound_act. These two sound emitters can in the gizmos/special folder located in the object browsing window of SE.

Each of these emitters allows you to specify the sound event you wish to play, and configure how the sound will be played. The difference between these two emitters is that emt_sound_act, unlike the emt_sound, may be turned on and off by sending it a we_req_activate or we_req_deactivate message (refer to SiegeU: 203A - Triggers II for more information on sending messages). The emt_sound sound emitter, however, is "cheaper" to use in terms of game performance. As a general rule, always use emt_sound unless you specifically need to turn an emitter on and off.

Note that "emt_sound" and "emt_sound_act" are the "fuel block" (a.k.a template) names for these objects. This article assumes that Siege Edit's Content Display is set to this fuel block view (in Siege Editor, the "Preferences" window will allow you to set this Content Display view).

Configuring a Sound Emitter
After placing a sound emitter in your region, open its Template Properties in the Object Properties window. Look for the sound_emitter block (or sound_emitter_act if using an emt_sound_act). This block will allow you to configure how an event sound is played. We will discuss some of the settings available below.
  • repeat: This setting will make the sound continually repeat, based on values that you supply for repeat_rate and max_repeat_rate.
  • repeat_rate, max_repeat_rate: The repeat_rate is the amount of time (in seconds) that should pass from the beginning of the event sound and when the sound is restarted. Supplying a max_repeat_rate allows you to make a range of time from which a randomly selected time will be chosen to restart the sound.
  • event_sound: The name of the event sound to play, as listed in sounddb.gas' [global_voice] block.
  • min_dist, max_dist: The distance from the emitter, in meters, at which the intensity of the sound begins to fall off, and the distance at which the sound intensity reaches zero, respectively.
There are two alternatives to repeat rates for controlling when a sample is played. Each of these is based of the in-game time of day.
  • start_hour, stop_hour: Game hour to begin playing the event sound, and the game hour at which to stop.
  • time_chunk: A portion of a game-day during which an event sound should be played.

Material Sounds
<Back to Table of Contents>

Material sounds are centered on a material type that is assigned in the template of a game object (via the [aspect] component's "material" field), and the [sounddb] block in the file sounddb.gas (found in \world\global\sounds\).

A material type is a way of assigning a property to a game object that describes the object for the purposes of game sounds. An actor such as the Ice Warrior, for example, has a material of "ice", and a weapon such as a short sword has a material of "steelsword". When the Ice Warrior or the shortsword are involved in combat, these assigned materials are taken into account when generating combat sounds or other sound-requiring events.

The material type for a game object is assigned in the [aspect] block of that object's template (or template instance). When a material sound event takes place (such as in melee combat - a sword hitting plate armor, for instance), the game engine looks for entries in the [sounddb] block of the file sounddb.gas for the materials of the objects involved in the event and, if found, a corresponding sound is played.

Adding Materials and Material Sounds
Materials are assigned in the [aspect] block of a game object's template by defining a value in the material field.

Example aspect block assigning a material.

   [aspect]
   {
      material = ice;
   }
Adding an entry for a material event in sounddb.gas involves adding event information to the [sounddb] block. This [sounddb] entry will contain a source and destination material, an event name, and information about the sound sample to be played. The sound sample information consists of the name of the sound sample (or "SED") to play, and sound priority information for the entry (SEDs and sound priority are discussed in the Appendix below).

The "source" and "destination" materials can best be thought of in the context of melee combat (although they are taken into account in ranged and even magical combat, as well). We will use for our example a fight between an attacker who is using a sword, and a defender wearing leather armor. When the attacker swings a sword and hits the defender's armor, an "attack_hit_glance" event occurs.

For this material event the source material would be "steelsword" (as defined in the base_sword template in contentdb\templates\regular\interactive\wpn_sword.gas). The destination material would be "leather" (as defined in the base_body_armor_leather template in amr_body_leather.gas).

In our example, then, we have a source material of "steelsword" and a destination material of "leather" for the event "attack_hit_glance." Looking in the [sounddb] block of sounddb.gas, we see that for such an event, s_e_hit_steelsword_flesh1_SED would be played.

It is interesting to note that the material types are arbitrary - you can create any material type you like and specify that material type within an object's template. As long as an entry in the sounddb has been added for that material type for the event that occurs (e.g. attack_hit_glance), the specified sound will play.

An example [sounddb] entry taken from sounddb.gas:
steelsword:leather:attack_hit_glance:*=s_e_hit_steelsword_flesh1_SED;
A [sounddb] entry follows this format:
[source_material]
{
   [destination_material]
   {
      [event]
      {
         * = sound_name;
         priority = low, norm, high, or always;
         ...
      }
      ...
   }
   ...
}
...
or using standard .GAS file "shorthand":
source_material:destination_material:event:* = sound_name;
source_material:destination_material:event:priority = norm;
Note that the sound_name may be either the filename of a .WAV, or an "SED" (an SED is a .GAS file that resides in the \sound\Effects directory, and contains more information to tell the engine "how" to play the sound). The file extension should not be included in the sounddb entry. Again, sound priority and SED files are discussed in the Appendix at the end of this document.

Material Event Rules
When a material event occurs, a series of rules, or steps, are followed to determine the source and destination materials of the game objects involved in the event.

Source material is found by a check for material done first on the source object's ammo, then on the contents (if any) of the weapon hand, and finally on the actor template itself. With this in mind, an archer could have a source of "arrow", a spellcaster could have a source of "fireball", a sword-wielding fighter could have a source of "steelsword", and an unarmed krug scout could have a source of "flesh" (fists).

Destination material is found by a check for material done first on the destination object's shield, followed by a check for material on the torso armor, and finally on the actor's template itself. For example, if a warrior wearing plate armor is holding a wooden buckler, the warrior's destination material is "wood" (the material specification of the shield). However, if that same warrior then dropped the shield, the destination material becomes "steel" (the material specification of the plate armor).

Voice Event Sounds
<Back to Table of Contents>

A "voice event" is a sound event that is specific to a particular object, or objects within the same specialization tree (unless later overridden, of course). Examples of voice event sounds are the sound an actor plays when it dies, or the sound an item plays when the item is dropped on the ground. A voice event may be found in the template of an object in its [aspect] [voice] block, or in the [global_voice] block of sounddb.gas.

Voice events, placed within the aspect:voice block, follow this format:
[event]
{
   * = sound_name;
   * = sound_name2;
   ...
   priority = low, norm, high, or always;;
}
An example [voice] block from the base_darkling template in darkling.gas, showing multiple events in shorthand format:
[voice]
{
   [cast]          { * = s_e_attack_darkling; }
   [die]           { priority = high; * = s_e_die_darkling; }
   [enemy_spotted] { priority = high; * = s_e_call_darkling_SED; }
   [hit_glance]    { priority = low; * = s_e_hit_darkling_SED; }
}
In the above example, the base_darkling template's [voice] block contains multiple events ("cast", "die", etc.), consisting of sound name and priority. Check the documentation in sounddb.gas for a more extensive list of event names.

When a voice event is requested, the engine tries to play a sound by performing a query. First, the aspect:voice block of the object involved is checked for an [event] (cast, die, enemy_spotted, etc). Next, if the game object's material is not of type 'generic' (if it has a material value specified in the [aspect] block), the [sounddb] in sounddb.gas is checked using the object's material as source and generic as the destination type. And, failing this, the [global_voice] block of sounddb.gas is checked.

Appendix A
<Back to Table of Contents>

Making New [sounddb] Elevator Material Entries

Elevator gizmos contain a "material" field, which references [sounddb]. By supplying a destination material name, an elevator will play sounds based on elevator events. See the Siege U 206: Elevators for more information.

A material that will be used for an elevator should use a source material of "elevator", and a destination material name supplied by you. It is this destination material that should be entered into the "material" field of an elevator gizmo.

The events that take place during an elevator transition that can be used in a material are level_1, level_2, moving_1, and moving_2. An elevator material would comprise of a sound name for each event used.

Example elevator material "sd_large" from soundb.gas:
   elevator:sd_large:moving_1:* = s_e_env_elevator_lrg_start;
   elevator:sd_large:moving_2:* = s_e_env_elevator_lrg_start;
   elevator:sd_large:level_1 :* = s_e_env_elevator_lrg_stop;
   elevator:sd_large:level_2 :* = s_e_env_elevator_lrg_stop;
Sound Effect Descriptors ("SED"s)

As you may have noticed, "SED"s can often be used interchangeably with actual .WAV filenames when calling a sample to be played.

SEDs, or Sound Effect Descriptors, are .GAS files that contain configurable information that affects the playback of a particular sound sample. An SED can control the playback rate of a sample, the maximum number of simultaneous instances of a sample that may be played, and the override falloff distance of a sample.

SEDS appear in \sound\Effects\, and accompany a particular sound file. Our naming convention has been to use the file name of a sound sample followed by "_SED" to name each SED .GAS file (example: s_e_spell_zap_cast_SED.gas is an SED of s_e_spell_zap_cast.WAV).
  • max_simultaneous_samples: maximum number of instances of this sample that may be played at any one time.
  • min_playback_rate , max_playback_rate: A percentage range of rate between which a random rate will be chosen to play the sample.
  • min_override_dist , max_override_dist: distance, in meters, for sound falloff. This forces this sound to always play with these falloff values, overriding values set in an emitter.
An SED may contain any or all of the above properties. In sound events where a sound sample is called, an SED for that sample may be called instead.

Example SED for sound sample s_e_env_brush_rustle:
   [t:sed,n:s_e_env_brush_rustle_SED]
   {
      sound_effect_file = "s_e_env_brush_rustle";
      max_simultaneous_samples = 2;
      min_playback_rate = 0.9;
      max_playback_rate = 1.1;
      min_override_dist = 12.0;
      max_override_dist = 14.0;
   }
Sound Priority

During normal gameplay, a large number of sounds are played on a limited number of sound channels. During these situations when there are more requests to play a sound than there are available sound channels, a sound event's priority is used to decide which sounds play and which are cut.

Sound priority consists of 4 levels, which are, in ascending order of priority, low, norm, high, and always. These are assigned to a sound event either in a template or in sounddb.gas (though not in effect scripts), with the default priority being "norm". Note that sound event priority may not be set in effect scripts.

When a sound is requested to play and there are no unused sound channels, a sound with a lower priority than the requested sound will be "bumped" in favor of the new sound. If all samples currently playing are of a higher priority than the requested sample, the requested sample will not be played.

Sound priority uses the following format:

   [event]
   {
      * = sound_name;
      priority = low, norm, high, or always;
   }
Note that ambient and environmental sounds played via moods, as well as user interface sounds, will always play with the highest priority, independent of any priority that may be given in sounddb.gas or elsewhere.

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_211 Naming Key

Naming Key

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

What This Tutorial Will Teach You

A Warning

Dissecting A Naming Key "Name"

The Naming Key Layout

Practical Exercise: Extending The Naming Key

Introduction

Dungeon Siege stores all of its resource data internally in one of two databases: the GAS Database and the Content Database. The GAS Database contains the definitions of all of the objects that are used in Dungeon Siege while the Content Database contains the actual data referenced by definitions in the GAS Database.

The Naming Key provides the link that allows the definitions in the GAS Database to locate items in the Content Database. The idea behind the Naming Key is simple: Every piece of content has a unique Name and given any valid Name you can locate the appropriate content in the Content Database.

The Naming Key uses index information stored in "NNK" files to associate Names with items in the Content Database. The format used in the NNK files was designed to meet three main goals: the lookup process must be easily automated, the index structure must require minimal maintenance, and all maintenance must be possible using only a simple text editor. As is often the case with a naming system designed to be recognized by a computer, the ease of readability for the rest of us winds up getting tossed out the window. While the Naming Key format may appear to be nothing but gibberish at first glance, rest assured that its Names will start to make sense in very short order.

What You Need For This Tutorial
  • Dungeon Siege 1.1 or later
  • Dungeon Siege Tool Kit
  • gmax
What This Tutorial Assumes You Have Already Learned
  • Siege U: 100
  • Siege U: 200 - Concepts and Terminology
  • Siege U: 201 - Templates
What This Tutorial Will Teach You
  • How the Naming Key is read, used, and created

A Warning
<Back to Table of Contents>

The entire Naming Key system relies on correctly formatted and spelled Names. The importance of naming your content properly can't be understated. If a Name is not valid, the Naming Key lookup will fail, files will appear to go missing, tools will not behave as designed, and eventually bugs will get reported that aren't really bugs at all.

[Examples of common mistakes include using a "_" (underscore) when you should have used a "-" (hyphen) and forgetting to assign a Name to an object in the first place. When something isn't working, check your Names]

Dissecting A Naming Key "Name"
<Back to Table of Contents>

Let's examine some samples of valid Names taken from the Dungeon Siege Content Database:
m_w_misc_flamethrower
b_i_glb_placeholder
t_grs01_houses_guard-stairs
By convention, all Names start with a single character followed by an underscore ("_"). This first character determines the main type of the file that the Name represents. The main type character conveys additional meaning to the Naming Key and describes the information that it contains. Because the type is explicitly encoded in the Name, Names do not require any particular extension (i.e., no .BMP, .PSD, .SNO, nor .ASP). Omitting the extension from the Name allows you to alter the format that the data is stored in (converting a bitmap from a BMP to a PSD, for example) without forcing you to rename all of the instances where the Name has been used.

Once the main type is determined, decrypting a Name is straightforward. Names are parsed left to right, with underscore characters separating successive sections within the Name. Each of the sections is expanded by matching it against sub-type 'trees' defined within the Naming Key NNK files (more on 'trees' a little later). As soon as a matching sub-type tree entry cannot be located for a section, the process stops. The actual location of the file is assembled by concatenating each of the matched sections together and then appending the original Name to the end.

As is the case with filenames in general, all Naming Key Names are considered to be case insensitive. For example, the Name "M_I_GLB" is the same Name as "m_i_glb".

Let's use the Name m_w_misc_flamethrower as an example and determine its location:

We first split the Name into sections to produce:
m
w
misc
flamethrower
Each section is then matched and expanded to become:
"Art\Meshes"
"Weapons"
"Misc"
(In this example, the "flamethrower" section has no matching sub-type tree.)

Finally, concatenation produces the full location:

\Art\Meshes\Weapons\Misc\m_w_misc_flamethrower

We can use this same technique to discover the locations of the other two samples listed at the beginning of this section:
b_i_glb_placeholder
t_grs01_houses_guard-stairs
generate:
\Art\Bitmaps\Items\b_i_glb_placeholder
\Art\Terrain\Grass_1\Houses\t_grs01_houses_guard-stairs

The Naming Key Layout
<Back to Table of Contents>

At this point a key question remains: How is the information needed to match and expand a Name into a Content Database location stored in the NNK files?

Information is stored in the Naming Key in a hierarchical structure, i.e. all the information is arranged in a 'tree' similar to a directory 'tree' on your hard drive.

Adding to the Naming Key is a matter of attaching a new 'tree branch' to an existing branch and giving that new tree branch a Name of its own.

Every tree branch is defined by a line in an NNK file that has the form:

TREE = <complete branch prefix> , <the branch directory>, "<branch description>"

Here are three sample definitions taken from NNK files that illustrate what an actual tree definition looks like:

TREE = M, | Meshes, | "Meshes"
TREE = M_I, | Items, | "Items"
TREE = M_I_BID, | Biddle, | "Biddle's Item Meshes"

The hierarchical nature of the format makes the order in which the TREE definitions are listed within the NNK files important as subsequent definitions build upon a earlier ones. In the example above, "M_I" must be defined before "M_I_BID" and "M" before "M_I".

Matters get more complicated when you consider in the fact that the Naming Key has support for more than one NNK file at a time. All the NNK files need to be parsed in a known sequence if the order of the TREE definitions spread throughout the NNK files is to be preserved.

NNK files fall into one of two types: "Standard" (created by GPG) and "Extra" (created by the mod community). Ordering NNK files is actually a two-step process: first the Standard NNKs, then the Extra NNKs.

Standard NNK files all have names of the form "NamingKeyXXX" (where the value of XXX in the filename represents the version of the key itself). The Extra NNK files can have any valid Windows filename, as long that name does not start with "NamingKey".

Whenever the Naming Key is loaded, resources directories are scanned and all the standard NNK files are collected and sorted. The Standard NNK with the highest version is the one that gets used. The contents of all other standard keys are ignored.

After the most up-to-date Standard NNK has been located and scanned, ALL other files that end in .NNK are collected, sorted and scanned.

If a Name is encountered more than once, (for example if "M_I_BOB" appears in more than one NNK file) then only the first definition is accepted; any attempt to redefine a Name is ignored.

Creating extra NNK files allows you to extend the content database with your own entries by adding your custom naming keys with names like:
MyWorkInProgress.NNK
TeamYahoo_Mod1.NNK
TeamYahoo_Mod2.NNK
In the long run, keeping the Naming Key definitions separated across multiple NNK files will make it easier for end-users to pick and choose the mod content they want to access and avoid the sort of naming conflicts that arise when two people both define objects that use the same Name.

Practical Exercise: Extending The Naming Key
<Back to Table of Contents>

The following 7-step tutorial will walk you through an example of extending the Naming Key with a new NNK file called "MyStuff.NNK". It illustrates a feature in Siege Max that will let you automatically generate an NNK stub file and place it in the correct directory on your hard drive (so that other programs that use the Naming Key for content lookup can access it)

Step 1
Launch Siege Max

Step 2
With Siege Max running, open the MAXScript Listener console by selecting it on the main menu.

Figure 1 - Launching the Listener

Step 3
Enter the Maxscript command dsExtendNamingKey "MyStuff" into the MAXScript Listener console.

Figure 2 - Entering the command (note cursor at end of line)

Step 4
Leave the cursor at the end of the line of text you have typed (as shown in Figure 2 above) and then press the ENTER key on the NUMERIC KEYPAD (not the regular Enter key!). Pressing Numpad-Enter executes the command on the line that the cursor is on. Executing this command will create a new file called MyStuff.NNK (Fig. 3), and then automatically open the new file using "Notepad" to allow you to edit it (Fig. 4). You may want to make a note of the directory on your system in which the new file is created. This is the default location in your "Bits" directory for any extra NNK files.

Figure 3 - Result of dsExtendNamingKey command

Figure 4 - The generated NNK file (before any edits)

Step 5
Use the Notepad editor to make a copy the first TREE definition in the generated NNK file and paste it at the end of the file and remove the leading comment (#) marker (Fig. 5).

Adding this definition will allow you to use Names that are prefixed with m_i_bid for any mesh objects that you create. This new "m_i_bid" definition relies upon the fact that the definition for "m_i" already exists (it is defined as part of the Standard NNK file). Remember to save the file after making your changes!

Figure 5 - The generated NNK file (with additional line at bottom)

Step 6
Switch back to the Listener console, enter the command dsLoadNamingKey(), then press Numpad-Enter to execute it. This command causes the Naming Key to be reloaded (Fig. 6). Additional information is reported to you regarding the NNK files that are accessed by the loading process. You should see an entry for the EXTRA file "mystuff.nnk" that you just created

Figure 6 - Loading the Naming Key

Step 7
You can test your new entry to be sure that everything is working properly with another Listener command. Experiment by entering the following commands:
dsBuildContentLocation "m_i_bid_my-first-model"
returns:
"Art\Meshes\Items\Biddle\m_i_bid_my-first-model"
and
dsBuildContentLocation "garbage"
returns:
"undefined"
because the "garbage" is still not recognized as a valid Name.

Figure 7 - Testing the new entry

Once you are satisfied that your new Name has been recognized by the Naming Key you can start to use it to prefix your own content. You are well on your way to organizing content for your own mods!

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_212 SiegeFX

SiegeFX

Introduction

What is SiegeFX?

Base Effects

Where to Start

Creating Effects

The Data Stack

Variables

Macros

Effect Targets

Linking Effects

Positional links

Logical links

Hooking Effects to Models

Bone positions

Freezing Effect Positions

Playing Effects on the Terrain

Positioning Trackballs

Timing Effects

Pausing Scripts

Delaying Effects

Setting the Duration of Effects

Stopping Effects

Camera Effects

Sounds

Doing Damage Through Effects

Trackballs

Fire

Lightning

LODFI

Color and Alpha

Running SiegeFX Scripts

Running Scripts from Skrit

Running Scripts from Template Triggers

Running Scripts from Other Scripts

Using Effect Parameters

Examples

Conclusion

Appendix A: Effect Parameters

Appendix B: Camera Command Parameters

Appendix C: Bones in the Farm Girl Model

Introduction

This article is made to help you learn how to make SiegeFX. It provides some step by step instructions towards understanding how effects work in Dungeon Siege. This, along with the effects included with Dungeon Siege, should be enough to get you started on creating your own effects for your own worlds.

What You Need For This Tutorial
  • Dungeon Siege, updated to version 1.09B or later
  • A text editor.
  • Untanked resource files, specifically Logic.dsres. Refer to Siege U: 201 for instructions on untanking.
What This Tutorial Assumes You Have Already Learned
  • How to mod content and view it in the game
  • A basic knowledge of Skrit or the template system
What This Tutorial Will Teach You
  • How to use the SiegeFX language to create and manage effects
  • Parameters to change the visual look of your effects
  • How to hook up effects through skrit or through a GO's template
What is SiegeFX?
<Back to Table of Contents>

SiegeFX is the particle effect system used in Dungeon Siege. SiegeFX was developed by Rick Saenz at GPG. SiegeFX, like everything in DS, is centered on the GO (Game Object) system. All effects have a Source, Target and owner GO. SiegeFX is stored in the form of effect scripts. These scripts are comprised of a series of effects that specify what effect to use, how it will look, and what it will run on.

Base Effects
Each effect in a script is built from one of the 22 base effects, and has a set of properties that describe how it will look and act. The list of properties for the base effects can be found at the end of the doc.

The 22 base effects are:

Charge: Random spherically spawned particles coalesce to a larger version of themselves.
Curve: A string of particles move along a random curve.
Cylinder: Texturable polygonal cylinder for creating tubes, funnels, and rings/coronas.
Decal: Creates a decal.
DiscPile: Unused, creates textureless disks.
Explosion: Particles start out in a random circular radius and outwards. Particles are affected by gravity.
Fire: Particles randomly appear within a circular radius and accelerate as they fade out and scale down. One of the most modifiable effects can be used for much more then just fires.
FireB: Similar to regular fire but creates a conical volume for simulating fire breathing/throwing.
Flurry: A bunch of particles moving in spherical polar coordinates with sinusoidal interference.
Lightsource: Dynamic light source.
Lightning: Simple fractal generated lightning/electricity effect that can be strung through numerous targets.
LineTracer: Leaves a tracer behind as the targets move through the world.
Orbiter: One single particle that orbits a target using spherical polar coordinates.
PointTracer: Pre-calculated trajectory tracer traces a single point using a line, textureless. Unused.
PolygonalExplosion: A bunch of textureless polygons start from a flat plane and explode out while rotating and stick into the ground.
Sparkles: Random spherically spawned particles alpha in and then out while never moving from the spawn point.
SPE: Used to create patterns with
Spawn: Object spawning effect.
Sphere: Textureless tessellated translucent sphere.
SRay: (Spinning Ray)Multiple spinning rays originate from a point on a sphere going straight out.
Steam: Particles randomly appear within a circular radius and accelerate upwards as they fade out and scale up.
Trackball: A single particle, which moves in a straight line or follows a target.

Where to Start
You want to start making SiegeFX but don't know where to start? Crack open offensive.gas located in \world\global\effects. I would recommend just taking the effect script fireshot, it should be the second effect in the file, and modify that so you can easily see in game what effects your changes produce.

Fireshot effect script after it has been cleaned out. Ready for playing around with. The messaging at the end of the script is used to notify the spell when it is finished, and clean up memory from casting the spell.
[effect_script*]
{
   name	=fireshot;

   script=[[
      // place test effect here.
      worldmsg WE_SPELL_SYNC_END #OWNER_GOID #OWNER_GOID
      #TARGET_GOID;
      ]];
}
There are a lot of effects in DS, over 1000 to look at. Sometimes the easiest way to learn is to try and take something that is already there and modify it to try to get what you want.

Creating Effects
<Back to Table of Contents>

The primary purpose of a SiegeFX script is to create and play effects. To explain how the effects system works, I think that we need an analogy. Let's imagine that the effects system is a cookie store. A SiegeFX script is a recipe on how to create an effect. It tells you the ingredients to use and how to prepare them. The command sfx create is the equivalent of asking a cook to prepare the cookies for baking. You give him the specifications on what the cookies should look like, whether you want pink hearts or yellow stars, and he returns a sheet of cookies ready for baking. The cook takes the sheet of cookies and puts it on the counter, or the data stack in SiegeFX Terms. You can request the cook to prepare as many cookie sheets as you want, and he will just keep piling them up on the counter. Like wise you can have the SiegeFX system create effects, and the SiegeFX system will keep putting Effect IDs to manipulate them placed on the data stack. This Stack of cookies can't be eaten of course until you bake them. Creating an effect works the same way. You won't see an effect until you start it. First lets take a look at how we specify how an effect will look with the sfx create command.

Command: sfx create

Format: sfx create <Effect type> <Target> "<Params>";

Arguments:
Effect type: One of the 22 base effect types.
Target: Target list to run the effect on. See "Macros" for a list of possible targets.
Params: List of parameters for the effect. These can either be the parameters for the effect type, or the parameters common to all effects. See Appendix A for a list of the available effect parameters.
Stack Use: Creates an effect ID and places it on the stack. Effect IDs can be used to start, stop, or modify how effects are run.

Example 1: Uses the sfx create command to create a particle that rotates around the target. This will push the ID of the effect onto the stack.
   sfx create orbiter #TARGET "color0(1,1,0)";
The Data Stack
<Back to Table of Contents>

Information when running a SiegeFX script is stored on a Stack. Stacks are a common way that computer programs store information. You can think of the stack like in the last analogy. The script is the recipe, and the Stack represents a place on the counter where we can pile prepared cookie sheets. You can take a sheet from the top of the stack, or put another sheet on the top of the stack. If you want you can also look at the sheet on the top. Unless you already know what's in the middle of the stack, the only way to get to the sheets underneath is to remove the sheets from the top. The typical operators that you can use on a stack are Push, Pop and Peek. Push puts an item on the top. Pop removes an item from the top. Peek looks at the top item. The SiegeFX system has some commands that will push data onto the stack. This is the only way that information gets placed on the stack. You can then view this data by using the macros Peek and Pop. You can find more information on macros under the "Macros" section.
#PEEK - Macro that will return the value of the top item on the stack.
#POP - Macro that will return the top value of the stack. Using #POP will also remove the item from the stack.
Now lets say that we wanted to run the effect that we set up in the last section. We can use the sfx start command to run an effect by ID. Since the sfx create command places the ID of the effect it created on the stack we can look there for the ID of the effect we want to run. If we just wanted to look at the value we would use #PEEK. We don't need to reference the ID later though so we can just #POP it off.

Command: sfx start

Format: sfx start <Effect ID>;

Arguments:
Effect ID: Effect ID of the effect to start. Once an effect has been created it's ID is pushed onto the stack. You never need to worry about entering a specific Effect ID. You will always just use an ID stored on the stack. You can start the effect by directly using the ID from the stack like in Example 2, or you can store it for use later like in Example 3.
Example 2: Creates an effect composed of a yellow particle orbiting around the target. This will push the ID of the effect onto the stack. We then pop the ID off the stack and use it to start the effect.
   sfx create orbiter #TARGET "color0(1,1,0)";
   sfx start #POP;
Variables
<Back to Table of Contents>

You can create variables and give them values for use later. This is useful when trying to work with a lot of data; you can easily changethe duration or color of a lot of different effects. Usually you don't need to worry about the type of variables, the exception being a few commands or when you pass parameters to an effect that you are calling from within another effect script. For more information about calling effect scripts, see "Running SiegeFX Scripts". To create a variable all you need to do is use it. To set a value to the variable we use the set command.

Command: set

Format: set $<Variable> <Value>;

Arguments:
Variable: Name that you can use to reference data later. Variable names must start with the symbol "$". You can use the variable anywhere that you could use the value.
Value: Value to set the Variable to. It can be any string, and it will be substituted in place of the variable where ever it is used.
Example 3: Creates a yellow orbiter around the target. This will push the effect ID onto the stack. We set the variable $orb to be the ID of the effect, and then remove the value from the stack. The ID stored in $orb is then used later to start the effect.
   sfx create orbiter #TARGET "color0(1,1,0)";
   set $orb #POP;

   // do some stuff

   sfx start $orb;
Example 4: Creates an orange obiter around the target, and an orange flame on the target. Uses a variable to store the color parameter. SiegeFX will insert the entire string "1,.5,.3" into the parameter list for the effect.
   set $color 1,.5,.3;

   sfx create orbiter #TARGET "color0($color)";
   sfx start #POP;

   sfx create fire #TARGET "color0($color)";
   sfx start #POP;
Macros
<Back to Table of Contents>

Macros are used to access certain information inside an effect script. These Macros are replaced by the SiegeFX system by the data that they represent. Besides the Macros #PEEK and #POP the most commonly used ones are the macros used to reference the script targets. When an effect is started you have the ability to set the source and the target of the script. The source and target can be the same or two different GOs. If the Script is run from skrit then the targets are specified. If the effect is called as a result of a trigger then the target and source are inferred from the message. You can find more information about starting effects under "Running SiegeFX Scripts".

The following macros can be used to make target lists to hook effects to:
#TARGET - returns a target list based on the target of the script.
#SOURCE - similar to #TARGET, but based on the source of the script.
#TARGET_KB - returns a target list based on the kill bone of target of the script. Kill bones are typically located in the center of the chest of a model, they are chosen by the artist that made the model to be the best place for effects to be played.
#SOURCE_KB - similar to #TARGET_KB, but based on the source of the script.
Example 5: Creates a blue particle around the source kill bone, and a red one around the target kill bone. We use the macros #TARGET_KB and #SOURCE_KB to run the effect on a different part of the model.
   sfx create orbiter #TARGET_KB "color0(1,.5,.25)";
   sfx start #POP;

   sfx create orbiter #SOURCE_KB "color0(.25,.5,1)";
   sfx start #POP;
Effect Targets
<Back to Table of Contents>

Each effect has a target that it is based on. This is used by the effect to determine where to draw the effect. The linetracer and curve effects need two effects to work. Some effects have a line mode where they can have two targets; these include fire, explosion, and steam. The lightning effect can be strung through two or more targets. You can add additional targets to an effect by using the sfx target command.

Target - Can be used instead of an effect id when specifying additional targets on an effect. References the same GO as #TARGET.
Source - Can be used instead of an effect id when specifying additional targets on an effect. References the same GO as #SOURCE.
Command: sfx target

Format: sfx target <Effect ID 1> < Effect ID 2 or Script target>;

Arguments:
Effect ID 1: ID of the effect script to add a target to.
Effect ID 2: ID of the effect to use as a target for the effect specified by Effect ID 1
Script target: Script target as explained above, can be either source or target.
Example 6: Creates a line of golden fire between the target and the source. The fire effect is created with the line() parameter. With the line parameter set the fire effect can use two targets and draw a line between them. The second line of the example takes the effect ID stored on the top of the stack and adds an additional target to it, the source of the effect script to be exact.
   sfx create fire #TARGET "line()color0(1,1,.7)";
   sfx target #PEEK source;
   sfx start #POP;
Linking Effects
<Back to Table of Contents>

Often when making effects we want a group of effects to move or act together. There are a couple of types of connections we can make. The first is a positional link. We can take an effect and remove it from its initial target and target it to another effect. The second type of connection we can make is a logical link. We can attach one effect to another such that when you do something to the parent effect it happens to both.

Positional Links
To hook effects together we need to target one to another. The sfx target command is what we use to do this. If we don't want to use the initial target then we need to remove it from the effect. We can use the sfx rat command to remove all targets from an effect.

Command: sfx rat

format: sfx rat <Effect ID>;

Arguments:
Effect ID: Effect to remove the targets from.
Example 7: Two orbs from Example 5 with purple fire between them. Using the sfx rat command to remove the original target from the fire. Then we can target it to both of the orbiters.
   sfx create orbiter #TARGET_KB "color0(1,.5,.25)";
   set $orb1 #PEEK;
   sfx start #POP;

   sfx create orbiter #SOURCE_KB "color0(.25,.5,1)";
   set $orb2 #PEEK;
   sfx start #POP;

   sfx create fire #TARGET "line()color0(.75,.35,.75)";
   sfx rat #PEEK;
   sfx target #PEEK $orb1;
   sfx target #PEEK $orb2;
   sfx start #POP;
Logical Links
Using the sfx attach command you can make two effects act as one. When you start the effect then any attached effect will start as well, likewise when you end the parent then the attached effect will stop.

Command: sfx attach

Format: Sfx attach <Effect ID 1> <Effect ID 2>;

Arguments:
Effect ID 1: This is the parent effect, changes to it will also affect the effect that is attached to it.
Effect ID 2: This is the effect that you want to attach to Effect ID 1. After the attachment you can control both by what you do to Effect ID 1.
Example 8: Creates a yellow orbiter with yellow fire on it. Note that since the fire is attached to the orbiter that we don't need to tell it to start. If we were to stop $orb1 then the fire would stop as well.

   sfx create orbiter #TARGET_KB "color0(1,.75,.25)";
   set $orb1 #POP;

   sfx create fire #TARGET "color0(.75,.55,.25)";
   sfx rat #PEEK;
   sfx target #PEEK $orb1;
   sfx attach $orb1 #PEEK;

   sfx start $orb1;
Hooking Effects To Models
<Back to Table of Contents>

We don't always just want to hook an effect to one of the default targets. Sometimes we want to base an effect off of a position, but we don't want it to move with the target. There are several commands that we can use to move effects around or attach them to different points of a model.

Bone Positions
There are several ways to hook an effect to a model. DS uses a skeletal animation system. The default target in a model is the top-level bone of that model, this is the bone that is targeted if you use the #TARGET macro. You can also hook effects to specific bones in a model. Since models might not have any bone names in common it's a good idea to only use these bones in effects that go on a specific monster or group of monsters that you know for sure will have those bones. To allow people to name bones what ever they want we cam up with a bone translator, this maps a bone from a model to a common name that you can use from within a SiegeFX script. The bone translator names are kill_bone, weapon_bone, shield_bone, body_mid, body_anterior, and body_posterior. To attach to a bone or a bone_translator bone you can use the sfx attach_point command.

Command: sfx attach_point

Format: sfx attach_point <Effect ID> <Bone> <Effect target>;

Arguments:
Effect ID: Effect the we want to move a target on.
Bone: Either a Model Bone or a Bone translator bone, bone translator bone names are prefixed by a @ symbol.
Example 9: Creates a line of fire. We target it to the source, then move the attach point to the specified bone translator bones. Note the @ symbol before the bone translator bone name.

   sfx create fire #TARGET "line()color0(1,.65,.65)";
   sfx target #PEEK source;
   sfx attach_point #PEEK @weapon_bone source;
   sfx attach_point #PEEK @body_anterior target;
   sfx start #POP;
Example 10: Create a line of fire between the left foot of the source and the left clavicle of the target using the bones in the models.
   sfx create fire #TARGET "line()color0(1,.65,.65)";
   sfx target #PEEK source;
   sfx attach_point #PEEK Bip01_L_foot source;
   sfx attach_point #PEEK Bip01_L_Clavicle target;
   sfx start #POP;
Freezing Effect Positions
By default, effects will derive their position from their target. When the target moves then the effect will move with it. If the effect is of a type that spawns particles then new particles will be spawned at the target as it moves. Sometimes you want to have an effect start at a location, but then stay there even if the target moves. To do this you use the sfx freeze_targets command.

Command: sfx freeze_targets

Format: sfx freeze_targets <effect ID>;

Arguments:
Effect ID: Effect ID to freeze in place.
Example 11: Create a greenish yellow fire that will stay still even if the target moves.
   sfx create fire #TARGET_KB "color0(.45,.75,.15)";
   sfx freeze_targets #PEEK;
   sfx start #POP;
Playing Effects on the Terrain
Since all effects are based on Gos it's sometimes difficult to get effects to play correctly with the terrain. To get an effect to sit on the terrain you can use the sfx snap_to_ground command. This command functions like freeze_targets except that the target moves vertically down to the ground.

Command: sfx snap_to_ground

Format: sfx snap_to_ground <effect ID>;

Arguments:
Effect ID: Effect ID to snap to the ground.
Example 12: Makes a light blue cylinder that rests on the ground. The effect will not move with the target.
   sfx create cylinder #TARGET_KB "color0(.25,.25,.45)";
   sfx snap_to_ground #PEEK;
   sfx start #POP;
Positioning Trackballs
The trackball effect has one target, but it still has a start position and an end position. You need to tell the trackball where to come from. You can set the initial position of a trackball to a bone using the sfx position_at command. The trackball will then move towards it's target as its parameters describe.

Command: sfx position_at

Format: sfx position_at <Effect ID> <Bone Name> <Script Target>;

Arguments:
Effect ID: Effect ID to move.
Bone Name: Bone to move the effect to.
Script Target: Script target to move the effect to. This can be either target or source.
Example 13: Creates an orange trackball at the weapon bone of the source. The trackball will the move towards the target. Note the use of the sfx friendly command. You will want to make all of your trackball attacks friendly to the party. This command prevents the effect from colliding with other party members, or the caster. For more information about trackballs, see "Doing Damage Through Effects".
   sfx create trackball #TARGET_KB "color0(1,.3,0)";
   sfx position_at #PEEK @weapon_bone source;
   sfx friendly party #PEEK;
   sfx start #POP;
Timing Effects
<Back to Table of Contents>

Part of making effects is choreographing when the different effects will start and stop. There are a few different methods for timing effects. You can accomplish this by using the pause command to halt the execution of a script for a set amount of time, or by using delays on the effects themselves. You can also set the duration of a particular effect.

Pausing Scripts
Pause will cause the script to stop executing until the specified time has elapsed. Effects before a pause will function normally, but the parts after the pause won't be run until after the pause has finished.

Command: Pause

Format: Pause <Time>;

Arguments:
Time: Time to halt the execution of the effect script.
Example 14: This will create two orbiters around the target. The first orbiter will continue to run while the script is paused. The second orbiter will be created 1.57 seconds after the first. This is the amount of time it takes an orbiter with default values to make half a revolution.
   sfx create orbiter #TARGET "color0(1,.8,.8)";
   sfx start #POP;

   pause 1.57;

   sfx create orbiter #TARGET "color0(.8,.8,1)";
   sfx start #POP;
Delaying Effects
Another way to time effects is to put delays in the effects themselves instead of pausing the whole script. Delays will result in more accurate timing. These are actually a parameter of the effect, and must go inside an effect parameter list. Delays can't be run as a stand-alone command.

Command: Delay

Format: Delay(<Time>)

Arguments:
Time: Amount of time to delay before starting this specific effect.
Example 15: Creates two orbiters, one is delayed so it won't start until after the first. Note the delay (1.57) in the second parameter list.
   sfx create orbiter #TARGET "color0(1,.8,.8)";
   sfx start #POP;

   sfx create orbiter #TARGET "delay(1.57)color0(.8,.8,1)";
   sfx start #POP;
Setting the Duration of Effects
The Dur command can be used to set the duration of a particle effect. You set the duration like you set a delay in an effect, as part of the parameter list.

Parameter: Dur

Format: Dur(<time>)

Arguments:
Time: Duration of the effect in seconds. Most effects without a specified duration will last until they are stopped.
Example 16: Creates two orbiters that will fade out after the make one revolution together.
   sfx create orbiter #TARGET "color0(1,.8,.8)dur(4.71)";
   sfx start #POP;

   sfx create orbiter #TARGET "delay(1.57)color0(.8,.8,1)dur(3.14)";
   sfx start #POP;
Stopping Effects
<Back to Table of Contents>

Once you have created an effect you might want to stop it after something happens. To do this you can use the sfx finish command, or the sfx stop command. Finishing an effect normally looks better, but not all effects can be finished. Stop works on everything, but can cause a pop when an effect disappears. If you know how long an effect needs to play for then it's usually better to use a dur parameter then using stop or finish.

Command:
Sfx Stop
Sfx Finish
Format:
Sfx stop <effect ID>;
Sfx finish <effect ID>;
Arguments:
Effect ID: Effect ID to finish or stop.
Example 17: Creates a flurry of particles around the target, then stops it after pausing for two seconds.

   sfx create flurry #TARGET_KB "color0(.25,.25,.45)";
   sfx freeze_targets #PEEK;
   set $flur #PEEK;
   sfx start #POP;

   pause 2;

   sfx stop $flur;
Camera Effects
<Back to Table of Contents>

Effect scripts consist of more then just particle effects. You can start camera effects from effect scripts. We have two types of camera effects:

  • Camera_stomp - Shake starts at max displacement, then rapidly falls off.
  • Camera_shake - More consistent shake effect.
Command: camerashake

Format: camerashake <Camera Effect> s<<Property List>>;

Arguments:
Camera Effect: Tells which type of camera effect to use. Can be either camera_stomp or camera_shake.
Property List: List of properties that describe how the camera effect works. The properties are listed in a continuous string and are separated by the & character. The entire list is enclosed in S< >. This tells the SiegeFX system that the list should be treated as a string.
Example 18: Creates a camera stomp. This will be centered around the target of the effect.
   camerashake camera_stomp <frequency=40&magnitude_x=0.0
   &magnitude_y=0.6&magnitude_z=0.0&duration=1
   &epicenter=#TARGET_GOID>;
Sounds
<Back to Table of Contents>

The visual part of an effect is only half the effect. Most effects also have associated sounds. Luckily you can play sounds from an effect script, making it easy to sync the sound and effect. To start a sound you use the sound play command. Starting an effect will push an ID for that sound onto the stack that you can use to stop the sound later. You can stop a sound with the sound stop command. Sound falloff is at full volume from zero distance to the min distance of the sound. After that it starts to fall off till the max distance.

Command: sound play

Format: sound play < Filename > [ at < Target Position > [ dist <min max> ] ][ loop ];

Arguments:
filename: Name of the sound file to play.
Target Position: Position to center the sound around. Sound will fall off the further a character is from this position. Optional argument.
Min: minimum falloff distance. Optional argument.
Max: maximum falloff distance. Optional argument.
Loop: if loop is specified then the sound will loop when it gets to the end. Best used with sounds that are made to loop. Optional argument.
Stack Use: Creates a sound ID and places it on the stack. Sound IDs can be used to stop effects later in the script.

And the sound stop command works like this.

Command: sound stop

Format: sound stop < Sound ID >;

Arguments:
Sound ID: ID of the sound sample to stop.
Example 19: Plays a looping burning sound. The Sound ID is put on the stack when we play the sound. The ID is stored in the $burning variable which is used to stop it later.
   sound play s_e_burning_large at #TARGET_POSITION dist 3 30 loop;
   set $burning #POP;

   pause 2;

   sound stop $burning;
Doing Damage Through Effects
<Back to Table of Contents>

Besides looking pretty effects in DS can also do damage. The spell fireshot is actually a simulated projectile flying through the air. The spell won't do any damage unless the projectile hits. The effects that can do damage include: Lightning, Fire, Fireb, and trackball. If you look though the SiegeFX scripts then you won't find the properties in the spell effects. None of the effects specify the damage directly. Damage properties are all passed in as a parameter from the skrit that runs the effect. This make tuning the spell much easier since the damage that the spell does can be set in the spell's template.

Trackballs
Trackballs will automatically try to do damage with what they collide with. When a trackball hits a go that it isn't friendly to, the SiegeFX system will look at the owner GO of the effect that contains the trackball. If the owner is a spell then damage will be done accordingly. The sfx friendly command like that used in example 13 can be used to specify if a trackball will collide with the party or not. You will probably always want to use this command when using trackballs.

Fire
The Fire effects fire and fireb can be used to make a damage volume. The names are a little misleading, because you can create more effects then just fire. Since you can set the color and texture of the particles you can make effects that look like anything. Since these are mostly used for visual effects damage must be done on a per effect basis. The volume that these effects do damage in is determined by where the particles that compose the effect are located. The parameters that describe how the effect does damage are "fdamage", "damage", and "ignite". "Fdamage" describes the damage done, and "damage" or "ignite" specifies if the target can catch fire from the effect. To use "damage" or "ignite" you just place them followed by an empty set of parenthesis in the parameter list for an effect.

Parameter: fdamage

Format: fdamage(<Min>,<Max>,<Particle Fit>)

Arguments:
Min: Minimum damage per second the effect should do.
Max: Maximum damage per second the effect should do.
Particle Fit: Minimum alpha level of particle to include in the damage area calculation. Value is a floating-point number from 0 - 1. Set closer to 0 for a larger area, and closer to 1 for a smaller area.
Example 20: Creates a fire effect that does damage when run as part of a spell.
   sfx create fireb #TARGET_KB
   "color0(.2,1,.1)fdamage(2,3,.2)damage()dur(1)velocity(0,0,50)";
   sfx start #POP;
Lightning
Lightning is strung between targets, it does damage to each target in the list. Lighting damage is specified though a damage parameter.

Parameter: damage

Format: damage(<Min>,<Max>,0)

Arguments:
Min: Minimum damage done by the effect.
Max: Maximum damage done by the effect.
Note: The last argument in lightning's damage property is unused.

Example 21: Creates a damaging lightning effect that stretches from the weapon bone of the source to the kill bone of the target.
   Sfx create lightning #TARGET_KB
   "damage(10,12,0)dur(.5)";
   sfx target #PEEK source;
   sfx attach_point #PEEK @weapon_bone source;
   sfx start #POP;
LODFI
<Back to Table of Contents>

Some effects are used very frequently. Torch effects for example are all over the place. For these commonly used effects we can trim them down to make the game run faster for slower machines. There are a few ways to accomplish this. Using the #LODFI macro and using the min_count effect parameter to decrease particle counts.
#LODFI - Macro that returns a floating point value that represents where the player has slid the Object Detail bar in the UI. You can make a comparison against this value to decide whether to draw an effect or not.
The other way to speed up an effect when the object detail is lower is to decrease the number of particles that are drawn. Some effects have the min_count property that specifies how many particles draw at the lowest object detail setting.

Parameter: min_count

Format: min_count(<Particle Number>)

Arguments:
Particle Number: Number of particles that will make up the effect when the object detail slider is at the bottom. The SiegeFX system will interpolate between count and min_count depending on where the slider is.
Example 22: this is an example of a torch effect, uses the #LODFI macro and also decreases the particle counts. Note that the first fire effect will only run if the Object detail is above 0.5.

if ( #LODFI > 0.5 )
   {
      sfx create fire #TARGET
         "flamesize(1)dark()grey_tex()color0(0,0,0)
         color1(0,0,0)min_displace(-.3)instant()
         offset(0,1.5,0)scale(.525)min_count(10)count(20)";
      sfx start #POP;
   }

   sfx create fire #TARGET
      "offset(0,1.1,0)instant()scale(.525)min_count(10)count(20)
      texture(b_sfx_fireball-01)flamesize(.5)color0(.75,.35,.24)
      color1(.2,0,0)max_radius(1.45)";
   sfx start #POP;
Color and Alpha
<Back to Table of Contents>

Color in DS is composed of four different properties, there are three color components and an alpha component. Color is made up of Red, Green, and blue. These can be combined to make different colors. Alpha is a measure of transparency. A particle that is more transparent will draw less of its colors on top of what is already being drawn.

The three components to color, Red, Green and blue can be combined to create different colors.

Red + Green = yellow
Red + blue = purple
Green + Blue = Cyan
Red + Green + Blue = White

So if for example we had a red particle with RGB values 1,0,0 and we had a green particle RGB 0,1,0 then when we draw them on top of each other we would get 1,1,0, which is yellow. Some effects also have a dark blending mode. Dark effects use the color of the texture of the particle they don't blend with the color of what they are over.

Dungeon Siege looks very good in 16-bit, but effects that use alpha will show banding. It's recommended that you run in 32-bit if you are creating effects.

Running SiegeFX scripts
<Back to Table of Contents>

Now that we have talked about how to create SiegeFX we should probably talk about how to run them. SiegeFX can be run from lots of different places. Some of the most common are running effects from Skrit, from a template trigger, or from another effect. When running an effect it's important to make sure that it will work correctly in a MP game. There are a lot of special cases with MP because of the streaming world.

Running Scripts from Skrit
You can run an effect from skrit in three different ways. They all have the same arguments. The difference is how the effects will work in a MP game. Effects created with RunScript are started locally and won't be passed to other machines in a MP game. If possible it's best to have each machine create effects locally with RunScript to cut down on the amount of data sent in a MP game. SRunScript will tell all of the machines in a MP game to start an effect, if the targets are valid on each of the clients then they will run the effects, if the targets aren't loaded on a particular client when they are told to run the effect then they will just not run the effect. SRunScript is useful for short effects that need to run on all clients, attack spell effects are a good example of this. SRunMPScript works similar to SRunScript except if the targets weren't valid when an effect was run, but became valid later then the effect will get run on the targets as soon as they are valid. SRunMPScript is useful for effects that last longer, persistent effects like those associated with stat-improvement spells are a case where running effects this way is needed. Running effects will return an effect ID that can be used to stop effects later.

Commands:
SRunMPScript
SRunScript
RunScript
Format: RunScript(<Effect Name>,<Target>,<Source>,<Params>,<Owner>,<Event>);

Arguments:
Effect Name: Name of the effect script to play.
Target: Goid to use as the target of the effect script.
Source: Goid to use as the source of the effect script.
Params: String to pass to the script to use as parameters.
Owner: Goid of the owner of the effect. Mainly used to handle effects that do damage. For example, the owner of a spell effect will be the spell. If an effect projectile collides with a target then the game knows where to look to find out how much damage to do.
Event: Trigger event that caused the effect to play. This is used to determine when to automatically end scripts. Scripts started with the WE_DAMAGED event for example will stay when the actor they are targeted to dies. Effects started from other events will end if their target is killed.
After you have run an effect you may want to stop it later. There are a few different commands that you can use to stop effects. StopScript and FinishScript both stop an effect locally. FinishScript looks a little better than StopScript, but StopScript will stop an effect immediately. SStopScript will stop effects on all of the clients in a MP game. You can either stop an effect by the Effect ID, or you can stop an effect by name on a specific GO.

Commands:
SStopScript
StopScript
FinishScript
Format: StopScript(<Effect ID>);

Arguments: Effect ID: ID of the effect to stop.

Or you can stop effects using the effect script name.

Commands:
SStopScript
StopScript
Format: StopScript(<Target>,<Script Name>);

Arguments:
Target: Goid to remove the effect from.
Script Name: Effect to remove from the target.
Running Script from Templates
You can set up simple triggers in templates to run an effect when a certain event happens. The main use for this is attaching effects to environmental objects. Template triggers are hooked up in the common block of a template. Each template can have any number of separate triggers, and any number of actions per trigger. Template triggers are used to do things like start a flame effect on a torch when it enters the world, or start some spell effects when a spell cast. The targets that the script is run on are inferred from the message the effect is triggered on.

System: Template Triggers:

Format:
[common]
   {
      [template_triggers]
      {
         [*]
         {
            action* = call_sfx_script( "<Effect Script>
            [(Run Options)]"[,"<Target Bone>"][,"<Script Params>"] );
            condition* = receive_world_message("<World Event>");
         }
      }
   }
Arguments:
Effect Script: Effect to play when the trigger fires
Run Options: The game tries to determine how to how to run an effect based on the type of object and the event that an effect is run from. You can use these options to override how the effect works in a MP game. This Argument is optional, and the possible options are listed below.
Target Bone: Bone or Bone Translator Bone to use as a target for the effect script instead of the default target. Optional Argument.
Script Params: Parameters to pass to the effect script when it is run. Optional Argument.
Run options:
s - only execute this script if we're the server
g - execute script as global
l - execute script as local
x - this script is xfer-able and gets sync'd as part of the clone request

Example 24: Taken from a spell in spl_spell.gas. This trigger will start sparkle effects on a a spell when it is dropped.
   [common]
   {
      [template_triggers]
      {
         [*]
         {
            action* = call_sfx_script( "combat_spell_sparkle" );
            condition* = receive_world_message("we_dropped");
         }
      }
   }
Running Scripts from Other Scripts
You can start an effect from another script. This is useful for a couple of reasons. Since called effects use the same targets as the effects that called them, you could easily create multiple versions of an effect. You could create a base effect that takes parameters to define its color, scale, or other visual attributes, and then you could make several versions that call that base effect and pass it different parameters. Running a script from another script can also be useful for timing. When you use the call command The SiegeFX system pushes a Script ID onto the stack. You can then use the wait for command to pause the original script and do something as soon as the called script finishes.

Command: call

Format: call <Effect Script> <parameters>

Arguments:
Effect Script: Name of the Effect script to run.
Parameters: List of parameters to pass to the new effect being run. Parameters are in a list separated by spaces, each parameter must have a specified type. The order of the parameters is how they are referenced in the script being run. See the section below on how to use effect parameters.
Stack Use: Creates a script ID and places it on the stack. Script IDs can be used to wait for the termination of the called script.

Types are defined by encasing the value in <> and putting an identifier in front of it. Types that can be used in an effect script:
I = string
U = unsigned int
F = float
S = string
V = vector
P = position
Example 25: Calls a test effect script and passes it some parameters. The first parameter will be the floating-point number 3.14.The second parameter will be a string containg a parameter to set the color of an effect.
   Call test_script f<3.14> s<color0(0,1,0)>;
Using Effect Parameters
Parameters are useful for effects that need to be different depending on the situation. Attack spells for example need to do different amounts of damage based on the magic level of the caster. Using Parameters we can just pass in the damage an effect is supposed to do. Inside an effect script you can refer to parameters with the parameter number encased in []. The first parameter would be [0], the second [1], and so on. You can use the parameter anywhere you would use the data located inside of it.

Example 26: Uses the values passed in from the previous example. This will create an orbiter using the color parameter passed in then pause for the amount of time passed in then stop the orbiter.
   Sfx create orbiter #TARGET_KB "[1]";
   Set $orb #PEEK;
   Sfx start #POP;

   Pause [0];

   Sfx stop $orb;
Examples
<Back to Table of Contents>

Here are some examples of simple effects. There are some items in these examples that weren't covered in this doc, but you should be able to tell what is going on. The examples are full of comments, lines that have a // in front of them are comments and do not effect the functionality of the script.

Example 27: This effect consists of an effect projectile that leaves sparkle trails. When the projectile hits it creates an explosion of particles.
[effect_script*]
{
name	=gh_bolt;

script	=[[
   // create a trackball and point it at the killbone of the target.
   // we want a bright blue homing attack.
   sfx create trackball #TARGET_KB
      "must_update()texture(b_sfx_sparkle01)scale(1.66)
      color0(.5,.5,1)dur(6)afterlife(2)homing()damped()kdamp(.1)";
   set $trackball #POP;
   // position the shot to come from the weaponbone of the source.
   sfx position_at $trackball @weapon_bone source;
   // set the trackball not to collide with friendlies
   sfx friendly party $trackball;

   // create a fire effect. This will look like trailing sparkles.
   sfx create fire #SOURCE
      "must_update()count(20)color0(.5,.5,1)alphafade(1.5)
      velocity(0,0,0)accel(0,-2,0)flamesize(2)max_displace(0)
      texture(b_sfx_sparkle01)scale(2.2)";
   set $fire #POP;
   // attach the fire to the trackball.
   sfx rat $fire;
   sfx target $fire $trackball;
   sfx attach $trackball $fire;

   // start the trackball, since the fire is attached it
   // will start as well.
   sfx start $trackball;

   // wait until the trackball collides with something.
   // the type of collision will be pushed onto the stack
   waitfor collision $trackball #DEFAULT_TIMEOUT;

   // if we hit something then we want to play a hit effect.
   // POP the collision type off the stack and make sure we hit
   // something instead of hit our timeout.
   set $collision_type #POP;
   if ( $collision_type != #NO_COLLISION )
   {
      // make an explosion where the trackball hit.
      get collision point $trackball;
      sfx create explosion #POP
         "texture(b_sfx_sparkle01)color0(.5,.5,1)
         radius(0)dur(2)omni_dir()";
      sfx start #POP;
   }

   // get rid of the trackball.
   sfx destroy $trackball;

   // message the spell that the effect is complete so
   // it can delete itself
   worldmsg WE_SPELL_SYNC_END #OWNER_GOID #OWNER_GOID #TARGET_GOID;
   ]];
}
Example 28: This effect script makes lightning shoot from both hands of the source and hit the target in the kill bone. There are also a few explosions of particles where the effects come from, and where they hit.

[effect_script*]
{
name	=gremal_zap;

script	=[[
   // create an explosion that originates from the source
   // of the effect.
   sfx create explosion #SOURCE
      "vmin(1)vmax(2)ivel(0,1,0)rvel(1,1,1)scale_range(.13,.16,0)
      texture(b_sfx_sparkle01)color0(.5,.5,1)
      dur(.2)radius(.4)fade_range(0,1,0)count(25)omni_dir()
      scale(.66)";
   // move the explosion to the weapon bone of what
   // the effect comes from.
   sfx attach_point #PEEK @weapon_bone target;
   sfx freeze_targets #PEEK;
   sfx start #POP;

   // string a bolt of lightning from the source to the target.
   sfx create lightning #SOURCE
      "must_update()scale(.75)bolt_life(1)
      mindisplace(-.1)maxdisplace(.1)
      color0(.5,.5,1)dur(.25)";
   set $bolt #POP;
   // make the bolt come from the weapon bone.
   sfx attach_point $bolt @weapon_bone target;
   // add another target for the lightning to hit.
   // Make it the target of the script.
   sfx target $bolt target;
   // hit the target in the kill bone.
   sfx attach_point $bolt @kill_bone source;
   sfx start $bolt;

   // string a bolt of lightning from the source to the target.
   sfx create lightning #SOURCE
      "must_update()scale(.75)bolt_life(1)
      mindisplace(-.1)maxdisplace(.1)
      color0(.5,.5,1)dur(.25)";
   set $bolt2 #POP;
   // make the bolt come from the shield bone.
   sfx attach_point $bolt2 @shield_bone target;
   // string the lightning to the target of the script.
   sfx target $bolt2 target;
   // hit the target in the kill bone.
   sfx attach_point $bolt2 @kill_bone source;
   sfx start $bolt2;

   // create an explosion at the target where it is hit by the
   // lightning.
   sfx create explosion #TARGET_KB
      "vmin(2)vmax(3)ivel(0,1,0)rvel(2,1,2)scale_range(.13,.16,0)
      texture(b_sfx_sparkle01)color0(.5,.5,1)
      dur(2)radius(.4)fade_range(0,1,0)count(25)omni_dir()";
   sfx freeze_targets #PEEK;
   sfx start #POP;

   // send a message to the skrit so it knows that the effect
   // finished playing.
   worldmsg WE_SPELL_SYNC_MID #INVALID_GOID #OWNER_GOID #TARGET_GOID;
   ]];
}
Conclusion
<Back to Table of Contents>

Now you know the basics of SiegeFX, there are still some features of the language that haven't been covered, but you should be able to look at examples from the game and figure them out. Good luck making your own effects!

Appendix A: Effect Parameters
<Back to Table of Contents>

Global Effect Parameters

These are common parameters that are usable by most of all the effects.

Mnemonic | Format | Description
dur | (n) | Duration - how long the effect will live in seconds
delay | (n) | Delay - how long to pause before the effect starts
pos | (x,y,z) | Position - initial position relative to the world
dir | (x,y,z) | Direction - initial direction relative to the object
offset | (x,y,z) | Offset - positional offset relative to the model
ts | (n) | Time scalar - factor to scale time by
scale | (n) | Scale factor - factor to scale size of effect by
texture | (text) | Texture - Name of a texture file to use including the extension, if no extension is specified then it treats the name as a [tsd] block saved in a gas file for an animated texture.
sup | () | Static Update - if the effect doesn't move, specify this parameter
abs | () | Absolute coordinates - effect is not scaled to match targets size
use_wind | () | Effect uses wind information to change motion
world_orient | () | Effect orients to the orientation of the world
bone_orient | () | Effect orients to the bone of the target it is attached to

Base effect parameters

Charge - Random spherically spawned particles coalesce to a larger version of themselves.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Vertex color | (0.0,0.0,1.0)
radius | (f) | Set the radius of the area of effect | (1.00)
count | (n) | Number of particles | (16)
tout | (f) | Transition out time in seconds | (1.0)
speed0 | (f) | Random acceleration factor for a particle | (1.0)
centersize | (f) | Maximum size factor for center particle | (0.75)
ialpha | (f) | Particle alpha increment amount to fade by | (4.00)

Curve - Generates a random curve.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Vertex color | (0.235,0.24,1.0)
size | (f) | Size of a single particle in the curve | (0.125)
step | (f) | Detail level of the curve | (0.25)
curvature | (f) | The higher the less straight | (1)
model | (n) | Curve behavior 0,1, or 2 | (0)
tlength | (f) | Trail length | (2.0)
velocity | (f) | The speed at which the curve is drawn | (30.0)
spacing | (f) | The space between each particle | (3.0)

Cylinder - Texturable polygonal cylinder for creating tubes, funnels, and rings/coronas.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Vertex color | (1.0,1.0,1.0)
segments | (f) | Number of sides cylinder will be made of | (16.0)
rp0 | (s,e,i) | Starting radius, ending radius, increment | (0.5, 0.5, 0.0)
rp1 | (s,e,i) | Starting radius, ending radius, increment | (0.5, 0.5, 0.0)
hp0 | (s,e,i) | Starting height, ending height, increment | (2.5, 2.5, 0.0)
hp1 | (s,e,i) | Starting height, ending height, increment | (0.0, 0.0, 0.0)
spin | (f) | How fast to spin the cylinder | (0)
alpha | (f) | Starting alpha value to use | (0.5)
tin | (f) | Transition in time in seconds | (0.5)
tout | (f) | Transition out time in seconds | (0.5)
dark | () | Uses an alternate blending mode to look opaque |
rotate | (x,y,z) | Amount to rotate the cylinder by in degrees | (0.0, 0.0, 0.0)
irotate | (x,y,z) | Amount to increment rotation by in degrees | (0.0, 0.0, 0.0)

Decal - Creates a decal.

Mnemonic | Format | Description | Default
name | (text) | The name of the texture including the extension | (b_d_blood.psd)
tin | (f) | Set the alpha in duration | (1.0)
tout | (f) | Set the alpha out duration | (1.0)
near | (f) | Set the near plane of the decal projection | (0.1)
far | (f) | Set the far plane of the decal projection | (1.1)
alpha | (f) | Limits the maximum alpha value to this amount | (1.0)
save_texture | () | After decal is gone it destroys the texture |

DiscPile - Ugly pile of discs… was supposed to be used for showing coins, textureless.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Vertex color | (0.0,1.0,0.0)
radius | (f) | radius of the area of effect | (0.45)
discradius | (f) | radius of the disc | (0.025)
thickness | (f) | disc thickness factor | (0.1125)
count | (n) | Number of discs | (100)
sides | (n) | Number of sides a single disc has | (10)
minvel | (x,y,z) | Minimum initial velocity | (-.75,1,-.75)
maxvel | (x,y,z) | Maximum initial velocity | (.75,5,.75)

Explosion - Particles start out in a random circular radius and explode in a set direction, fade out and bounce off the ground.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Vertex color | (0.6,0.7,1.0)
color1 | (r,g,b) | Particle variance color | (0, 0, 0)
color2 | (r,g,b) | Splat color | (1.0, 0.1, 0.1)
rebound | (n) | How elastic each particle is for bouncing | (0.85)
fade_range | (s,e,0) | Set the range of the fading start to end | (0.5,1.0,0.0)
count | (n) | How many particles to use | (32)
dark | () | Uses an alternate blending mode to look darker |
radius | (f) | Spawn radius factor | (0.5)
line | () | Uses target and source targets to generate a line to spawn particles from. |
vmin | (f) | Minimum initial particle velocity scalar | (3.0)
vmax | (f) | Maximum initial particle velocity scalar | (6.5)
ivel | (x,y,z) | Initial velocity vector | (0, 0, 0)
rvel | (x,y,z) | Initial random velocity vector | (0.25, 0.25, 0.25)
min_theta | (f) | Theta range min - determines spawn point | (0)
max_theta | (f) | Theta range max - determines spawn point | (3.1415)
min_phi | (f) | Phi range min - for omni-directional mode | (0)
max_phi | (f) | Phi range max - for omni-directional mode | (6.283185)
cfreq | (f) | Collision frequency: 1 = 100% of particles | (1.0)
collide | () | Causes particles bounce off of terrain polygons |
omni_dir | () | Creates an omni-directional explosion |
no_noise | () | Specifies global texture without noise |
rand_scale | () | Specifies to use random scale per particle |
splat | () | Causes particles to "splat" onto terrain polygon |
splat_life | (f) | Duration a splat exists before it fades away | (2.0)
splat_fade | (f) | Rate at which a splat fades away after expiring | (0.25)
scale_range | (s,e,0) | Random range of particle size from s to e | (0.2, 0.7, 0)
grey_tex | () | Default texture is red, this is greyscale |
srate | (f) | Spawn rate - duration in seconds | (0)
stexture | (text) | Texture - Name of a texture file to use including the extension, to use for the splat texture |
alphastart | (f) | Alpha starting value for a splat | (1.0)
splatscaleup | (f) | Alpha starting value for a splat | (2.275)
splatskip | (n) | Number of splats to skip before doing a splat | (4)

Fire - Particles randomly appear within a circular radius and accelerate as they fade out and scale down.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Vertex color | (0.8,0.5,0.0)
color1 | (r,g,b) | Particle variance color | (0.15,0.0875,0.0)
instant | () | Fire instantly has volume instead of burning slowly |
velocity | (x,y,z) | Initial flame velocity before acceleration | (0.0,8.0,0.0)
accel | (x,y,z) | Constant of acceleration | (0.0,14.0,0.0)
flamesize | (f) | Size of individual flame relative to effect | (1.75)
fctrl | (min,max,i) | Controls for how a flame expands over time | (-1.25, 1.75, 0.0)
alphafade | (f) | How fast to fade flame out | (0.85)
count | (n) | The maximum number of particles to use | (30)
min_count | (n) | The minimum number of particles to use | (30)
min_radius | (f) | Inner spawn radius | (0.0)
max_radius | (f) | Outer spawn radius | (4.0)
min_iradius | (f) | Amount to increment inner spawn radius | (0.0)
max_iradius | (f) | Amount to increment outer spawn radius | (0.0)
radius_rmin | (f) | Range for min radius to not exceed from incrementation | (0.0)
radius_rmax | (f) | Range for max radius to not exceed from incrementation | (4.0)
radius | (n) | Radius additive - absolute scale | (0.0)
cheap | () | Uses cheaper particle motion calculations |
line | () | Uses target and source targets to draw a line of fire through. |
rect | () | Uses object and subject targets to define a rectangular area to spawn the particles from. |
fdamage | (min,max,f) | Flame damage min,max, alpha fit filter - fit >= 0.2 | (3.0,7.0,0.2)
ignite | () | Fire will do damage determined by fdamage and possibly cause victim to catch fire |
damage | () | Flame particles will do damage to the victim but the victim will not catch fire |
min_displace | (n) | Random y displacement range min | (0.0)
max_displace | (n) | Random y displacement range max | (1.25)
yorient | () | Orients fire to y-axis only |
sinspeed | (f) | Amount to increment max_radius by using sine | (0.0)
sinpos | (f) | Starting angle in radians | (0.0)
linespeed | (f) | Amount to increment linepos by | (0.0)
linepos | (f) | Starting point on line from 0-1 | (0.0)

FireB - Similar to regular fire but uses a conical volume specified by four radii to accelerate through for simulating fire Breathing/throwing.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Vertex color | (0.8,0.5,0.0)
velocity | (x,y,z) | Initial flame velocity before acceleration | (0.0,8.0,0.0)
accel | (x,y,z) | Constant of acceleration | (0.0,14.0,0.0)
flamesize | (f) | Size of individual flame relative to effect | (1.75)
fctrl | (min,max,i) | Controls for how a flame expands over time | (-1.25, 1.25, 0.035)
alphafade | (f) | How fast to fade flame out | (0.85)
count | (n) | The maximum number of particles to use | (90)
min_count | (n) | The minimum number of particles to use | (90)
cheap | () | Uses cheaper particle motion calculations |
flamesize | (f) | Size of individual flame relative to effect | (1.75)
dark | () | Uses an alternate blending mode to look smoky |
fdamage | (min,max,f) | Flame damage min,max, alpha fit filter - fit >= 0.2 | (3.0,7.0,0.2)
obb_freq | (f) | How often to generate obb | (0.1)
ignite | () | Fire will do damage determined by fdamage and possibly cause victim to catch fire |
damage | () | Flame particles will do damage to the victim but the victim will not catch fire |
cvel | () | Turns off initial random velocity generation |
velocity_s | (f) | Velocity scalar | (1.0)
lower_r0 | (f) | Inner cone radius nearest to spawn point | (0.5)
lower_r1 | (f) | Outer cone radius nearest to spawn point | (1.25)
upper_r0 | (f) | Inner cone radius furthest from spawn point | (1.0)
upper_r1 | (f) | Outer cone radius furthest from spawn point | (3.25)
displace_min | (f) | Min distance from r0 to r1 to determine cone | (0)
displace_max | (f) | Max distance from r0 to r1 to determine cone | (0)
start_rate | (f) | How quickly to start burning | (27.5)
end_rate | (f) | How quickly to stop burning | (16.5)
light_spawn | () | Mixes in light sources with particles |
light_freq | (f) | How many particles before light appears | (50)
light_radius | (f) | Radius to use for a lightsource particle | (1)

Flurry - A bunch of particles moving in spherical polar coordinates with sinusoidal interference.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Vertex color | (0.0,0.0,1.0)
radius | (f) | Set the radius of the area of effect | (1.0)
tin | (f) | Transition in time in seconds | (1.0)
tout | (f) | Transition out time in seconds | (1.0)
count | (n) | Number of particles | (50)
iphi | (f) | Latitude angle increment | (1.0)
itheta | (f) | Longitude angle increment | (1.0)
iamp | (f) | Sinusoidal interference amplitude speed | (1.0)
amplitude | (f) | Sinusoidal interference amplitude factor | (1.0)
grow_params | (s,m,f) | Scaling factors at t for start, mid and end | (1.0,1.0,1.0)

Lightsource - Dynamic light source.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Light color | (0.5,0.5,0.5)
color1 | (r,g,b) | Variance color | (0.5,0.5,0.5)
iradius | (f) | Inner falloff radius | (0)
radius | (f) | Outer falloff radius | (5.0)
tin | (f) | Transition in time in seconds | (1.0)
tout | (f) | Transition out time in seconds | (1.0)
frequency | (f) | Flicker frequency | (0.025)
draw_shadow | () | Draw a shadow or not | FALSE

Lightning - Simple fractal generated lightning/electricity effect that can be strung through numerous targets.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Vertex color | (0.6,0.7,1.0)
damage | (min,max,0) | Damage range to apply if specified | (5.0,10.0, 0)
bolt_life | (f) | How long a bolt lasts before fading out | (0.4)
subd | (f) | Level of bolt subdivision | (0.4)
minsubd | (f) | Minimum level of subdivision | (2.0)
mindisplace | (f) | Minimum distance to stray from center | (0.100)
maxdisplace | (f) | Maximum distance to stray from center | (0.165)
check_ground | () | Prevents point generation below ground |

LineTracer - leaves a textureless tracer behind as the targets move though space.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | point a color | (1.0,1.0,1.0)
color1 | (r,g,b) | point b color | (1.0,1.0,1.0)
fade_rate | (f) | How fast to fade the tracer after reaching target | (0.10)
tin | (f) | Transition in time in seconds | (0.5)
tout | (f) | Transition out time in seconds | (0.5)
lock | () | Locks to a third reference target |

Point a and point b are the two points that make up a segment of a trace effect one being the outer point and one the inner.

The reference target is the third target that must be added to the linetracers target list last in order to preserve the target order and the lock() parameter must be specified.

Orbiter - One single particle that orbits a target using spherical polar coordinates.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Vertex color | (0.6,0.7,1.0)
radius | (f) | Radius of the effect | (1.0)
theta | (f) | Longitude angle | (0)
itheta | (f) | Longitude angle increment | (0)
phi | (f) | Latitude angle | (0)
iphi | (f) | Latitude angle increment | (2.0)
tin | (f) | Transition in time in seconds | (1.0)
tout | (f) | Transition out time in seconds | (1.0)
scale | (f) | scale of the particle | (1.0)
vdisplace | (f) | additive vertical displacer value | (0)
radiusi | (f) | additive radial increment - makes spirals | (0)
invisible | () | doesn't draw anything - use this for tracking |
model | (text) | Template name of model to use for orbit |
model_sin | (f) | Scale in duration | (1.0)
model_sout | (f) | Scale out duration | (1.0)
model_smax | (f) | Scale scalar | (1.0)
rot_inc | (x,y,z) | Model rotation incrementation amounts | (0.0, 0.0, 0.0)
smooth | () | Base position on average of recorded positions |
shistory | (f) | History depth | (16)
free | () | Uses own orientation instead of targets |

PointTracer - Pre-calculated trajectory tracer traces a single point using a line, textureless.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Vertex color | (1.0,1.0,1.0)
fade_rate | (f) | How fast to fade the tracer after reaching target | (2.0)
decay_rate | (f) | Controls tail length - smaller makes longer | (0.08)
velocity | (f) | Velocity of the tracer - matches what its tracing | (17.0)
frequency | (f) | *Controls the level of detail | (0.01)
period | (f) | *Controls the level of detail | (3.0)

* Period/frequency determines the number of points sampled. If this value is greater than 400 the trajectory will truncate. The period is equal to the number of seconds the trajectory is in flight.

PolygonalExplosion - A bunch of textureless n-sided polygons start from a flat plane and explode out while rotating and stick into the ground, textureless.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Vertex color | (0.2,0.2,0.85)
fade_range | (s,e,0) | Set the range of the fading start to end | (0.5,1.0,0.0)
poly_sides | (n) | Set the max random sides for an n-sided poly | (9)
count | (n) | Set the maximum number of polygons used | (200)
radius | (f) | Set the radius of the area of effect | (0.75)
mag | (f) | Set the magnitude of the explosion | (1.0)
rotrange | (x,y,z) | Sets the rotation range for a particle | (200.0,200.0,200.0)
displace | (x,y,z) | Sets the displacement range from the origin | (0, 0, 0)

Sparkles - Random spherically spawned particles alpha in and then out while never moving from the spawn point.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Vertex color | (0.0,1.0,0.0)
radius | (f) | Set the radius of the area of effect | (1.00)
count | (n) | Number of particles | (60)
psize | (f) | Particle size scalar | (1.0)
yvel | (f) | Velocity of particle in Y | (0)

SPE - Abbreviation for spatiotemporal pattern effect, I didn't really know what to call this one. Probably the most parameterizable effect out of all of them. Default characteristics make it look like a ring, which moves up and down.

It is possible to generate a stupidly huge number of different looking effects from this effect using all the different parameters - however you need to understand how it works in order to control it. I will now make a feeble attempt to explain how it works - sorry about the trig but I don't really know another way to explain it.

The calculation of a position for a single particle is determined by the function below and is done separately for each of the positions x, y, and z components as follows:

Position = (sin(index0) + sin(index1)) / 2

So all this really does is just average the result of the sine of the two index values. If you want to simplify this further then just make both of the indices the same and it would mean that the position calculation would be:

Position = sin(index0)

(It doesn't matter which index value we use since index0 = index1)

How the actual movement of a particle occurs is the result of adding a value to the index, which is done by adding the values you specify in the speed0 and speed1 parameters. The larger the value the bigger the change and the faster the particle moves.

The last parameters relating to the pattern generation are space parameters space0 and space1. They do just what they imply, they space the particles apart from each other. The larger the value the greater the distance is between each particle but, since we are basing all of our calculations off of sine we know the periodicity and can always calculate what would cause a group of particles to connect back up by dividing 2*PI by count which equals 0.09817477 for 64 particles - the default settings.

If you look at the default settings for index, speed, and space now it should make a bit more sense why the default behavior for the SPE effect produces a ring that moves up and down because if you look at the default settings for speed you will notice that it only adds to 1.0 to Y and not X or Z and why the ring moves just like a sine. Looking at the values for index0 and index1 they are both the same value of 1.57 which is roughly PI/2 or 90 degrees which is what cosine is: sin( PI/2 + angle ) = cos( angle ).

This is why the default pattern is circular since the parametric equation of a circle is:

x = cos(angle) y = sin(angle)

If you look at the spacing the values they are (0.098, 0.0, 0.098) which specifies that spacing only occurs for x and z and why it is drawn in the XZ plane.

If you wanted the circle to be in another plane such as the XY plane you just transpose z with y for the indexes and spacing and get (0.0,1.57,0.0) and (0.098,0.098,0) respectively. Moving it left to right instead of up and down is done by changing both speed values to (1.0,0,0)

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Vertex color | (0.6,0.7,1.0)
radius | (f) | Radius of the effect | (1.0)
scale | (f) | Scale of each particle | (0.12)
index0 | (x,y,z) | First sinusoidal index for x, y, and z | (0.0,0.0,1.57)
index1 | (x,y,z) | Second sinusoidal index for x, y, and z | (0.0,0.0,1.57)
speed0 | (x,y,z) | First speed value for x, y, and z | (0.0,1.0,0.0)
speed1 | (x,y,z) | Second speed value for x, y, and z | (0.0,1.0,0.0)
space0 | (x,y,z) | First space value for x, y, and z | (0.098,0.0,0.098)
space1 | (x,y,z) | Second space value for x, y, and z | (0.098,0.0,0.098)
count | (n) | Number of particles | (64)
tin | (f) | Transition in time in seconds | (1.0)
tout | (f) | Transition out time in seconds | (1.0)

Spawn - Object spawning effect.

Mnemonic | Format | Description | Default
model | (text) | Template name of model to use for spawning | (frag_glb_bone_06)
spawn_int | (f) | Spawn interval | (0.5)
spawn_count | (f) | Spawn count | (1)
count | (f) | Max objects to spawn | (16.0)
sr_min | (f) | Starting radius -minimum | (0.5)
sr_max | (f) | Starting radius -maximum | (1.0)
er_min | (f) | Ending radius - minimum | (1.0)
er_max | (f) | Ending radius - maximum | (1.5)
lvel | (x,y,z) | Linear velocity base | (0.0, 8.0, 0.0)
lvel_var | (x,y,z) | Linear velocity variance | (0.0, 2.0, 0.0)
lacc | (x,y,z) | Linear acceleration base | (0.0, 0.0, 0.0)
lacc_var | (x,y,z) | Linear acceleration variance | (0.0, 0.0, 0.0)
avel | (x,y,z) | Angular velocity base | (0.0, 0.0, 4.0)
avel_var | (x,y,z) | Angular velocity variance | (0.0, 0.0, 5.0)
aacc | (x,y,z) | Angular acceleration base | (0.0, 0.0, 0.0)
aacc_var | (x,y,z) | Angular acceleration variance | (0.0, 0.0, 0.0)

Sphere - Textureless tessellated translucent sphere.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Vertex color | (0.25,0.0,0.5)
color1 | (r,g,b) | Line color | (0.5,0.5,0.5)
subd | (n) | Level of subdivision | (1)
grow_params | (s,m,e) | Scaling factors at t for start, mid and end | (1.0,1.0,1.0)
lines | () | Draws polygonal lines |
cull | () | Backface cull |
rotate | (x,y,z) | rotational offset relative to the model | (0.0,0.0,0.0)
irotate | (x,y,z) | rotational offset increment | (0.0,0.0,0.0)
sides | (n) | Number of side segments to draw | (20)
tin | (f) | Transition in time in seconds | (1.0)
tout | (f) | Transition out time in seconds | (1.0)

SRay - Spinning Ray - multiple spinning rays originate from a point on a sphere going straight out.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Starting color | (0.9,0.9,1.0)
color1 | (r,g,b) | Ending color | (0.2,0.2,1.0)
color2 | (r,g,b) | Randomness - color variance to apply to both ends | (0.0,0.0,0.0)
radius | (f) | Radius of sphere to originate rays from | (0.0005)
count | (n) | Number of rays to draw | (16)
lmin | (f) | Minimum length of a ray | (10.0)
lmax | (f) | Maximum length of a ray | (10.0)
wsmin | (f) | Minimum start width of ray | (0.15)
wsmax | (f) | Maximum start width of ray | (0.15)
wemin | (f) | Minimum end width of ray | (0.15)
wemax | (f) | Maximum end width of ray | (0.15)
theta | (f,f,f) | Start, min increment, max increment | (0.0,1.0,3.0)
phi | (f,f,f) | Start, min increment, max increment | (0.0,1.0,-3.0)
alpha | (f,f,f) | Start, fade min, fade max | (1.0,0.5,0.5)
srate | (f) | Spawn rate - period before next ray is spawned | (0.015)

Steam - Particles randomly appear within a circular radius and accelerate upwards as they fade out and scale up.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Vertex color | (0.6,0.7,1.0)
dark | () | Uses an alternate blending mode to look smoky |
velocity | (x,y,z) | Initial wisp velocity | (0.0,5.75,0.0)
accel | (x,y,z) | Acceleration constant | (0.0,4.0,0.0)
alphafade | (f) | Factor for fading out a wisp | (0.55)
alphastart | (f) | Alpha value to start wisp texture at | (1.0)
wispsize | (f) | Factor for scaling an individual wisp | (2.25)
instant | () | Steam instantly has volume |
count | (n) | The maximum number of particles to use | (96)
min_count | (n) | The minimum number of particles to use | (96)
cheap | () | Uses cheaper particle motion calculations |
line | () | Uses object and subject targets to draw a line of fire through. |
rect | () | Uses object and subject targets to define a rectangular area to spawn the particles from. |

Trackball - A single particle which moves in a straight line or follows a target.

Mnemonic | Format | Description | Default
color0 | (r,g,b) | Vertex color | (1.0,0.0,0.0)
radius | (f) | Radius of spiral interference | (0.0)
spin | (f) | Spiraling spin speed | (0.0)
accel | (f) | Rate of acceleration | (2.5)
velocity | (f) | Initial velocity | (5.0)
rvel_min | (f) | Random velocity min | (0.0)
rvel_max | (f) | Random velocity max | (0.0)
max_velocity | (f) | Maximum velocity | (80.0)
kdamp | (f) | Coefficient of damping 0.0 - 1.0 | (0.45)
damped | () | Limits how sharp a turn can be |
homing | () | Adjusts to hit target |
theta | (f) | Radian angle to start spinning from | *random
afterlife | (f) | Number of seconds to exist after a collision | (0.0f)
hit_target_only | () | Checks for collision with intended target only | (false)
invisible | () | Effect is not drawn |
no_collide | () | Does not perform a collision check |
no_collide_objects | () | Does not perform collision checks with objects |

Appendix B: Camera Command Parameters
<Back to Table of Contents>

Camera quake - Shakes the camera like Santa's belly.

Property | Description | Default
magnitude_x | Magnitude of x quake | 1.0
magnitude_y | Magnitude of y quake | 1.0
magnitude_z | Magnitude of z quake | 1.0
duration | Duration for effect to last in seconds (0 for infinite) | 1.0
camera_space | Shake in camera space, otherwise shake in object (node) space | false
self_destruct | Self-destruct on algorithm completion | false
epicenter | Goid of the object causing the quake |
falloff | How quickly the quake starts to fall off in magnitude | 12.0

Camera stomp - Shakes the camera. Sharp shake with quick falloff.

Property | Description | Default
magnitude_x | Magnitude of x stomp | 0.0
magnitude_y | Magnitude of y stomp | 1.0
magnitude_z | Magnitude of z stomp | 0.0
duration | Duration for effect to last in seconds (0 for infinite) | 0.3
frequency | Frequency of sine wave to apply to stomp per effect run | 3.0
camera_space | Shake in camera space, otherwise shake in object (node) space | false
self_destruct | Self-destruct on algorithm completion | false
epicenter | Goid of the object causing the stomp |
falloff | How quickly the quake starts to fall off in magnitude | 12.0

Appendix C: Bones in the Farm Girl Model
<Back to Table of Contents>

The top bone in a biped is the Bip01_Pelvis bone. If you don't specify a different attach point then effects will start on the top bone in a model.

Bip01_Pelvis
Bip01_L_Thigh
Bip01_L_Calf
Bip01_L_Foot
Bip01_L_Toe0
leftfoot_pin
Bip01_R_Thigh
Bip01_R_Calf
Bip01_R_Foot
Bip01_R_Toe0
rightfoot_pin
Bip01_Spine
Bip01_Spine1
Bip01_Spine2
Bip01_Neck
Bip01_Head
Bip01_Ponytail1
Bip01_Ponytail11
Bip01_Ponytail12
Bip01_Ponytail13
Bip01_L_Clavicle
Bip01_L_UpperArm
Bip01_L_Forearm
Bip01_L_Hand
Bip01_L_Finger0
Bip01_L_Finger01
Bip01_L_Finger1
Bip01_L_Finger11
shield_grip
Bip01_R_Clavicle
Bip01_R_UpperArm
Bip01_R_Forearm
Bip01_R_Hand
Bip01_R_Finger0
Bip01_R_Finger01
Bip01_R_Finger1
Bip01_R_Finger11
weapon_grip

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_213 DS Resource System

Dungeon Siege Resource System

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

What This Tutorial Will Teach You

All About Tanks

What Are Tank Files?

How Tank Files Are Made

Important Notes

What "bits" Are And How Dungeon Siege Tool Kit (DSTK) Tools Use Them

Important Notes

Default Tank Files

How To Build A Tank And Use It

Create A "Map" Tank

Build A "Mod"

"Mod" Recipe

Retrieve Files Out Of Tanks

How The Resources Are Combined To Form The Unified File Table

Conclusion

Introduction

The "Resource System" is comprised of files that contain the code for loading all of the resources that Dungeon Siege and the tools in the Tool Kit need to operate, such as textures, models, and .gas files.

Resources can be stored as:
  • Individual files on the hard drive, which we call "bits"
  • In a compressed, optimized meta-file called a "tank". Tank files are similar in concept to .zip files generated by WinZip™. Modifying Dungeon Siege, which includes map creation, always involves creating at least one tank file.
What You Need For This Tutorial
  • Dungeon Siege (of course)
  • Siege Editor
  • Siege Editor Manual (comes bundled with Siege Editor)
  • Siege U: 100 (optional)
What This Tutorial Assumes You Have Already Learned
  • Siege U: 100
  • Siege U: 200 - Concepts and Terminology
  • Siege U: 203A - Triggers I
What This Tutorial Will Teach You
  • About tanks: What are tanks? How are they made? What are they for?
  • About "bits"
  • Default DS tanks
  • How to build and use a tank
  • How to retrieve files out of tank
  • Combining resources

All About Tanks
<Back to Table of Contents>

What Are Tank Files?
Tank files are compressed, optimized meta-files that are used to store resources for Dungeon Siege so that they can be located and loaded more efficiently by the engine. They are similar in concept to .zip files generated by WinZip™. Modifying Dungeon Siege, which includes map creation, always involves creating at least one tank file from individual files called "bits". Tanks are analogous to Doom's™ ".WAD" files, Unreal's™ ".U" files, or Total Annihilation's™ ".HPI" files.

There is nothing particularly special about tank files. The binary data has:
  • A header
  • A table of what the files are inside of it
  • The actual file data itself
The extension for a tank file is irrelevant, but our convention is to have .dsres files for resource files that are mounted when the game starts and .dsmap files for map files that are mounted on the fly when you choose a map. DO NOT put anything from a /world/maps directory into anything other than a .dsmap file -- it will just cause you grief. Other tanks include the .dsparty files and .dssave files, for save games.

How Tank Files Are Made
Tanks are made in Siege Editor using the "Save Map as .dsmap…" and "Save Folder as .dsmod…" functions. Note that the interface for making tanks in SE is a prototype and may be a bit quirky. A community tool called "Rapid Tank Creator" (available at http://www.game-editing.net/) also makes tanks; however, Rapid Tank Creator is not a Gas Powered Games tool and no GPG support is offered for it.

Important Notes:
  • The .dsmod files are not yet supported in the game.
  • When making tanks, rename to .dsres or .dsmap.
  • Options that are set in the tank creation dialog in SE are preserved in subsequent tank builds, which can create unexpected results. For example, if you use "Save Map as .dsmap…" and then "Save Folder as .dsmod…", the prefix /world/maps is retained in the dialog the second time around.

What "bits" Are And How Dungeon Siege Tool Kit (DSTK) Tools Use Them
<Back to Table of Contents>

"Bits" are what we call individual files that are stored on the hard drive outside of tanks (but in the same layout that the tanks have), similar to an extracted .zip file. Bits are convenient for content development because you can avoid the extra steps of building a tank and restarting the application to "see" the new resource. One of DS's main requirements was to let people develop as rapidly as possible, and one of the ways we do that is to permit dropping files right into a bits folder for immediate usage. It doesn't work with the retail game (DungeonSiege.exe), but that's what DSMod.exe is for. For fastest results, all content development should use DSMod.exe and bits overrides.

Important Notes:
  • The retail version of DS does not use bits. DSMod.exe and the other DSTK tools support both bits and tanks.
  • When bits are available and used, they will override the resources stored in tanks.
  • The default bits folder is in \My Documents\Dungeon Siege\Bits. Any file placed in there will take precedence over the files of the same name and path in any tanks that the game uses.
  • Most people who are going to play your mods will be using the retail EXE, therefore, you'll probably want to compile your work into tanks for distribution on to the Internet or to friends.

Default Tank Files
<Back to Table of Contents>

Several different tank files ship with Dungeon Siege. This section explains what they are called and what they contain.
Logic.dsres: This contains all the core logic to glue the game together. All skrits, the contentdb, the rules, the UI configuration, and anything that is not a raw resource is stored here.

Objects.dsres: This has the art resources in the game, for example object meshes, animations, and bitmaps, plus all the movies. Terrain resources are separate and are found in Terrain.dsres.

Sound.dsres: This has non-localizable sound resources, such as sound effects and music.

Terrain.dsres: This has all the art resources required for terrain, such as bitmaps, nodes, and the node index (note: the index is now obsolete as of v1.1).

Voices.dsres: Because voice samples require localization, we separated them out so the loc teams could translate all the voices easier and only have to replace one tank file. This file contains all spoken text samples.
These five files contain all the resources that are required to run DS, except for the map files.
MpWorld.dsmap: This is the multiplayer world Utraean Peninsula.

World.dsmap: This is the single player campaign Kingdom of Ehb.
The .dsmap files are swapped in and out as the system needs them. You might be wondering, "Why not mount them all simultaneously?" Well, we always try to keep memory usage at a minimum. The index required to maintain a tank file takes up space (storing all the names of those files, etc.), and only mounting the .dsmap files as needed satisfies this objective.

How To Build A Tank And Use It
<Back to Table of Contents>

Building a tank depends on what you want to accomplish. Let's use make a map first because it's the easiest thing to do.

Create a "Map" Tank
  • All you have to do to create a map tank is use the "Save Map as .dsmap…" command to save your map to a ".dsmap" file. Pretty straightforward. The game is already configured to look in the \My Documents\Dungeon Siege\Maps folder.
  • If you don't know how to create a map in Siege Editor, refer to Siege U: 100.
Build a Mod
Building a mod is more complicated due to the multiplayer CRC check we do. Let's say you override a file in the contentdb, and change a particular template. This will change the content in such a way that your game will not be compatible with others in multiplayer. That means that everyone will have to have the same .dsres file installed in order to play together, even if they are not playing your mod! So the solution is to do what we did on Yesterhaven (and what other games like Quake™ used to do with their mods). Take the mod and put it into its own subfolder of the Dungeon Siege directory and launch the game with a different shortcut that says where to find the mod. This removes the tank file from the general collection so that you can play the regular GPG-created .dsmap files with anyone, yet you can also run your mod by starting with that separate shortcut.

This all applies to the current engine. We hope to resolve this issue in the future, where we will permit re-mounting of tanks on the fly and dependency mappings to say that a particular map requires a particular mod, etc. So hang tight there!

Mod Recipe
  • .dsres file containing your "bits" directory for the modifications you've made, minus the /world/maps directory within it. This means that when you make a tank of your bits directory, you need to temporarily move the /world/maps folder out.

  • .dsmap file containing your map. This one is easy, just use the "Save Map as .dsmap…" function in Siege Editor.

  • Shortcut or .bat file to launch DS for your mod. It should launch Dungeon Siege with a command line that has "res_paths=<modname> map_paths=!<modname>" where the .dsres and .dsmap files you created are stored in a subdirectory of Dungeon Siege with the name "modname". The res_paths command line option says "add this additional path to the set of available paths to find .dsres files in". So it will look in \Dungeon Siege\Resources as well as \Dungeon Siege\<modname>. Your .dsres file's priority will cause its files to take precedence over anything else in the system. The map_paths command line option is about the same, except that the "!" in the front means "ignore the \Dungeon Siege\Maps folder". This means that only your map will be available when you run the game. This prevents the map from being chosen by accident without having the .dsres file available (which may cause a crash depending on your mod).
Retrieve Files Out of Tanks
There are 2 ways to retrieve files ("bits") out of tanks:
  1. The best way is with Dan Strandberg's TankViewer tool, available at www.game-editing.net.
  2. The other option is the Siege Editor (SE). It has an extract dialog that will let you extract the contents of a tank to your hard drive. The SE dialog is simple. Just tell it what "source" tank to use and then the "destination folder" where you want the files stored. It will just extract everything it finds, replicating the tank's file structure exactly.

    Note: if you want to extract all of the DS tanks to the same folder, you can run SE directly from it using just the bits.

How The Resources Are Combined To Form The Unified File Table
<Back to Table of Contents>

It's pretty simple. At game startup time, the resource system goes through all the .dsres tanks it can find and then sorts them by priority (there is a flag in the tank for this that you see in that SE tank builder dialog). Tanks with the same priority will get sorted by build time.

Starting with the highest priority tank, the resource system gets file directories for each one and adds them to a single master directory of all the resources available to the game. If a resource has already been found, it is only used once. When the system is looking for a file of that name, or it is asking for all the files in that directory, it will come up with the highest priority tank's version to load in.

For example, say that tank 1 has a file jooky.gas in it, and tank 2 has one also. Whichever tank has priority will get looked at first. That version of the jooky.gas file will "win" and the second tank's version will be ignored. Modding in Dungeon Siege works by having the files in your mod override the factory versions that ship on the CD.

Bits are similar except they are stored in individual files on the hard drive. Since bits are not used in DungeonSiege.exe (the retail version), the bits folder will effectively be invisible to it. That's why it's necessary to "tank up" your bits folder for the retail game to see it.

Bits will always override tanks. If you have a file "/world/contentdb/defaults.gas" in your "bits" folder, it will always override the equivalent file stored in any tank that gets loaded.

Important: this system is completely arbitrary. There are no requirements for which files go into which tanks. I recommend putting all of your files into a single tank for your mod (and the map in a second one) but the resource system doesn't really care if you put sounds into a file called bitmaps.dsres. The system just reads in everything it can find and merges it all into a single file table.

Conclusion
<Back to Table of Contents>

That's it for this tutorial. You should now have an understanding of the Resource System, "bits" and "tanks", the default tanks that ship with DS, and how resources are combined to form a unified file table. You should also be able to retrieve files out of tanks, create a map, and build a mod. Additional advanced topics are covered in other Siege 200 and 300 tutorials for more modding pleasure.

And if you're curious, there is a detailed discussion of the predecessor to the Dungeon Siege file system at http://www.drizzle.com/~scottb/gdc/its-still-loading-paper.htm

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_214 Siege Max: Characters

Siege Max: Characters

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

What This Tutorial Will Teach You

Siege Max Setup

Naming Conventions

Setting Up For Your Character

Prepping The Mesh

File And Mesh Naming

Poly Count

Scale

daVinci

Textures

Mip Levels

Bones

Bits-n-Pieces

Previewing The Mesh

Exporting The Mesh And Skeleton

Animations

gmax™ FPS Settings

Animating Naming Conventions

Creating The Default Stance

Creating The Walk Cycle

Creating The Default Fighting Stance

Creating An Attack Cycle

Creating A Death Animation

Templating

Testing

Conclusion

Introduction

This tutorial will cover the basics of exporting a character from gmax™ into Dungeon Siege. The first character we'll show how to prep is a relatively straightforward monster type. We'll be using the Skeleton as an example.

Monsters in Dungeon Siege don't use the complex system of weapon and armor swapping developed for the Player Characters (like the human, dwarf, and skeleton that you can choose to play in Multiplayer Mode), and are a good starting point for anyone interested in creating characters for the game.

For the purposes of this tutorial we're assuming that you've already taken the time to learn the basics about creating models, textures, bones, and skinning them. gmax™ comes with a very nice set of basic character tutorials to get you up to speed on these things. We'll be focusing on what's needed to get that character working in Dungeon Siege.

What You Need For This Tutorial
  • Dungeon Siege version 1.1
  • Dungeon Siege Tool Kit (includes Siege Editor and Siege Max)
  • gmax™ from Discreet®. Download it here.
    • For further help in gmax™, first download the help files from Discreet®.
    • In gmax™, please refer to Help -> User Reference.
  • A test map you can practice with.
What This Tutorial Assumes You Have Already Learned
  • Siege U: 201 - Templates
  • Siege U: 208A - Non Player Characters
  • Siege U: 211 - Naming Key
  • The basics of modeling and texture mapping
What This Tutorial Will Teach You
  • File placement requirements for your models, textures, and animations
  • The modeling standards for Dungeon Siege
  • How to export your files to Dungeon Siege
  • Animation naming conventions
  • Default animation stances
  • A list of resources for more information on modeling, texturing and animating
Siege Max Setup
When Siege Max sets up, it sets up a subfolder in the gmax folder under "...\gmax\gamepacks\Siege Max\Scenes" that contains a folder named "ArtAssets" and one named "Reference Scenes." The ArtAssets folder is the one we'll be looking at for this tutorial, and where you'll be saving your first Dungeon Siege character.

Let's take a quick look at the contents of ArtAssets to familiarize ourselves with what's there. Inside you'll see the folders "terrain", "objects", and "characters" among others. These are the folders that store the source files for your content. The output files, the ones actually used by Dungeon Siege, will be placed directly into the proper install folders for use by the exporter tools, and ideally you won't need to concern yourself with them much at all.

Inside each of these subfolders you'll notice a number of sample files, all with seemingly incomprehensible long filenames of strange glyphs and potent symbols of dark magic.. The Dungeon Siege toolset looks at the structure of the filenames of each piece of art in order to determine it's proper place in the overall scheme of the game. Various parts of the tools, and the game itself, rely heavily on the filenames, so it's very important that you learn the naming conventions for each type of content you're working on.

Naming Conventions
<Back to Table of Contents>

Now, I know that we already listed the Siege U: 211 - Naming Key tutorial as "required reading." Chances are, you skipped past that in a gleeful rush to make your character.

Slow down there bucko, take a deep breath, and read it. Naming conventions for files are key to keeping your characters clean and in their proper place, and in allowing the tools to understand what they are and what they can do. They are also absolutely essential for Dungeon Siege to 'see' your new content and have it to show up in Siege Editor as a usable character.

Setting Up For Your Character
<Back to Table of Contents>

Now that you've read Siege U: 211, and discovered the importance and joy of naming conventions, we need to build the folders to hold all the pieces of content your character will use.

To begin with, create a custom NNK naming key file for your mod content, for example "WootsMod.NNK". Now, let's assume you're excessively tidy, as are all animators worth their salt, and would like to keep all your content for your new creature in its own subfolder. Follow the instructions in the Naming Key doc to create a 'Tree' for your character.

You will need to create multiple folders in multiple locations for your character.

Start by creating a new folder and NNK Tree in the \art\Meshes\Characters folder for your character. For example, you're making a creature called the Nose Goblin, a fierce beast with a powerful long-ranged mucus attack; you would create a folder such as \art\Meshes\Characters\NoseGoblin, and make an appropriate entry for that new Tree level in your custom "WootsMod.NNK" naming key file.

Follow the same procedures to create a custom folder and naming key for your character's texture under the art\bitmap tree as well.

Later on we'll be adding more folders for the animations, and various types of attacks, but for now you only need the mesh and bitmap folders and NNK trees set up.

Prepping The Mesh
<Back to Table of Contents>

As with any complex task, keeping tight to the standards is an absolute must. To create the volumes of art that fill the worlds of Dungeon Siege, we found that standards became a critical part of making clean content and reducing the amount of fixes and rework.

Modeling standards for Dungeon Siege are fairly standard for low-poly games. You need to be very careful about not having polys duplicated on top of each other, seams or cracks between polys, etc. Many tutorials exist on the web already for basic low-poly game modeling... Hunt around, join some community forums -- there's a lot to learn but a lot of great help all over the place.

File And Mesh Naming
When creating meshes for Dungeon Siege content that doesn't animate, the mesh name is used to determine the correct naming key and what to export. This is so you can have dozens of nodes or items in a single gmax™ file.

Characters work a bit differently. For character gmax™ files, the tools use the filename isntead of the mesh name to determine the export file locations and names. Only one mesh should exist in a character gmax™ file, and it must be named "skinmesh".

This grew from the need to be able to quickly copy an animation to start a variant of it by simply resaving the filename as something new.

In both mesh "m_" and animation "a_" gmax™ character files, you simply need to ensure that your gmax™ filename correctly follows the naming key, and that the mesh is named "skinmesh".
Note: Anything that will animate should follow this standard. If you're making a door that opens, or a lever that pulls or some other animating item, treat it exactly as you would a character file. Allow only one mesh per gmax™ file, named "skinmesh", and match the gmax™ filename to the naming key for the item you wish to export.

Whenever you name any mesh "skinmesh", you are essentially telling the Siege Max tools to use the .gmax filename whenever it needs to determine the actual name of the exported object.
Poly Count
Poly count standards for characters are roughly between 200-800 polys, with a definite hope that people will lean on the lower side, as Dungeon Siege combat is usually between large groups of characters at a time.

"Boss" type monsters could be higher in polycount, since they are almost always unique, only one is on screen at a time. These should be kept to a minimum in the game. For this tutorial, we won't be dealing with any of the extra issues related to creating Boss monsters, such a extreme scale and movement issues, and will focus just on a "standard" 200-800 poly monster.

Scale
Keeping scale consistent throughout your work will really help to reduce confusion. We've used meters as our standard, so you'll likely want to set your scale preferences to match.

To set the Scale:
  1. Click Customize on the main menu, and select Preferences from the drop-down.
  2. Under the General tab, set System Unit Scale to 1 unit = 1.0 Millimeters. Click OK.
  3. Click Customize on the main menu again, and select Unit Setup. In the Units Setup Dialog, select Metric and set it to Meters.
This will ensure that everyone's units and scale are all the same as they make content for Dungeon Siege.

Check the scale of your character:
  1. Set viewport to Front view, using the "F" key, then reset zoom to match your character by hitting CTRL+SHIFT+Z.
  2. Create a Tape Measure helper object, and line it up with your character.
  3. A standard bipedal "man-sized" character is roughly 2 meters tall, compare that to the reading in the tape measure helper and adjust your character scale accordingly to fit your design concept.
  4. Remove the Tape helper when your character is scaled to fit.
daVinci
The character should be setup in what's called a "daVinci" pose, which is its spine strait up and its limbs out straight, or at a slight angle parallel to the main body from the front view. Modeling a character in an action pose, and then trying to bone and animate it from that, usually leads to a lot more trouble.

Figure 1 - The daVinci pose.

The main reason to making sure a model is created in a daVinci pose is so that its range of motion during animation will be the widest possible before polys begin to stretch or bunch up in bad ways. In addition, daVinci poses are much easier to texture than natural or action poses. In any case, it's a good standard to follow. Save the posing for the animations!

GPG artists used a slightly modified daVinci, with the legs strait and the feet flat on the ground. In most cases, the daVinci pose will also have the arms flat sticking strait out from the torso at 90 degrees to the spine. This simplifies the process of animating, and since the models are low-poly, you lose very little in terms of range of motion for the limbs.

Textures
All textures in Dungeon Siege MUST be sized in a 'power of 2' dimension. So 2x2, 4x4, 64x64, 64x128, 128x64, etc. are all valid sizes, but 117x251 is not. Also, Character textures in Dungeon siege are usually 256x256 pixels in size, although this level of detail is very rarely actually used in the game, due to system requirements and average camera zoom distances. 128x128 sized textures are just as ideal for use in DS in many cases, as most of the time that's the highest resolution that is seen in the game. Textures should be saved in the same folder as your meshes. So character textures are saved in the 'characters' folder.

Mip Levels
Some of you may be asking, "What's a Mip Level?" MIP stands for "multum in parvo" which is Latin for "many things in a small place." A Mip map is a collection of scaled down versions of your texture, which are swapped in the video card on the fly based on user preferences for texture resolution, and distance from the camera.

In Dungeon Siege, character texture Mip levels are selected based on user preference settings and camera zoom, and ramp down strongly by powers of 2 in resolution.

Mip maps for Dungeon Siege textures are created automatically as part of the Tankbuilder process, so no effort is needed when making custom content, other than understanding that they will affect the appearance of your character in the game, and to create textures ahead of time that work well at lower resolutions.

Texture details should focus on large patterns, instead of small details. Aim for "realism" through use of low-saturated colors, instead of lots of small details, which could look bad when crunched down by the MIP'ing tech.

Bones
gmax™ comes with a set of tutorials on the basics of creating Bones, and skinning them onto the character mesh. Please read and follow these tutorials on your character before proceeding.

Dungeon Siege characters need very specific bone names in order to export properly through the converters, and interact properly with the game engine. A lot of little things rely on the consistency of these names, so being diligent in following them will eliminate a lot of common problems.

Figure 2 - Standard bone names and parenting for a bipedal character.

While the structure of this skeleton doesn't need to be followed exactly, you can have only one spine bone, or four fingers, or even a tail for example, the names should be matched precisely for what pieces you do have. The Pelvis, Hands, and Feet bone names are all used by the tools, and must match the names in the diagram to avoid creating an error in that process.

After creating the skeleton for your character, you'll use the skin modifier to make the bones properly deform the character. gmax™ includes a detailed tutorial on skinning, and other resources are available on the web.

Weighting vertices is a very crucial part of good character animation. Spend a lot of time setting and testing your vertex weighting for your skin, and you'll be a lot happier with the results.
Note: The rest of the tutorial assumes you've boned your character, and that the bones are skinned and weighted onto the mesh. Some of the tools within Siege Max will not work if you don't at least have the base bip01_pelvis bone attaches to a character mesh, and have saved the character file out with an acceptable filename.

Most errors you will likely encounter initially will be related to improper naming. Take the extra time to ensure everything is named correctly.
Bits-n-Pieces
Each basic Dungeon Siege character requires some extra pieces to interact with the game, to hold weapons and shields, and tell the engine how fast they move. Assuming you've named you're bones correctly, adding these to your character is a simple matter of running a couple of quick tools.

Siege Max Mesh Modifier: This gmax™ modifier is used to store information as part of your character mesh file that remembers specific mesh needs related to gmax™. It is added like any other gmax™ mesh modifier, by clicking on the Modifier List drop down, under the Modify rollout panel, and choosing the "Siege Max Mesh" modifier from the list. This enables your mesh to store data such as animation event flags, and more custom features we will be addressing in future tutorials.
Note: All Siege Max functions, other than adding the Siege Max Mesh modifier, are accessed by right clicking on the mesh. Many of the various tools or functions that make up Siege Max will only appear if all your naming is correct and the correct modifier is applied. If you expect to see a certain function, and it's not there, double check your file and bone naming.
Root: The "Root" is a large blue arrow shaped object that rests underneath your character, and is used by the game to 'remember' where your character's actual x & y position is in the world during run-time. It is, in effect, the 'center' of your character as far as the game is concerned, and all of the character's decisions about path-finding and movement will be based on the location of the root.
Note: If you animate your character too far off the root, chances are good that at some point it will pass through walls or other characters in the game.
Adding the Root:
  1. Select the Character Mesh
  2. Right click on the mesh, then select "Siege Max Root Tools" and select the Add/Reset Root Marker tool under the submenu.
Grips: Grips are dummy objects used to set the point where weapons or shields lock to the character. Generally the weapon grip is placed in the right hand and parented to the bip01_r_hand bone, and the shield grip is placed next to and parented to the bip01_l_hand bone. But this isn't a requirement. Dungeon Siege allows equal representation for lefties as well!
Note: Parenting the grips can only be done in the mesh-building phase of creating a character. Grips cannot be 're-parented' on the fly in an animation! You can animate the grips, for example to make a 'flipping a dagger' type of fidget animation, but the grip must remain parented to it's original parent bone throughout the animation.
In fact, the choice of grip location and parenting when modeling a character is really only limited to how you want the character to work later on.

For example, if you want you could parent the shield grip to the spine bone, for a character that wears his shield on his back. It won't affect how the shield works in the game, if it's equipped it's equipped regardless of location.

To get grips to work properly though, you do need to be careful about their orientation. A weapon or shield will align itself along the axis of the dummy grip. A sword will point 'up' along the z-axis of the grip, and the flat of the blade will follow along the x-axis.

For example, if you wanted to create a thief character that always attacks "backhanded" you would rotate the weapon grip 180 degrees, so that it's z-axis pointed backwards, from the character's perspective, instead of forwards.
Note: Make sure that any rotations for grips are done in 'Local' axis mode, so that you have are editing with an accurate display of the Pivot Angle for the item.
Another example might be a golem type character that 'mounts' its weapons inside it's arm. In this case you'd simply aim the z-axis of the weapon grip exactly along the z-axis of the arm bone, and parent it to the arm-bone, and recess the grip into the arm mesh itself. Then, when this golem is using a weapon, it'll stick out of the arm and look like part of the golem!

Adding Grips:
  1. Select the Create tab and then the Helpers subgroup in the gmax™ side panel.
  2. Click on "Dummy" and then create a small dummy box where you wish your character to use its weapon.
  3. Select the Modify tab and rename the dummy box to "weapon_grip".
  4. Select the "Select and Link" button from the main toolbar, and drag a link line from the grip dummy to the bone that will control the weapon.
  5. Repeat steps 1-4 for the shield grip, naming it "shield_grip".
After you've added the root and the grips, double check to make sure they are correctly aligned and that the character is correctly properly centered over the root object.

Previewing The Mesh
<Back to Table of Contents>

Okay, so you've got your character naming key set up, and it's modeled, textured, boned, skinned, the bits-n-pieces are added on, and you're ready to check it out!

To Preview a Mesh:
  1. Select the character mesh.
  2. Right click on the mesh, and then select "Launch Previewer".
Figure 3 - The AnimViewer window.

This brings up the AnimViewer, a separate program that runs a small portion of the DS engine called 'NEMA', and allows you to view content as it will truly appear in Dungeon Siege.
Note: The preview window may stay active during the entire editing session. To update the display, simply reselect "Launch Previewer" and the contents of the AnimViewer will reflect your recent changes.

Exporting The Mesh And Skeleton
<Back to Table of Contents>

After you've successfully previewed your mesh, and made sure everything's okay, you're ready to export it as a game usable object! Don't forget to save your gmax™ file with the proper final file name, as per your Naming Conventions.

"Native" Dungeon Siege mesh files are created with an "ASP" file extension, which stands for Animatable Aspect. The Export tool creates the ASP files for you, saving them to the locations you've specified in your Naming Key Tree by comparing it against the filename for the character gmax™ file.

Editing, copying, or otherwise working directly in or with ASP files isn't advised, you should always work in gmax™ sources and export fresh when you want to make changes.

To Export Your Mesh to an ASP file:
  1. Select the character mesh.
  2. Right click on the mesh, and then select the "Export Mesh."
The exporter will use your NNK setup, and create the proper ASP file in the Dungeon Siege game folder you've prepared. If the folder doesn't exist, it'll prompt for you to create it.

Note: During development of a mesh you'll likely want to save several versions of it. Remember that the tools will create the final files based on your gmax™ filename, and might not work if you've used something that isn't listed in your Naming Conventions NNK file. Save various versions of your model by changing your gmax™ file names, and then when you're ready to go, simply re-save as your final "game" file name, and you'll save yourself a lot of confusion.

Animations
<Back to Table of Contents>

Now that you've successfully exported your mesh, you're ready to start creating animations!

But wait, not so hasty, there's a couple more things to do before we get going...

First, you need to set the frame rate for your gmax™ file to match the DS standards, to avoid creating animations that are too problematic for Dungeon Siege to play appropriately.

gmax™ FPS Setting
With the quantity of animations in Dungeon Siege, we found that reducing the overall amount of keyframes per animation was very helpful in providing clean animations that were quick for the engine to interpret. In order to keep the amount of keyframe data low in our animations, we've set the FPS [Frames Per Second] in our animation data to 12 FPS.
Note: This doesn't mean that the engine runs the animations in the game itself with only 12 frames per second!! The in-game engine interpolates between the keyframes smoothly, at whatever FPS the system it's running on can handle. Setting the FPS in gmax™ to 12 only reduces the overall amount of keys available per second of animation in gmax™, keeping the animations clean and lightweight for processing, the animator from over-keying, and allowing the smooth interpolation in the engine to work well.
To set the FPS in gmax™ for Dungeon Siege standards:
  1. Near the bottom right hand corner of the gmax™ interface, with the other animation controls, click the Time Configuration icon.
  2. In the Time Configuration Dialog, set Frame Rate to Custom, FPS to 12, and Time Display to Frames. Setting the Time Display to Frames is important, as otherwise the animator will be able to key in partial frames, allowing way more keyframe data per animation than is necessary for Dungeon Siege characters.
Hint: One thing to keep in mind in your animations, is that the characters are going to be seen mostly at a small scale, as most players tend to have the camera zoomed all the way out. Being too fussy over small details of motion will simply create more keys than are really needed, and create animations that could read "jittery" or odd at game scale.

Focus on the large motion, testing at the farthest zoom, and try to get away with as few keys as possible. This will improve the performance of your character in the game engine, and likely will make things easier on you as an animator and cleaner in the end product.

Note: If you experience any problems during animation with bones 'flipping' or over-rotating, you may want to consider changing the rotation controller to one that allows more precise control, such as the Euler XYZ controller, which allows tangent bezier handles for each axis in the track view. There are also a few scripts available on the net that auto-smooth curves for Euler controller key frames.

If you want to get your animation looking as close to the true output in the Dungeon Siege engine as you can, change to using the Linear rotation and translation controllers.

For more information on how to change controllers, consult the gmax™ help files.
Now you're ready to resave your mesh gmax™ file and start making it move!

Animation Naming Conventions
Once again, Dungeon Siege and the Siege Max toolkit work around a very locked down file-naming structure. In the case of animations, we use the filename to indicate the attack style as well as the different motions used by each style.

Dungeon Siege supports 8 different styles of combat, based on the weapon equipped by the character, or hard-coded directions from it's template. This is specified in the filename directly following the character name.

Figure 4 - Animation naming format.

In this layout example, the modeler has already specified a 'mod', 'moddername', 'charactername', and 'fs#' Tree setup in his NNK file, to allow the proper directory structures to work.
Note: Setting up the 'fs#" sets for your animations in your custom NNK must be done prior to creating any animations. For further examples, consult the Naming Key Doc and look at examples in the installed NNK files.
Attack Style Suffixes:
  • fs0 - Barehanded, spellcasting, or natural attacks (bite/claw/etc.)*
  • fs1 - Single handed weapon
  • fs2 - Single handed weapon, plus a shield
  • fs3 - Shaft Handled 2-handed weapon (battle-axe, halberd, etc.)
  • fs4 - Short Handled 2-handed weapon (swords, etc.)
  • fs5 - Staff*
  • fs6 - Bow
  • fs7 - Crossbows (Note: exotic ranged weapons, flamethrowers, etc., all use the same animation set as crossbows)
  • fs8 - Shield only.
Animation Type Suffixes:
  • at - Attack
  • dfs - Default fighting stance
  • dff - Default fighting stance fidget
  • di - Die
  • ds - Default stance
  • dsf - Default stance fidget
  • mg - Magic
  • rl - Run loop
  • wl - Walk loop
  • fl - Fly loop
  • rv - Reveal (un-camouflaging from hiding)
So, for example, if your name was "Bob" and your mod was called "GoofMod" and you wanted to make a bow attack for your fancy Winged Monkey critter, the filename would be: a_c_Bob_Goofmod_WingedMonkeyCritter_fs6_at.gmax.

Now, this assumes that you correctly set up the folders in your content path and NNK Naming File to match, so you'd save the file into this folder/sub-folder structure: ...\ArtAssets\animations\bob\goofmod\WingedMonkeyCritter\fs6 for instance.

If you want to have multiples of any given type of animation you simply add a dash and then a 2 digit number for the anim after that. For instance, the suffix for a second attack anim for a claw attack would be "_fs0_at-02". The one for a third magic anim for a staff holder would be "_fs5_mg-03" etc.. Using a dash is important, as an underscore is used by the exporting tool and NNK data to indicate a new subfolder.
Note: Only barehanded fs0 anims, and staff fs5 anims, are rigged in the DS engine to handle magic "mg" animations.
Creating The Default Stance
The first animation you're going to want to make isn't really an "animation" at all. It's a single frame pose of the character in what will be its casual or default stance in the game.

Create gmax "_ds" File:
  1. Save/Copy your master mesh file to a new file, using the naming convention for a default stance animation, which is a suffix of "_ds" added after the Custom Identifier and Attack Style Suffix.
For example: You've created the fine mesh known to the world as m_c_mod_WootsMod_NoseGoblin_pos_1.gmax and properly exported him as an ASP file.

To create an animation file for the NoseGoblin, simply copy the file to the new name a_c_mod_WootsMod_NoseGoblin_fs0_ds.gmax and you're ready to go.
Set Anim Time Config:
  1. Near the bottom right hand corner of the gmax™ interface, with the other animation controls, click the Time Configuration icon.
  2. In the Time Configuration Dialog, set Frame Length to 1.
This sets the anim to only have one frame. It's very important that "stance" animations do not receive extra frames, or keys beyond frame 0.

Pose the Character:
  1. The default stance for a character should look natural and rested. It is the pose that will be used for the character when it's not engaged in combat or otherwise turned aggressive for any reason.
  2. Adjust the character so that it stays mostly over the center root, but doesn't look "twinned" or too symmetrical.
Once you've got a pose that you're happy with, that's it, you're ready to export it.

Save gmax™ File and Export Animation PRS:
  1. Save the file, double-checking that you've got the right naming conventions for an "FS0" anim, default pose, such as a_c_mod_WootsMod_NoseGoblin_fs0_ds.gmax.
  2. Select the Character Mesh.
  3. Right click on the mesh and select "Export Animation."
You should now have a PRS file of the same name as your character created in the proper folder. This anim is theoretically enough to allow the character to be placed in the game. A default stance is required for every character. But, of course, that's not all we want for all our work, so let's move on, pardoning the pun...

Creating The Walk Cycle
To make a character move in Dungeon Siege, you'll need an animation known as a "walk cycle." A walk cycle is a looping animation that ends in the exact same pose of the character it began in. At the end of character creation, you'll want to enter the rate of travel of the walk cycle into the character's template, but more on that later.

For now we'll just assume you've figured out the basics of animating a walk cycle on your own, there are many online tutorials and books on the subject, and focus again on how to export it into Dungeon Siege.

Create gmax "_wl" File:
  1. Save/Copy your Default Stance file to a new file, using the naming convention for a walk loop animation, which is a suffix of "_wl" added after the Custom Identifier and Attack Style Suffix. Such as "a_c_mod_WootsMod_NoseGoblin_fs0_wl.gmax" if we continue the example from above.
Set Anim Time Config:
  1. Near the bottom right hand corner of the gmax™ interface, with the other animation controls, click the Time Configuration icon.
  2. In the Time Configuration Dialog, set Frame Length to roughly 10-20 frames.
Trying to predetermine the amount of frames needed for a walk loop can be difficult. Don't fret it too much. Use the amount of frames in your scene as a rough guide for how quickstepped or ponderous you want your walk to be, and then try and fill it in the best you can. You can always easily change the amount of frames later on.

A good place to start with is the standard 10-frame walk cycle. Many books on the subject list out the 5 basic 'parts' of each step for a bipedal character, and these can just be plugged in following their suggestions. An excellent book on the subject is "Digital Character Animation", by George Maestri.
Note: At any time you can get a good view of your walk cycle as it would appear in the game by bringing up the Previewer. Simply right click and select Launch Previewer.
Once you're happy with your walk cycle, and it fits precisely in the amount of frames set up for the scene, you only need to do a few things to get it to work properly in Dungeon Siege.

First you need to set your Root object, the blue arrow that rests under your character, to follow along at the same speed as the main character Bip01_DUMMY object. This is handled via the Root Tools.

Setting the Root to Match Speed:
  1. Select the mesh.
  2. Right click on the mesh, then select the "Siege Max Root Tools" submenu and choose "Add/Reset Root Marker."
  3. Right click on the mesh, then select the Siege Max Root Tools submenu and choose "Animate Root Marker."
This will add the blue root marker and match its motion to that of the character's BIP01_DUMMY main parent object. The root object is what is used by the game to know the "center" of the character's animation data. Matching it to your character for a walk cycle is what makes the character move appropriately in the engine, if you forget this step your character will 'pop' when he is asked to walk by the game.

After setting your root, you need to add the Loop Events. All walk, run, crawl, hop, or "drag yourself along the ground with your fingertip" animations need to be set up with loop events to work right in the game, otherwise the anim will play once and then the character will stop animating while it moves along!

Adding Loop Events to a Walk Loop:
  1. Select the mesh.
  2. Right click on the mesh, then select the "Siege Max Event Tools" submenu and choose "Add Loop Events."
This should have added two events [flags for the game engine] into your animation. To check them simply do the following:

Checking Events:
  1. Select the mesh.
  2. Right click on the mesh, then select the Siege Max Event Tools submenu and choose "List Anim Events."
A list window should come up showing a layout of all the frames in your animation. Frame 1 should have a "BEGL" event listed, and the last frame should have a "ENDL" event. If they don't, double-check all of your piece and file names to make sure they comply with the conventions, and try again.
Note: The walk, run, and fly loops are the only animations that should have Loop Events and their root moved!
Save gmax™ File and Export Animation PRS:
  1. Save the file, double-checking that you've got the right naming conventions for an "FS0" anim, walk loop, such as a_c_mod_WootsMod_NoseGoblin_fs0_wl.gmax.
  2. Select the Character Mesh.
  3. Right click on the mesh, then select "Export Animation."
Creating The Default Fighting Stance
By now you should have the basics on the tools to export an animation, so we'll just focus on the specifics for the other essential anims to make a character work.

The DFS anim, or Default Fighting Stance, is a 1 frame long animation, just like the Default Stance we've already made, but this one is posed with the character in an aggressive posture. The pose in the DFS will be the starting frame of the animation for all attacks.

To Create the Default Fighting Stance:
  1. Load up your previous Default Stance anim.
  2. Repose the character aggressively.
  3. Save the gmax™ file with the correct filename (don't forget to change the suffix to 'dfs'!!).
  4. Select the mesh and export the PRS file.
Creating An Attack Cycle
Now to the really fun part, hacking and slashing! Attack animations for Dungeon Siege are used for a great variety of motions, from simple sword swings to lunging worm throat spikes.

The motion itself is completely up to you; however you must remember two things:
  • All attacks must start with the Default Fighting Stance pose.
  • All attacks must be made to loop cleanly.
By 'looping cleanly' we intend that the animation is made so that it can run in a looped sequence and look natural and smoothly performed. For example, if a monster starts off an attack very slowly, building energy for example, but ends that attack with a quick retraction, then the movement will look wrong when the end of the anim loops back to the beginning..

Once you've got an animation you're happy with, it's time to prep it for export it. Just as with the walk cycle, there are a few special things you need to do with an attack animation for Dungeon Siege to understand it properly.

Adding Tracers to an Attack Anim:
  1. Select the mesh.
  2. Right click on the mesh, then select the "Siege Max Equip Tools" submenu, and choose "Show Tracers."
Note: This actually only displays the tracers in gmax™ for proofing purposes, it doesn't make them show up in game. Check the tracer display to make sure it doesn't pass through the character, and reanimate if you see any problems.
Adding Tracer/Swing Events to an Attack Anim:
  1. Select the Mesh.
  2. Right click on the mesh, then select the "Siege Max Event Tools" submenu, and choose "Open Anim Event Tool."
  3. Advance to the frame you wish the tracers to begin.
  4. In the Anim Event Toolkit dialog, doubleclick on "Begin Swing/Cast."
  5. Advance to the frame you wish the tracers to end.
  6. In the Anim Event Toolkit dialog, doubleclick on "End Swing/Cast."
  7. Click "List Critical Events" in the Toolkit to see the events you've added and make sure they're where you want them.
Adding Fire Events to an Attack Anim:
  1. Select the Mesh.
  2. Right click on the mesh, then select the "Siege Max Event Tools" submenu, and choose "Open Anim Event Tool."
  3. Advance to the frame where the attack should actually "strike" the opponent.
  4. In the Anim Event Toolkit dialog, doubleclick on "Fire/Strike with weapon."
  5. Click "List Critical Events" in the Toolkit to see the events you've added and make sure they're where you want them.
Now, just save the gmax™ file with the correct suffixes, and then export the animation.

Creating A Death Animation
Ahhh, now after all the hard work and love you've put into your fantastic legendary beast, it has met its match: the dreaded Pimply Faced Adventurer! Such is life...and death, in this case. Time to die.

Death animations start with the Default Attack Stance, so let's load that back up to get going. Now, unlike attack cycles, the death anim doesn't loop, so you're free to end it however you wish, again pardoning the atrocious pun. A good guide to follow though is to have the death anim be as fast and furious as possible, and to fall as close to the center root of the character as you can without it looking strange.

The reason for fast death anims is simple, Dungeon Siege is a high combat game, and slow deaths will simply be lost in the chaos. The extra frames won't be any help for overall performance either. Not to mention that a dozen monsters on-screen all performing the same big long melodramatic death animation will look very comical. Gak 'em hard and fierce. It's the only way to go.

The reason for falling close to center is more technical, it's to reduce the chance of the mesh falling inside of walls, hanging off of cliffs in mid-air, etc. Do your best, we know it's not easy.

So, load up your DFS gmax™ file, set your frame count to around 10-12, and animate that death! Don't forget, you can preview at any time during animating by bringing up the Siege Max previewer.

Once you've completed your death anim, you need to assign the death event so DS will understand the right time to 'kill' the character and spawn it's treasure and so forth.

Adding Kill Events to a Death Anim:
  1. Select the Mesh.
  2. Right click on the mesh, then select the "Siege Max Event Tools" submenu, and choose "Open Anim Event Tool."
  3. Advance to the frame where the character should die, which will indicate either the frame that the character should explode or where it throws off it's treasure.
  4. In the Anim Event Toolkit dialog, doubleclick on "Die/Explode."
  5. Click "List Critical Events" in the Toolkit to see the events you've added and make sure they're where you want them.
Now, just save the gmax™ file with the correct "_di" filenamename and export the PRS.

Templating
<Back to Table of Contents>

Please read the Siege University 201 and 208a to learn how to set up a template that will allow you to use your character in the game. Without a template, you won't be able to test your character or place it on any maps; it simply won't show up in Siege Editor.

Testing
<Back to Table of Contents>

Following the Siege Editor tutorials, create a very small map of a handful of flat nodes, and then drop a few of your monster spread around over the map. Save the map and then load it up in the game.

Run around and see how it reacts and fights. You'll likely be surprised by a number of things, but with any luck in only a few rework sessions you'll have a fine new monster to add to the Dungeon Siege Bestiary!

Conclusion
<Back to Table of Contents>

In this tutorial we've taken you through the basic steps of creating a simple 'monster' type character for Dungeon Siege.

We started with an overview of the Naming Key and folder setup, so that your content can be 'seen' by Dungeon Siege and stays in nice clean piles. Then we covered some basics on modeling a character for DS and the requirements involved. After which we moved onto animating your creature with the required set of animations to get it working in game.

Obviously there's a lot more we could cover, but hopefully this tutorial will give you the tools you need to get started.

Most likely along the way something didn't quite follow the tutorial, or you encountered problems unrelated to the specifics of exporting content for DS, such as bad vertex weighting on the bones, mis-parented bones, etc. We sincerely wish we had the energy to cover all these complex topics at length in this tutorial, but there are books full of tutorials out there that don't answer all the possible questions or problems with modeling and animating 3D characters.

Making a 3D character work in a game like Dungeon Siege is a very complex task, don't get frustrated early on! It's going to take a lot of careful work and proofing of every step along the way before you're successful. Keep at it, the end results are more than worth it. Nothing is more exciting to an artist than seeing some idea he or she came up with running around and whacking on things with pointy sticks!

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_215 Siege Max: Objects

Siege Max: Objects

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

What This Tutorial Will Teach You

Siege Max Setup

Scale

Naming Conventions

Prepping And Exporting A Weapon

Helpful Guidelines

Getting To Know A Weapon

Preparing Your Weapon For Export

Time To Export

Create A Template

Testing In Dungeon Siege

Exported And Testing

Prepping And Exporting An Object

Helpful Guidelines

Getting To Know An Object

Preparing Your Object For Export

Time To Export

Create A Template

Testing In Dungeon Siege

Conclusion

Appendix A: Troubleshooting

Appendix B: Siege Max Sample Files

Appendix C: Helpful Resources

Introduction

This tutorial is an introduction to preparing weapons and objects in gmax™ for export into the Dungeon Siege game engine. Most weapons follow the same basic process for export. Some weapons like the Dragon Minigun are a bit more complicated and require extra steps. The process for creating world objects is similar, but not as complex as creating weapons.

This tutorial will begin with the assumption that you have already taken the time to learn the basics of modeling and texture mapping weapons and world objects. Gmax™ comes with a great set of tools and tutorials to get you quickly up to speed on this.

What You Need For This Tutorial
  • Dungeon Siege v1.1 or newer
  • Dungeon Siege Tool Kit (includes Siege Editor and Siege Max)
  • gmax™
    • For further help in gmax™, first download the help files.
    • In gmax™, please refer to Help > User Reference.
  • A test map you can practice with.
  • For online Modeling and Texturing Help:
    • Planet DS Forums
    • Game-Editing.net Forums
What This Tutorial Assumes You Have Already Learned
  • Siege U: 102 - Objects - (for your weapons and objects testing within SE)
  • Siege U: 201 - Templates - (for getting your art assets into the game)
  • Siege U: 211 - Naming Key
  • The basics of modeling and texture mapping
What This Tutorial Will Teach You
  • Setting up Siege Max
  • Scaling
  • Naming Key reminders
  • Components of weapons and objects
  • Exporting weapons and objects in Siege Max
  • Opening weapons and objects in Siege Editor
  • Guidelines for weapons and objects
Siege Max Setup
Before delving into the sweet delicacies of editing Dungeon Siege content, you must first make sure Siege Max is set up correctly according to the file structure for the art assets.

When Siege Max sets up, it sets up a scenes folder under the pathname "…\gmax\gamepacks\SiegeMax\Scenes". The "Scenes" folder contains a folder named "ArtAssets" and another folder named "Reference_Scenes." The "ArtAssets" folder is the one we will be concerned with for this tutorial, and where you'll be saving your first Dungeon Siege weapon and object. The "Reference_Scenes" folder contains reference equipment and objects from Dungeon Siege in the gmax™ file format (.GMAX).

The "ArtAssets" folder contains the subfolders: animations, bitmaps, and meshes. These folders are where you will store your source files for your content. The files used by Dungeon Siege will get exported to the Dungeon Siege install directory in the appropriate directory structure. The exporter will handle this.

You will notice inside each of these subfolders a number of sample files, all with seemingly incomprehensible long filenames. Dungeon Siege looks at the structure of the filenames of each piece of art, in order to determine the file's proper place in the file structure of the game. Various components of the tools, and the game itself, rely heavily on the filenames, so it's very important that you learn the naming conventions for each type of content you're working on. If you haven't read Siege U: 211 - Naming Key yet, stop right now and read it.

Scale
Keeping scale consistent throughout your work will greatly help to reduce confusion. Dungeon Siege used meters as the standard, so you will want to set your scale preferences to match.

To set the Scale:
  1. Click Customize on the main menu, and select Preferences from the drop-down.
  2. Under the General tab, set System Unit Scale to 1 unit = 1.0 Millimeters. Click OK.
  3. Click Customize on the main menu again, and select Unit Setup. In the Units Setup Dialog, select Metric and set it to Meters.
This will ensure that everyone's units and scale are consistent, as they make content for Dungeon Siege.

Naming Conventions
Previously listed under the Introduction for this tutorial is information about the Dungeon Siege naming convention (.NNK). It is very important to have an understanding of the naming convention used. Without your files named correctly, the game will be unable to load them properly.

Prepping And Exporting A Weapon
<Back to Table of Contents>

Helpful Guidelines
There are different ways to model a weapon; extruding splines, box modeling, using primitives, etc. It all depends on what you are most comfortable with. Pick whatever method works best for you. Texture it however you like (using one texture), keeping in mind the texture size doesn't need to be huge and poly count should stay reasonable. The following are a few helpful guidelines, but nothing is set in stone and everything is up to the artist/modder.
  • Poly count. Keep an eye on it. More unique weapons have higher counts, so save the polys for the rare / unique items (the more polys on screen, the more of a performance hit). Try and keep them below one hundred polygons if possible. Many weapons in DS are above and below this figure, so it can only be taken so literally. It's up to you.

  • Texture size. Can be no bigger than a 256 x 256 (512 x 512 will not work). Think about how much space the item takes up on screen; in game view and even when zoomed in, and adjust accordingly. The majority of our weapon textures were all contained on one 256 x 256. So the mapping of the majority of weapons was done very sparingly (the more textures to load and especially the larger ones, will cause more of a performance hit). If you had an individual 256 x 256 texture per weapon in Dungeon Siege, the amount of 256 x 256 textures loading would be unreasonable. So be careful with your texture usage.

  • Scale. Adding a reference model to the scene is always a good way to save time when determining the scale of your weapon model. In gmax™, right-click to bring up the "RefObj Dropper" (Reference Object Dropper). Double-click on an object to drop, and then click within your scene to place it. If you prefer to use your own model, you can merge or import your own. Using a character model for reference works well.

  • File Name. The file name does not have to be named specifically as the mesh does, but it's helpful to have them sync up together. The mesh name has to have the proper file name format to work with the DSTK. You must name your mesh "m_…" to get the "Item ToolKit" to function. This shows the toolkit what kind of art asset it is working with. For example, you would want to name your sword something like "m_w_swd_number here" (mesh_weapon_sword_#). The specifics of your mesh name are up to your naming key and file structure, but it must begin with "m_…" (for mesh).
Getting To Know A Weapon
A Dungeon Siege weapon requires several components before it can be used in the game. This tutorial will cover adding these components and exporting. Once you become familiar with these processes, you can apply them to create and export other types of weapons.

Components to a Weapon
  • Template: Text in a .GAS (file type) file defines the characteristics of the object and enables the engine to load it.
  • Mesh: Mesh name must follow naming convention: m_w_NAME.ASP
  • Texture: Must be a power of 2 and can be no larger than 256 x 256; should be a .PSD, will also get mip-mapped automatically (for more info on mip-mapped textures, see the Siege University Siege Max Character tutorial. It must follow naming convention: b_… (bitmap_…).
  • Grip: Single weapon grip.
  • Dummy Root: Determines where the object will be when placed on the ground.
  • Trace 01 and 02: Designates locations of traces on the weapon.
- Below is a list (Fig. 1) of the other types of weapons in Dungeon Siege and the breakdown of their attached components:

Figure 1 - A list of all the Dungeon Siege weapons and their helpers / components.

The example I'll be using is a sword from the game - the "Jewel Sword" (Fig 2 and 3).
  • Template: sd_un_g_c_sd_1h_mag
    • More about templates later in this article and also in Siege University 201.
  • Mesh: m_w_swd_038
    • Mesh name = meshes_weapons_sword _#.
  • Texture: b_w_weapons
    • Texture name = bitmaps_weapons_weapons.
  • Grip: grip_m_w_swd_038
  • Dummy Root: dummyroot_m_w_swd_038
  • Traces: AP_trace01 and AP_trace02
    • Weapon traces are usually placed at top of the blade and at bottom of the blade.
Figures 2 and 3 - Farmboy holding the world famous "Jewel Sword"!

Preparing Your Weapon For Export
For this example, we are going to assume you have already modeled, textured, and named your weapon mesh according to the proper naming scheme. For further help on modeling and texturing in gmax™, refer to Help > Tutorials.
  1. Before adding any components, it's a good idea to preview your object first to make sure everything is showing up like you want. Make sure your mesh is named correctly and texture is applied. Right-click and select "Launch Previewer". Notice if your weapon shows up oriented like you wanted. If not, you will need to adjust the pivot point.
    - For further help see Appendix A: Troubleshooting - Part 1. Pivot Points at the end of this document. Once you are satisfied with the orientation of your weapon, move on to step 2.
  2. Select your weapon and right-click (Fig 4) and select "Open Item Toolkit" from the pop-up quad menu (Fig 5).

    Figure 4 - Opening the Item Toolkit.

    Figure 5 - The Helpers floater.

    Figure 6 - The helpers / components attached to our sword.

  3. Select your mesh and then on your Helpers Floater, select "Add Grip to Item". This will add the grip to the weapon wherever you have designated the pivot point (Fig 6).
    - For more help on pivot points, see gmax™ Help > User Reference. If you get any errors regarding a "grip" or "dummy root" already existing, see Appendix A: Troubleshooting - Part 3. Helpers.
  4. Next, select "Add Dummy Root to Item". Similar to the grip, the dummy root will get added wherever the mesh has its pivot point, but this will get added in the same position of the grip or pivot, but just below it at 0 on the z-plane (Fig 6). If needed, you can adjust the position of this root later.

  5. Next, with your mesh selected, select "trace01" from scroll down menu, and then press "Add Attach Point to Item". When this is performed you should see a pop-up message saying "Creating AP_trace01 at mesh_name_here". Now that you have added the first weapon trace, you need to move it to the tip of the sword blade. Now add another trace, and name it "AP_trace02". Move this trace to the bottom of the blade (Fig 6). Traces define the polygons used to create the "motion blur" effect when a weapon is being used in combat.

  6. Now we need to move the Dummy Root into correct position. The Dummy Root will determine where the weapon sits relative to the ground when dropped. We want it a little below the sword so it will actually be hovering a bit, but not enough to look strange (this will make it easier to see, and prevent it from clipping into somewhat uneven terrain). Go to your Hierarchy Panel > Pivot > Adjust Transform > Move/Rotate/Scale and turn on "Don't Affect Children". Now move the Dummy Root so that it is positioned a little below the item. Once that is in place, be sure to turn off "Don't Affect Children" (Fig 7).

    Figure 7 - Positioning the Dummy Root below the weapon mesh.

  7. Hierarchy: You need to check the linking of your weapon and helper components before exporting to make sure everything works right. It should be set-up like the images below (Fig 8 and Fig 9).
    - For further help see Appendix A: Troubleshooting - Part 1. Pivot Points at the end of this document.
    Figure 8 - Weapon helpers / components.

    Figure 9 - Weapon components hierarchy.

  8. Preparation finished! So now that everything is hooked up (Fig 10) and linked correctly, you are ready to export and test your weapon in the game.

    Figure 10 - The helpers / components attached to our sword.

Time To Export
  1. The name of your weapon mesh governs in which directory the exported mesh, or "aspect file" (.ASP), will reside. The "Naming Key" (.NNK) defines this naming convention. Please refer to the documentation regarding the "Naming Key" to familiarize yourself with the naming convention used for Dungeon Siege art assets. As was covered earlier in the "Helpful Guidelines", you basically want your file and mesh named something similar to "m_w_swd_#".

  2. Select and right-click on your weapon mesh, and then select "Preview" from the pop-up menu. This will bring up the weapon previewer, or "Anim Viewer". The "Anim Viewer" lets you inspect your mesh to see if everything looks copasetic.

  3. If everything looks good, select and right-click on your weapon mesh in gmax™, then select "Export Mesh". You will get a confirmation dialog box confirming the export location along with the mesh name.
More than just previewing with the Anim Viewer:
The Anim Viewer can be used to not only preview your weapon individually, but can also be used to preview your weapon while it's held by a player character and along with an animation playing. Check out the Anim Viewer user help 'H' (for keyboard shortcuts) and load a character mesh and appropriate animation (depending on weapon type) and load your weapon mesh also. Try experimenting with this to test your weapons on the character along with different animations playing.

Here is a list of animation types along with their appropriate weapon type:
  • FS0 -- unarmed
  • FS1 -- 1 handed melee
  • FS2 -- 1 handed melee + shield
  • FS3 -- 2 handed melee (2h axe, 2h hammer, etc)
  • FS4 -- 2h sword
  • FS5 -- staff
  • FS6 -- bow
  • FS7 -- minigun, crossbow, etc.
  • FS8 -- shield only
Create A Template
Once you have exported the mesh, you can test it out in game. There are a couple of ways to do this, export your mesh and edit a template, or just export your mesh over an existing one (make sure you back up the existing one so you can go back to it).

- I suggest looking into more template help located in Siege U: 201
  1. Editing a template is easy enough to test your new art. Here is the template for the existing sample sword (just search for the template name "sd_un_g_c_sd_1h_mag" located in the wpn_sword.gas file).
    [t:template,n:sd_un_g_c_sd_1h_mag]
{
   doc = "Jewel Sword";
   specializes = base_sword;
   [aspect]
   {
      model = m_w_swd_038;
   }
   [attack]
   {
      f damage_max = 113.000000;
      f damage_min = 83.000000;
   }
   [common]
   {
      pcontent_special_type = unique;
      screen_name = "Jewel Sword";
   }
   [gui]
   {
      active_icon = b_gui_ig_i_ic_swd_005;
      equip_requirements = strength:31;
      inventory_height = 3;
      inventory_icon = b_gui_ig_i_w_swd_069;
      inventory_width = 1;
   }
   [pcontent]
   {
      [base]
      {
         f modifier_max = 23.000000;
         f modifier_min = 7.000000;
      }
   }
}
    You can simply export your mesh and edit the "model" block of the template (model = m_w_swd_038;) with your weapon mesh name.

  2. Exporting a mesh over an existing one will enable you to avoid editing any templates to test your art in the game (you might want to back up any existing mesh before exporting your own). You can just name your current new mesh the same as an existing one and overwrite its mesh when you export it.
Testing In Dungeon Siege
There are two ways to test your weapon in the game:
  1. If you overwrote an existing mesh or edited a template, you can simply load your map or ours to see you new weapon by using whichever weapon you overwrote.

  2. If you created a new template, you will have to fire up Siege Editor and load your new weapon through Siege Editor. For more information see Siege U. 102: Objects.
Exported And Tested
Now that your weapon has been exported and templated, you can load it into the game and try it out.

(For any further help, please refer to Appendix C: Helpful Resources at the end of this document)

Prepping And Exporting An Object
<Back to Table of Contents>

Helpful Guidelines
The same basic guidelines apply to objects as weapons. Keep an eye on your poly count (keep it low as possible and save the polys for only visible detail). Keep the texture size 256 x 256 or below and make sure it is a power of 2 (e.g. 16 x 16, 32 x 32, 64 x 64, etc.). For the scale of your object, first make sure you followed the gmax™ scale setup at the beginning of this article and also remember you can roughly scale your object by adding a character using the "RefObj Dropper" tool and using it for reference. Make sure your file and mesh are named "m_i_glb_name here" (mesh_item_global_name).

This is only an example of the naming of your mesh. Depending on where you want it, you would name it accordingly (glb, ice, swp, etc.).

Now that we have all of that out of the way, we can move on to getting familiar with the different aspects of a Dungeon Siege object.

Getting To Know An Object
Setting up a world object for export into Dungeon Siege is relatively simple.

Components of an object:
  • Template. Text in a .GAS file defines the characteristics of the object and enables the engine to load the object (more about templates previously in this article and also in Siege U: 201 - Templates).

  • Mesh. The mesh must follow the principles of the naming convention.

  • Texture. The texture must be a power of 2, can be no larger than 256 x 256, and should be a .PSD. For Dungeon Siege we consolidated the majority of our weapon textures onto 1 bitmap; "b_w_weapons.psd".

  • Dummy Root. The dummy root acts as the identifying pivot point for an object when multiple objects are contained within one gmax™ file. It also defines where the object sits relative to the ground in the game.
Figure 11 - Lookout!! It's the crate!

The example I'll be using is from the game and is the basic yet amazing global crate (Fig 11).
  • Template Name: crate_glb
  • Mesh: m_i_glb_crate (meshes_items_global_NAME)
  • Texture: b_i_glb_crate (bitmaps_items_global_NAME)
  • Dummy Root: (here's more info on this…)
    • A dummy root is used to tell the object where to sit when on the ground. It acts as its pivot point (0, 0, 0) for the object. If you are creating multiple objects in one gmax™ scene (such as weapons) each individual object needs a dummy root named and linked specifically to the object so the exporter can distinguish between the different objects upon export. If you are creating an item and it has ONLY 1 item in the scene (.GMAX), a dummy root IS NOT necessary - but it can't hurt to add it =). Just make sure you specify the object's pivot point to be at the bottom of the item where you want it to snap to the ground. For example, you would want to place the dummy root of a weapon a little bit below the mesh so it hovers above the ground in the game. For a crate, however, you would want the dummy root or pivot point at the base of the crate so that it doesn't float above ground. Think of the dummy root as the object's individual pivot point.
Preparing Your Object For Export:
Create any object mesh and texture it. For this example, we'll use a basic crate.

The Model and Texture

Okay, so by now you should have your model textured and ready to prep for export. You want to make sure your mesh is named according to the correct naming scheme along with the texture.

For more help on the naming convention, please refer to Siege U: 211 - Naming Key.

Adding the Dummy Root
  1. Select your mesh and right-click and select "Open Item Toolkit" from the pop-up quad menu.

  2. Select your mesh and then on your Helpers Floater, select "Add Dummy Root to Item". The dummy root will get added wherever the mesh has its pivot point, but just below it at 0 on the z-plane. If needed, you can adjust the position of this root later. For more help on pivot points, see gmax™ Help > User Reference (downloaded from: http://www.discreet.com/products/gmax/).
Moving the Pivot Point / Dummy Root

Adjust the pivot (or dummy root if you added one) so that your item lines up with the terrain properly.
Adjusting the Pivot Point
  1. To move the pivot: With your object selected, go to your Hierarchy panel > Pivot > Adjust Pivot > Affect Pivot Only.
  2. Move your pivot to line up with the bottom of your object (Fig 12).
  3. De-select "Affect Pivot Only".
Figure 12 - The crate pivot point.

Adjusting the Dummy Root
  1. Now we need to move the Dummy Root into correct position. We want it lined up with the bottom of the crate so it will not clip into the terrain (Fig 13). Go to your Hierarchy Panel > Pivot > Adjust Transform > Move /Rotate /Scale and turn on "Don't Affect Children". Now move the Dummy Root to be a little below the item.

  2. Turn off "Don't Affect Children" and deselect your Dummy Root.

    Figure 13 - The crate dummy root.

Time To Export
  1. Select and right-click on your object mesh, then select "Preview" from the drop-down menu. This will bring up the previewer window so you can see your object, as it will appear in the game. If you need to make any changes to your object in gmax™, simply select "Preview" again. The "Previewer" will show any modifications you've made.

  2. To export, select and right-click on your object mesh in gmax™ and pick "Export" from the quad menu.
You should be all set, with your mesh exported and set nicely where it needs be and properly named, its time to get your object into the game!

Create A Template
Templates are used throughout the game and if you read the previous help on weapons you'll recognize some of this info. The best info regarding templates is located in Siege University 201.

To get your item into the game, you can use the same method mentioned above in weapons; export a new mesh and edit a template / create a new template OR you can export your new mesh and overwrite and existing one.

The crate I used for an example is the most generic / global crate. Here's the template (from gas file "ctn_container.gas"):
[t:template,n:crate_glb]
{
   specializes = base_container_wood;
   doc = "crate_base";
   category_name = "container";
   aspect:model = m_i_glb_crate;
}
  1. To Export a new mesh and edit a template /create a new template in this instance, would be as easy as exporting your new mesh (lets say it's named m_i_crate_new-crate-01) and editing the template (you would search for the template crate_glb in CDimage\ world\ contentdb\ templates\ regular\ interactive). Simply rename the mesh from "m_i_glb_crate" to "m_i_crate_new-crate-01".

  2. To Export your new mesh and overwrite and existing one, simply locate a mesh and overwrite it with your own mesh (you might want to back up the current mesh first, so you can always get back to the original at any time).
Testing In Dungeon Siege
To test out your object, you will have to add it in a map through Siege Editor. To create your own map, refer to these helpful resources:
  • Siege Editor
  • Siege Editor Manual (siege_editor_manual.htm comes bundled with the Siege Editor)
Editing and creating templates can be a pretty extensive endeavor, but understanding how to do this is a powerful skill. The more template knowledge the mod maker has, the better. You can get a pretty good understanding how the art pipeline works. I highly recommend it.

Conclusion
<Back to Table of Contents>

Exporting weapons and objects is a process that can easily be grasped after a bit of experimentation. The export process mainly involves adding a few key components in gmax™ and then exporting your weapons and/or objects into the game with a basic understanding of the naming conventions and template structure.

Just to recap, a weapon involves adding a weapon grip, traces, and a dummy root. The placement of the grip will determine where on the weapon your character grasps it, the weapon traces will determine where the traces originate from when they are drawn on the weapon, and the dummy root will determine where the weapon sits relative to the ground when dropped.

An object export is less complex than a weapon. An object only requires the addition of a dummy root. The dummy root acts as its identifying pivot point for the object when exported from a file with multiple objects in a gmax™ scene.

The example sword and crate are good starting points to get you going on editing and creating content. Reading information about the naming key (.NNK), the basics of building a test map with Siege Editor, overview of templates (Siege University 201), and Help within gmax™ will get you well on your way with your Dungeon Siege editing. Good luck and happy modding.

Appendix A: Troubleshooting
<Back to Table of Contents>

  1. Pivot Points: You might run across a problem of your weapon or object showing up in the Anim Viewer Previewer Mode possibly turned around, flipped upside down, or a combination of the two. Yet in your gmax™ scene, everything looks fine. You have your dummy root or pivot set where you want, so why is it not coming in correctly? My best advice is to check and adjust the rotation of the pivot point of your item (or the pivot point of your dummy root). This will require a little trial and error to get it right. Within gmax™, go to Help > User Reference > and search for "adjusting pivots."

  2. Texture: If you Preview your item and it shows up with a "black and yellow construction tape texture", this is Dungeon Siege's way of telling you that you have a texture error. Also, if your texture is not a power of 2 or if it exceeds the size limitations (256 x 256), or has other critical errors, you will get a handy error that will pop up letting you know.

  3. Helpers: (unlinking). When adding helpers to your weapon or item, if you have cloned the item, you need to "unlink selection" so it is no longer linked to the original item's attachments (if they had been added). If you do not unlink your cloned mesh, you will get an error that a "grip" or other attachment is already attached. When it is actually looking at the original helper attachment. For more help on linking in gmax™, go to Help > User Reference > and search for "Linking and Unlinking Objects".

  4. Hierarchy: (more linking). It is important that your weapon and object are linked up to their helpers/components correctly. The images in the weapon and object sections of this tutorial will show you the correct hierarchy setup. For help with linking in gmax™, go to Help > User Reference > and search for "Linking and Unlinking Objects".

Appendix B: Siege Max Sample Files
<Back to Table of Contents>

Included are a few examples of both weapon and object sample files to help get familiar with the Siege Max tools. These are located in: \gamepacks\SiegeMAX\Scenes\ArtAssets\Objects within the gmax folder.

Weapons (sword)

  • m_w_swd-sample-A.gmax
    File containing a single sword mesh; complete with weapon grip, dummy root, and traces attached. This is ready for export.
  • m_w_swd-sample-B.gmax
    File containing a single weapon without any export preparation features. You can add the necessary export features, and then export.
  • m_w_swd-sample-C.gmax
    File containing a single weapon mesh, un-textured. You can texture, prepare for export, and then export.
  • m_w_swd-sample-D.gmax
    File containing all three sword meshes. This is an example of multiple weapon meshes within one gmax™ scene. You can texture, prepare for export, and then export.

    *You can also add more sword meshes to any of these gmax™ scenes and export. For example, you could have one "master sword gmax file" and have all your swords within this file and export them all from one gmax™ file instead of multiple gmax™ files.
Objects (crate)
  • m_i_glb_crate-sample-A.gmax
    File containing a crate with added dummy root, ready for export. This is ready for export.
  • m_i_glb_crate-sample-B.gmax
    File containing an un-textured crate minus the dummy root. You can texture this mesh, prep, and export.
  • m_i_glb_crate-sample-C.gmax
    File containing multiple crates ready for export. This is an example of multiple objects within one gmax™ scene. You can export these meshes.

    *You can also add more crate meshes to any of these gmax™ scenes and export. For example, you could have one "master crate gmax file" and have all your crates within this file and export them all from one gmax™ file instead of multiple gmax™ files.

Appendix C: Helpful Resources
<Back to Table of Contents>

Here are some helpful sites. I'm sure you recognize PlanetDungeonSiege and DungeonSiege.com from earlier on in this article, but also be sure to check out Game-Editing.net as an excellent resource for editing Dungeon Siege content. Also, Polycount is a great site for news and help on modeling and texturing. Be sure to check out the forums too.
  • PlanetDungeonSiege
  • PlanetDungeonSiege Forums
  • DungeonSiege.com Siege University
  • Game-Editing.net Forums
  • Polycount - (general help on modeling and texturing, also visit the forums)

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_216 Siege Max: Terrain

Siege Max: Terrain

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

What This Tutorial Will Teach You

Siege Max Setup

Node Basics

Doors

Floors

Water

Vertex Normals

Cleanup And Round Off

Siege Node Tool Kit

Doors

Floors, Water, Ignored, Locked, No Round

Making Changes To The Mesh After Creating A Siege Node

Practical Exercises

Dooring and Flooring

Flagging Faces As Water, Locking And Rounding Off Vertices

Ignoring Faces And Flagging Vertices Not To Round Off

Sample Node Set

Conclusion

Introduction

Siege Nodes are the geometry that makes up the terrain of Dungeon Siege. A Siege Node could be a house, a bridge, a river, a dungeon feature or just a simple flat plane. The following tutorial will lead you through the process of exporting a node and will highlight topics related to how a node should be constructed for best results.

Examples of nodes.

For the purposes of this tutorial we're assuming that you've already taken the time to learn the basics about creating models and applying texture maps. gmax™ comes with a very nice set of basic tutorials to get you up to speed on these things. We'll be focusing on what's needed to get terrain nodes working in Dungeon Siege.

What You Need For This Tutorial
  • Dungeon Siege v1.1 or newer
  • Dungeon Siege Tool Kit (includes Siege Editor and Siege Max)
  • gmax™
    • For further help in gmax™, first download the help files.
    • In gmax™, please refer to Help > User Reference.
  • The tutorial files for this course, located in your \gmax\gamepacks\SiegeMax\Scenes\ArtAssets\Terrain folder.
What This Tutorial Assumes You Have Already Learned
  • Siege University: 211 - Naming Key
What This Tutorial Will Teach You
  • Basic concepts of node construction
  • How to designate doors and walkable areas
  • Properties and benefits of normals and smoothing
  • How to use the Siege Node Tool Kit
  • Exporting your nodes to Dungeon Siege
Siege Max Setup

When constructing a node there are a few guidelines to adhere to. First and foremost, make sure you have your units in Siege Max set up properly. Your units should be in meters under "Customize -> Units setup…" and under "Customize -> Preferences…" set the "System Unit Scale" so that 1 Unit = 1 millimeter.

The textures that the node uses should be in:

\gmax\gamepacks\SiegeMAX\Scenes\ArtAssets\bitmaps
and named properly (see Siege U: 211 - Naming Key).

The node must have "t_" in the beginning of its name ("t" is for terrain). Be sure to add some special personal label to your node name so that it is unique from all nodes created by everyone (i.e. t_joesnode-floor-a). Only use an underscore after the first "t" and nowhere else.

Node Basics
<Back to Table of Contents>

The terrain is split up into nodes for two reasons. One is so that the engine can load in the nodes quickly for streaming and the other is to allow flexibility when creating landscapes.

When creating nodes for Dungeon Siege we discovered that things were easier when we made the node work as modular as possible. Early on we constructed big custom scenes out of one mesh and then carved that mesh up into smaller sections. This was like a jigsaw puzzle where every piece had its own place and could not be used anywhere else. Because of this, making any fundamental design changes to the scene required a lot of work, sometimes redoing things from scratch.

Figure 2 - Jigsaw puzzle construction approach.

A better approach is to think of the nodes as building blocks that can be arranged multiple ways to create many different layouts. Instead of a jigsaw puzzle approach, the nodes became more like Legos™. This required a bit more planning on how the pieces fit together, especially to minimalize texture seams, but it was well worth the extra effort.

Figure 3 - Modular rock wall nodes.

The best guideline for knowing if your node will connect properly with other nodes is to build stuff out of nodes in Siege Max itself. If things don't line up properly in Siege Max, they won't work in the Siege Editor. Use Siege Max as your testing ground first.

It is very important to keep your nodes on a grid when working in Siege Max. A good technique is to find a good ratio of node sizes that work well both in creating regions and streaming in the game itself. Our average node is about 4 meters square. In making sets we stuck with various ratios that included 2,4,8,12,24 and 32 meters.

We also matched our texture mapping to these ratios. A 4x4 meter floor is mapped with a 128x128 pixel texture map while an 8x8 meter floor is mapped with a 256x256 texture map. The pattern follows logically from there so that a rectangle that's 8 meters tall and 24 meters wide will be mapped with two 256x256 textures or one 256x256 texture tiled twice.

Once you have your node or set of nodes built and can make a scene out of them in Siege Max, you are ready to export it for use in the Siege Editor. The exporting process consists of defining the node connection edges ("doors"), the walk able surfaces ("floors"), water for triggering special effects, and setting up vertex normals for accurate lighting between nodes.

Doors
Nodes connect along a common edge that you define. Two nodes share a common edge when the vertices that make up that edge line up perfectly. This is an important point: If the vertices don't line up perfectly then you will get cracks between the nodes when connecting them in the Siege Editor.

The edge that nodes can connect to is called a "door". Any edge in the mesh can be used as a door connection with another node, not just edges that are on a boundary. A door can be defined by one edge or multiple edges, though multiple edges must be continuous without a gap or break.

Figure 4 - Example of node with three doors defined.

A door's primary attribute is its direction. The direction that a door faces is illustrated with a yellow line after the door has been defined. Two nodes will connect properly if their respective door directions point to each other.

Floors
Faces in the node that the characters can walk on are defined as "floors". The only rule that applies to floors is that you cannot have faces that overlap each other on the z-axis within the same node. If you want to overlap two floors on the z-axis, the respective faces must be in separate nodes.

Figure 5 - Example of node with faces flagged as "floor".

Water
Water is defined much the same way as floors. You flag individual faces as "water" so that the game engine knows to draw special splash effects when characters walk through them. The flagging of faces only applies to the special effects, so you still need to actually create polygons that are mapped with textures that look like water.

Vertex Normals
Each vertex has a normal. You can see this normal by marking the checkbox "show normals" under the Vertex Selection rollout. Siege Max uses this normal to calculate smoothing groups. Smoothing groups are used to calculate how faces will shade when lighted. Vertex normals that are generally parallel to each other will fall into the same smoothing group. Normals that don't point in the same direction within a given threshold will fall into two separate smoothing groups.

Having control over vertex normals is important when creating nodes because it ensures that lighting will be consistent across two attached nodes. Generally this isn't a problem if the two nodes have edge vertex normals that have similar trajectories, such as two flat floor planes. Vertex normals become an issue is when the two nodes have curved edges and you want light to shade across the seam consistently, like joining the left and right sides of a split cylinder.

Figure 6 - Joining two hemispheres of a cylinder causes a shading seam.

To clarify, smoothing information isn't automatically translated across two separate nodes that are joined together in the Siege Editor. If the faces that make up the two door edges are close to parallel, such as two flat planes connecting, then there probably won't be an issue with smoothing. If the two nodes connect on a curved surface like the above cylinder, the Siege Editor will not be able to smooth the seam properly because the default smoothing value does not take in account the continuation of the curved surface. To remedy this problem, Siege Max has a couple tools that allow you to force what the smoothing information will be at a door edge.

Siege Max comes with two tools that allow you to define vertex normals: "Ignore Faces" and "Lock Vertex Normals"

"Ignore Faces" is a powerful tool that lets you select faces in a node that will be ignored by the exporter, but it still keeps the smoothing information of those faces. Ignored faces won't draw in the Siege Editor or the game. The smoothing information of those ignored faces still applies to the lighting calculations. Using this tool, you can attach extra faces to the edges of a given node and that node will light as if those faces existed.

Figure 7 - The white areas represent faces that are flagged as "ignored". These ignored faces won't be exported with the rest of the geometry, but their smoothing information will. Thus when the two hemispheres of the cylinder are joined, the surface will shade properly.

"Lock Vertex Normals" lets you select vertices and force their normals to be parallel to the z-axis. A good example for using this tool would be to lock the joining bottom edge vertices of a curved wall to a flat floor plane.

Figure 8 - The bottom vertices of the curved wall are a good example of ones that should be flagged as "locked" so that the wall will shade smoothly with the joining floor.

Cleanup And Round Off
<Back to Table of Contents>

Cleanup and Round Off is a very important feature in the Siege Node Tool Kit. What this function does is move all vertices to the nearest .01m grid point. This will help ensure that door vertices are exactly aligned. This function will work properly only if your nodes are on a grid to begin with.

Siege Node Tool Kit
<Back to Table of Contents>

Siege Node information is stored as a modifier labeled "Siege Max SNO". This modifier behaves like other gmax™ modifiers. Collapsing the stack will delete any Siege Node information. You activate the Siege Node Tool Kit floating panel by pressing the "Open SNO Tool Kit" button at the bottom of the Siege Max SNO rollout or by selecting it in the right-click menu after selecting a node. There are two major fields in this panel: "Siege Node Doors" and "Siege Node Tools". Siege Node Doors is used to define and modify door flags while Siege Node Tools contains five tools for flagging the rest of the Siege Node information.

Figure 9 - The Siege Node Tool Kit

Doors
Selecting a node and clicking the "Edit Doors" button activates the "Siege Node Doors" panel. This will change the selection level to Edge and allow you to begin selecting edges to define as doors. The "Edit Door" button is a toggle that can be turned on/off.

"Num Doors" identifies how many doors the node currently has defined.

"Selected Door" identifies which door is currently selected.

The slider bar is a quick and easy way to select doors.

"Append New Door" button defines the selected edges as a door. The number on the button will be the new door's identifying number.

"Insert New Door" will insert a door into the current sequence of doors. The number on button will be the newly inserted door's identifier. All doors following the newly inserted door will be renumbered accordingly.

"Delete Door" will delete the current door. The doors following the deleted door will be renumbered accordingly.

"Flip Door" will flip the direction the selected door 180 degrees.

"Delete All Doors" will delete all dooring information from the selected node.

Floors, Water, Ignored, Locked, No Round
The bottom panel contains the functions floor, water, ignored, locked and no round. These functions share the same three buttons of "Add Selected", "Remove Selected" and "Remove All". These buttons are self-explanatory and will be covered in greater detail in the following tutorials.

Once you have defined all the applicable properties to the node you are ready to export it to a .sno file for use in the Siege Editor. With the node selected, bring up the right click menu and select "Export Siege Node". The .sno file will be exported to this directory:

\gmax\gamepacks\SiegeMAX\Scenes\ArtAssets\meshes

and will have the same name as your mesh object in Siege Max.

Making Changes To The Mesh After Creating A Siege Node
<Back to Table of Contents>

Because the Siege Max SNO is a modifier, it behaves like any other gmax™ modifier. You can add modifiers above or below the Siege Max SNO modifier and collapse modifiers around it. You cannot collapse the Siege Max SNO modifier; this will delete all Siege Node information. Adding or deleting vertices is acceptable, but you may alter the position of some of your flagged information because these are referenced to specific vertex, edge or face identification numbers. If this number shifts, so will your flagged information. A good rule of thumb is that adding vertices, edges or faces will most likely be OK, while subtracting vertices, edges or faces won't.

Practical Exercises
<Back to Table of Contents>

Dooring And Flooring
In this exercise we will add doors and floor flags to a sample node. To begin, load "Tutorial_Floor.gmax" into Siege Max. This is a simple 4x4 meter node made with eight faces. It is named "t_dgn-floor-04x04-a". The "t_" labels the node as a terrain piece and makes it valid for exporting. The following labels describe the nodes function: "dgn" for a dungeon piece, "floor" for a floor piece, "04x04" for the size of the node, and "a" to allow for iterations of the type of node; in this way we could build a similar node and use the same name only with a "b" at the end of the name. This is useful if we have two nodes that have the same function (a 4x4 dungeon floor), but maybe have difference textures or other features.

Figure 10 - The sample floor node "t_dgn-floor-04x04-a".

To begin with, make sure Siege Max's main viewport is set to view in wireframe mode with "Shade Selected Faces" toggled on (the default hotkey toggle is F2).

Select the node and add the Siege Max SNO modifier. Press the "Open SNO Toolkit" button at the bottom of the rollout.

We will start by defining doors. Click the "Edit Doors" button at the top of the panel to activate the dooring tools. The editable mesh selection will be automatically switched to Edge. Select the bottom two edges and click the "Append New Door #1" button.

The edge is now defined as a door. You will notice that the vertices that make up the door are labeled with green triangles and the first and last vertices are connected with a blue line with a smaller yellow line perpendicular to it. This yellow line points in the direction the door is facing. The door is also labeled with a red number, in this case a "1". This is the number for this door and it is the same number used in the Siege Editor to select doors for connecting.

Figure 11 - The node with Door #1 defined.

Next, select the two edges on the right side of the node and press the "Append New Door #2" button. The new door is similar to the first, except this door is labeled "2".

Now select the top two edges and press the "Append New Door #3" button. This new door is now labeled "3". With door #3 still selected, press the "Flip Door #3" button. You will notice the yellow line flipped 180 degrees to point in the opposite direction. If we would keep this orientation for the door, any node connecting to it on a door that faces outward would overlap this floor node, since doors connect facing opposite of each other. We don't want this so press the "Flip Door #3" button again to put the door back to its original orientation. This exercise was just to show the flip door functionality. Occasionally, based on the geometry of a given node, a door may default facing a direction opposite to what you want and the flip door function lets you correct it.

Figure 12 - An example of Door #3 (the door in the first image) flipping 180 degrees, before and after.

Using the slider bar, select door #2. You will know door #2 is selected when it's highlighted red. Now press the "Delete Door #2" button. The door will be deleted and you will notice that what was door #3 is now door #2.

Next, select the two edges on the left side and press the "Insert New Door #2" button. You now have this new door labeled "#2" and the top door has been changed to door #3.

Finally, select the two edges on the ride side and press the "Append New Door #4". This node is now doored with four doors in a clockwise rotation.

Figure 13 - Node with four doors flagged in a clockwise order.

We will now add floor flags to the faces of the node to finish up. Make sure your node is still selected and press the "flr" (floor) button on the bottom panel. Your sub-object mode is now switched to face level and all the buttons in the Siege Node Tools are now activated. Select all the faces of the node and click the "Add Selected" button. The value in the "Num Floor Faces" field will be 8. This is a good time to try out the other two buttons as well. Select one of the faces and press the "Remove Selected" button. The shaded selected faces will update to show the face you selected being unfloored and the number of floor faces is now 7. Now try out the "Remove All" button. All the floored faces of the node are now removed. Select all the faces again and press the "Add Selected" button to re-floor the node.

Now that your node is doored and floored, right click the node and select the "Export Siege Node" function to export the node to a .SNO file. That's it! You've just created a Siege Node.

Figure 14 - Finished node floored and doored.

Flagging Faces As Water, Locking And Rounding Off Vertices
In this exercise we will introduce the water flag along with locking vertices and using the round off function. Begin the tutorial by loading "Tutorial_Water.gmax" into Siege Max.

Figure 15 - Tutorial_Water.gmax.

Select the node "slope-water-a". To begin with, we need to change the name of the node. Rename the node "t_joenode-slope-water-a". The "joenode" part is a unique name that you will give all your new nodes to differentiate them from nodes that others have created. Instead of "joenode" use a name you have made up.

Right-click on the node to bring up the Siege Max's quad menu and select "Open Siege Node Toolkit".

We will begin by making sure all the XYZ positional values of the node's vertices have been rounded off. With your main viewport set to wireframe, press the "Cleanup and Round Off Siege Node" button. You will notice several of the vertices moved. All the vertice's XYZ positions on the grid have been rounded off to the nearest .01m.

Now we'll flag the faces on the node that are textured with a water bitmap. Click the "wtr" (water) button. Your Editable Mesh sub-object selection is now set to the Face level. Scroll the editable mesh modifier rollups down to the "Material" field and click the "Select By ID" button. Choose ID 2 and press "OK". All the faces textured with material #2 in the node's multimaterial are now selected. Press the "Add Selected" button in the Siege Node Took Kit. The number of water faces is now 20.

Figure 16 - Location of the "Select By ID" field.

Figure 17 - Node with water faces selected.

Now press the "flr" (floor) button and use the same method outlined above to select by material ID number all faces textured with material 1. Press the "Add Selected" button in the Siege Node Tool Kit.

Our next step will be to flag all the vertices on the top and bottom boundary edges as locked. To illustrate why we do this, select the vertex on the bottom left corner and the vertex to its right (vertex#47 and vertex #41). Put a check mark in the "Show Normals" box and change the scale value to 5000. If you view the node from the left or right side you can easily see that the normals' angle are not the same. We want all the normals of the top and bottom edge to point straight up, parallel to the z-axis. This is the same direction the normals will point on a flat floor node.

Figure 18 - Vertices with their normals shown.

To do this, select the "lck" (lock) button on the Siege Node Tool Kit Panel and then select all the vertices of the top and bottom edges of the node. Now press the "Add Selected" button. All the vertices are now marked with a yellow triangle and the Num Locked Normal Verts should be 10. The tool doesn't adjust the normals in Siege Max, but they will export out with the newly adjusted angle, one that is parallel to the z-axis.

Figure 19 - The locked vertices are labeled with yellow triangles.

The last step is to flag the boundary edges as doors. Use the screenshot below for your guideline. When you are done, the node is now ready for exporting.

Figure 20 - The node has six doors labeled counterclockwise starting from the bottom left corner.

Ignoring Faces And Flagging Vertices Not To Round Off
In this exercise we will introduce the ignore faces functionality along with how to flag vertices so they don't move when the Round Off Siege Node tool is used. Begin the tutorial by loading "Tutorial_Archway.gmax" into Siege Max.

There are two nodes in this scene: "t_dgn-wall-archway-bottom-a" and "t_dgn-wall-archway-top-a". You will notice that when you view them in smooth + highlights mode there are parts of the node textured white. These are the faces we will be ignoring when exporting. We have the top of the archway separate because we want to be able to fade it off when a character walks through the archway. We include these faces in the model because we want the smoothing information they provide.

Figure 21 - "t_dgn-wall-archway-bottom-a" and "t_dgn-wall-archway-top-a"

Begin by selecting the "t_dgn-wall-archway-bottom-a" node and select all the faces that are assigned material ID # 8. All the white faces should be selected. Now hide these faces (first major button on the "Selection" rollout). You will see that there are faces that cap off the gap of the hidden arch top. You can select these faces and move them around to see that they are not welded to the rest of the geometry. This is because if we were to weld the vertices to the rest of the archway, we would create bad geometry where three faces would join on a common edge. This isn't allowed since Siege Max won't know how to shade such an instance and otherwise it's just sloppy modeling. Having the capped faces attached but not welded will work fine for what we want. Undo any moving of faces you might have done and unhide all the hidden faces.

Figure 22 - Node with capping geometry when the top is hidden.

With "t_dgn-wall-archway-bottom-a" still selected, open up the Siege Node Tool Kit. Press the "ign" (ignore) button in the "Siege Node Tools" panel. Now select all the white faces either by hand or by using the Select By Material ID method. Once you have them selected, press the "Add Selected" button. The number of ignored faces should now be 46.

Now press the "flr" (floor) button. If you examine the geometry of the floor of the archway node, you will see that there are extra faces that are close to the wall. This is done because we don't want those faces that hug the wall to be walkable. If these extra faces were not there, then a character would be able to walk right up to the wall and in some cases be able to have parts of its body go through, or clip, the wall. To minimalize this problem, we added the extra faces to put a bit of a barrier between the wall and a character.

Select all the faces of the floor except those that hug the wall and use the "Add Selected" button to flag them as floor. Your selected faces should look like the screenshot below.

Figure 23 - Faces that should be floored.

Now press the "Edit Doors" button and door the node like the screenshot below. Notice the last door that will be the connection edge for the top of the arch. Selecting this door could be tricky since you need to select this door and not any of the edges of the capping geometry.

Figure 24 - Don't forget the seventh door at the top of the node.

Now press the "Edit Doors" button again to toggle it off. Select the "t_dgn-wall-archway-top-a" node and click the "Edit Doors" button once again. Using the buttons in the Siege Node Tool Kit in this way (as toggles) lets you move from node to node to flag properties without having to close the Siege Node Tool Kit and reopen it for each node.

Now press the "nrd" (no round) button. The selection sub-object level will switch to vertex. We want to flag some of the vertices of the actual archway not to move when we cleanup and round off the node. This functionality is useful if we want to preserve the geometry in a node and not have it move when rounded. Common examples of needing this are for curved surfaces or angles were the points can't be on a nice, even XYZ position. This isn't recommended for vertices that make up a door edge, but it is allowed so use at your own risk.

Now select all the vertices that make up the top part of the curve of the archway, just like the screenshot below.

Figure 25 - Select the vertices at the top of the arch.

Press the "Add Selected" button. All those vertices should now be tagged with red triangles. Press the "Cleanup and Round Off Siege Node" button. The flagged vertices did not move even though they have XYZ positions that could be rounded off.

Now press the "ign" (ignore) button. Select all the white faces either by hand or by using the Select By Material ID method. Once you have them selected, press the "Add Selected" button.

We will now define a door connection. Select "Edit Door" and door the edge that matches the connecting edge in node "t_dgn-wall-archway-bottom-a". This door is facing the same direction as the one in the bottom piece, which won't work for us since that would make the top connect up wrong. We need this door to point in the opposide direction as the door in "t_dgn-wall-archway-bottom-a". Press the "Flip Door #1" button to flip the direction of the door 180 degrees (facing the opposite direction as the door in the bottom node). The screenshot below is how it should look.

Figure 26 - The door for "t_dgn-wall-archway-top-a" should point to the left.

You now can export each node.

Sample Node Set
Included with the tutorial files is a sample dungeon node set called "Tutorial_Dungeon.gmax" This file contains 13 nodes that are the very basic pieces for making a dungeon including floors, corners, doorways and different wall variations. These are the same nodes used in the game so take some time to study how they were constructed, how they were texture mapped, what kind of textures were used, and how they were flagged with the Siege Node Tool Kit. Also pay attention to how the nodes are named and how they list in alphabetical order when the "Select By Name" button (found in the main toolbar) is pressed in Siege Max. It is highly recommended that you use the nodes for practicing your texturing abilities along with using the Siege Node Tool Kit. Feel free to add pieces to the set to expand it and give it more functionality, such as making a stairway or adding pillars to a floor node. Try to use the premade textures as a template for creating your own dungeon look. Have fun!

Figure 27 - Sample dungeon node set.

Conclusion
<Back to Table of Contents>

The information outlined above is all you need to know when exporting a mesh from Siege Max to a usable node for the Siege Editor. The process of defining Siege Node properties is straightforward and is consistent from one node to the next. The only area that is open to subjectivity is building nodes themselves.

There were over 3800 individual nodes created for Dungeon Siege ranging from very simple flat floor planes that were a couple faces all the way to complex sections of a custom terrain feature that required pulling every trick to get it to work properly. There is no single right way to build a node and you are limited only by your ingenuity and imagination.

We highly recommend taking a look at the nodes we made and study how they were constructed. Pay close attention to how we used ignored faces, how we divided edges to support walk able surfaces and keep character from walking part way into walls, why we separated nodes a certain way to facilitate fading, and how we UV mapped nodes so that bitmap resolutions were consistent.

The best advice we can give you is to start off with very simple geometry. Keep your sets as basic as possible and take baby steps until you get a feel for how the Siege Node tools behave. Work with prototype geometry to see how things connect and whether they light property. Don't worry about making the nodes look good until you know they are fundamentally sound and functional in Siege Edit. This will save you much frustration and hair pulling in the end.

Good luck and happy node building!

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_217 Actor and Party Commands

Actor and Party Commands

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

AI Commands

Some Definitions

Catalyst AI Commands

Target AI Commands

Manipulation Commands

The Alignment Changer (cmd_alignment_changer)

The Animation Command (cmd_animation_command)

The Inventory Changer (cmd_inventory_changer)

The Selection Toggle (cmd_selection_toggle)

Party Commands

The Party Move Gizmo (cmd_move_party)

The Party Stop Gizmo (cmd_stop_party)

The Party Wrangler Gizmo (cmd_wrangle_party)

Conclusion

Introduction

"Actor and Party Commands" refers to the set of gizmos that specifically affect Actors and Party Members: either changing their AI behavior, modifying their properties, or manipulating their inventory. For the purposes of this tutorial, we'll divide these gizmos into three categories:
  1. AI Commands
  2. Manipulation Commands
  3. Party Commands
AI Commands are a set of gizmos that give basic "orders" to an Actor, including Party Members. The orders include "move", "attack target", "patrol", "equip", etc; and can be assigned either to a specific Actor or to a trigger catalyst. While there are many AI Commands, only a few of them were actually used in DS1.0, since more complex behaviors were handled in other ways (like skrit).

Manipulation Commands also affect Actors, but these gizmos affect the Actor's properties in some way instead of assigning AI behavior. These gizmos can change the visibility, hostility, invulnerability, and other properties of the Actor; and also manipulate the Actor's inventory. There are only a few gizmos in this category:
  • cmd_alignment_changer
  • cmd_animation_command
  • cmd_inventory_changer
  • cmd_selection_toggle
Finally, Party Commands assign behaviors to the entire Party, all at once, no matter where they are in the world. These three commands are used almost exclusively in Single Player:
  • cmd_move_party
  • cmd_stop_party
  • cmd_wrangle_party
What You Need For This Tutorial
  • Dungeon Siege v1.1 or newer
  • Siege Editor from the Dungeon Siege Tool Kit
  • The "map_world" map converted to bits
  • A test map to experiment with different commands
What This Tutorial Assumes You Have Already Learned
  • Siege University: 100 - The Basics of the Siege Editor
  • Siege University: 200 - Dungeon Siege Concepts and Terminology
  • Siege University: 201 - Templates
  • Siege University: 203a - Triggers I
  • Siege University: 203b - Triggers II
  • Siege University: 208a - Non-Player Characters

AI Commands
<Back to Table of Contents>

AI Commands are gizmos that can assign a wide variety of behaviors to any Actor, including Party Members. Most of these behaviors mimic the orders you can give to your Party Members with your mouse, like "move", "stop", attack", etc. These behaviors, or Jobs that the Actor performs, stack up in the Actor's Command Queue for execution.

AI Commands are assigned to Actors in one of two ways:
  1. Gizmo assigns a Job to the catalyst of a trigger (Catalyst AI Commands)
  2. Gizmo assigns a Job to a specific Actor designated by SCID (Target AI Commands).
Both sets of gizmos are virtually identical, with minor exceptions, and have the same component block that defines their properties: the [cmd_ai_dojob] block.

We'll talk about Jobs, the Command Queue, and the [cmd_ai_dojob] block in the section below.

Some Definitions

The AI Job

First, let's look at the "AI" definition as listed in Siege U 200, specifically pulling out the "Jobs" description:
Jobs: The lower level of AI. Jobs are responsible for handling specific functions, using chores to accomplish those tasks. Jobs manage:
  • Movement: Following, moving in formation, guarding, moving
  • Attacking: How to get there, what animations to use
  • Item manipulation: Getting items, dropping, equipping
  • Talking to other actors: Selecting the appropriate conversation, syncing dialog and animations, playing any special actions that occur as a result of the conversation
  • Other actions: Using, drinking, going unconscious/conscious, special monster behaviors, fleeing, dying
The thing to take away here is that when an Actor is performing an action, he is doing a "Job". Jobs have names like "jat_move" and "jat_attack_object", and every Actor template associates these names with particular AI skrits (place any Actor in SE and look at its [mind] block). The AI Command gizmos tell the Actor to run one of these skrits. The Actor must support the Job that a gizmo attempts to assign.

For this lesson, there are really only two things to remember about Jobs:
  1. AI Command gizmos assign a specific Job to an Actor, and
  2. Incoming Jobs are placed into an Actor's Command Queue for execution.
The Command Queue

The Command Queue is the list of internal queued Jobs that an Actor uses to drive its behavior. The Actor will execute the first Job in its queue, then the second, and so on, until all Jobs in its queue are completed; then the Actor will revert to its default AI. An easy way to see this in action is to select a Party Member within the game, hold down SHIFT, and click several times on the ground. The Party Member will travel to the first click location (a Move Job), then the second, and so on, until the sequence is complete.

During the move sequence, the Party Member will not do anything else except move to the waypoints, even if an enemy attacks. This is because Player-given commands are not interruptible, i.e. the Party Member is incapable of making a different AI decision while under the influence of a Player command. AI Command gizmos are by default non-interruptible, but this property may be changed in the gizmo instance.

Speaking of the gizmo instance…

The [cmd_ai_dojob] Block

Do the following:
  1. Open your test map in SE
  2. Place a catalyst move command (cmd_ai_c_move)
  3. Place a target move command (cmd_ai_t_move)
  4. Look at the [cmd_ai_dojob] block in the properties of each gizmo.
Even though there is a lot of confusing component fields, you might notice that the component fields are identical between the two gizmos. When using AI Commands, there are only a few of these fields you will ever need to use:
  • next_scid: (SCID) The SCID of the next linked AI Command, if any (see Linking Catalyst AI Commands and Linking Target AI Commands below).
  • placement: This field determines where in the Actor's command queue this command will be placed. There are 3 values of interest (qp = queue placement):
    • qp_front (default): The command is put the front of the queue, becoming the active command. All pre-existing commands are bumped down one slot, and they will still execute after the new command terminates.
    • qp_back: The command is put at the end of the command queue, and will execute after all pre-existing commands have terminated.
    • qp_clear: The command queue is cleared, and the new command is inserted as the only command in the queue. This is desirable when you do not want the Actor to return to any previous commands after the new one terminates (for example, let's say you have a bounding box that intercepts a moving Actor and tells him to go somewhere else. If qp_clear is not set, the Actor will arrive at the new destination, but then resume his previous move job and try to walk to the original destination. If the trigger was not one-shot, then the Actor can bounce back and forth indefinitely, until his original move job completes or is cleared).
  • target1: (SCID) Use depends on the gizmo. Usually is the target of an AI Target Command.
  • target2: (SCID) Use depends on the gizmo. Usually used when multiple targets need to be specified.
  • slot: This field is used when the command references an object the Actor is holding or wearing (i.e. cmd_ai_c_drop). (NOTE: To manipulate items in the Actor's inventory, use the "cmd_inventory_changer" instead, listed under Manipulation Commands, below.) The "equip slot" is the location of the specified object. Valid slot names can be found under the "Equip Slot" dropdown menu on the "Equipment" tab of any Actor's properties, or use the following list:
    • es_weapon_hand
    • es_shield_hand (default)
    • es_forearms
    • es_feet
    • es_chest
    • es_head
    • ring# (where # is 1-4)
    • primary_spell
    • secondary_spell
  • setJobTraitInterruptable: (true/false) If true, job is interruptible. If false, is not. Default: false.
Now that we've covered some common ground between Catalyst and Target Commands, let's go over them in detail.

Catalyst AI Commands

These Commands are found in SE under "Game Objects/Gizmos/Commands/ai/catalyst_commands".

Catalyst AI Commands are used to assign an AI behavior to an Actor. Instead of specifying a specific Actor in SE, you'll pass a catalyst to the Command with a Spatial Trigger (see Siege U 203A and 203B for information on catalysts and spatial triggers). The catalyst will be sent along with a "we_req_activate" message.

Do the following in your test map:
  1. Place a trigger_generic gizmo, then place a cmd_ai_c_move gizmo at some distance away.
  2. Give the trigger an "actor_within_sphere" condition, and send a "we_req_activate" message to the move gizmo.
  3. Paste the Condition ID of the sphere condition into the "Message Broadcast" field of the Action, or set the field to "all_conditions".
  4. Save Objects, and then run your map in game.
  5. Walk your character into the trigger area.
Your character should walk into the trigger area, and then, because he is now the catalyst of the spatial trigger, immediately walk to the location of the cmd_c_ai_move gizmo. (After arriving at the waypoint, your character may try to walk back to the point where you initially clicked. This is because your initial move order was not cleared; setting the "placement" value in the move gizmo to "qp_clear" should prevent this behavior.)

As previously mentioned, most Catalyst AI Commands were never used in the game. Practically the only Catalyst AI Commands used are the Move and Patrol commands. The other commands should work just fine, but since they haven't been used or tested for so long, your mileage might vary. Most of these gizmos are self-explanatory, especially since the "Documentation" fields do a good job of describing their function and use. Rather than duplicating these fields, we'll look at some of the special cases:

(NOTE: In the current SE version, the development name/description for all Catalyst AI Commands do not use the _ai portion of the actual template name. For example, the actual template name (and the name found in the .gas files) for "cmd_c_move" is "cmd_ai_c_move". This naming oversight will hopefully be fixed in a future SE version.)

Gizmo Notes:
  • cmd_ai_c_animate: This was only used a few times in DS, mostly because it was replaced by the "cmd_animation_command" gizmo. Use "cmd_animation_command" under the "specialized" subdirectory instead of this gizmo, which can support both catalysts and targets (see Manipulation Commands, below).

  • cmd_ai_c_attack_object: This command requires both a catalyst (derived from a spatial trigger) and a target (SCID of an object pasted into the "Target1" field). When activated, the catalyst will attack the specified object.

  • cmd_ai_c_drop/equip/give: Each of these commands references an inventory object that the catalyst drops/equips/gives. These objects must come from or be going to an equip slot. The SCID of the object is pasted into the target1/target2 field (depending on the gizmo), and the location or desired location of the object is entered into the "slot" field. Note that the "cmd_inventory_changer" duplicates much of this functionality, plus allows you access to regular inventory items (see Manipulation Commands, below).

  • cmd_ai_c_move: If multiple Actors are given this command, they will all move onto the gizmo, possibly standing on top of each other. If you want to move the Player's Party Members in formation, use "cmd_move_party" under the "specialized" subdirectory instead (see Party Commands below). Also, Actors will not attack enemies they encounter while moving to a cmd_c_move waypoint (use cmd_c_patrol instead for move-attack orders).

  • cmd_ai_c_move_orient: Same as above, except the Actor(s) will face in the direction of the gizmo's arrow upon completion of the command.

  • cmd_ai_c_patrol / cmd_ai_c_patrol_orient: Same as the above two gizmos, except that Actors will attack enemies along their route.
Linking Catalyst AI Commands:

Catalyst AI Commands can be linked--much like NIS cameras--resulting in the catalyst being passed from one Catalyst Command to the other, upon completion of the previous Command. In this way, a catalyst can be given new AI commands without needing a new spatial trigger to redefine the catalyst.

To link Catalyst AI Commands, either paste the next Command's SCID into the first Command's "next_scid" property, or right-click on the first Command, select "Link Object", and left-click on the second Command.

Target AI Commands

These Commands are found under "Game Objects/Gizmos/Commands/ai/target_commands".

Target AI Commands are used when you know the exact SCID of the Actor to manipulate. They are mostly identical to Catalyst AI Commands except they require the target's SCID be pasted into the gizmo's "Target1" field, and they do not usually require a catalyst. Actors without a SCID, like the starting player character or a monster from a generator (basically any Actor that was not placed within SE), cannot be the targets of a Target AI Command. Like the Catalyst AI Command, the Target AI Command executes when it receives a "we_req_activate" message.

Do the following in your test map:
  1. Place a trigger_generic gizmo and a cmd_ai_t_move gizmo.
  2. Place a dwarf worker NPC at some distance from the move gizmo (Game Objects/actors/good_characters/townspeople/regular/dwarf_worker).
  3. Give the trigger a "player_within_sphere" condition, and send a "we_req_activate" message to the move gizmo.
  4. Paste the SCID of the dwarf_worker into the "target1" field of the move gizmo.
  5. Save Objects, and then run your map in game.
  6. Walk your character into the trigger area and watch the dwarf worker.
When your character enters the trigger area, a "we_req_activate" message is sent to the move gizmo. Because the dwarf_worker's SCID is the target of the move gizmo, the dwarf_worker moves to the gizmo's location.

Gizmo Notes:

Most Target AI Commands are identical to their Catalyst Command counterparts, except they require a SCID in their "Target1" field.

Just like Catalyst Commands, many Target AI Commands were never used in the game. Practically the only Target AI Commands used are Move, Patrol, and Attack Catalyst.
  • cmd_t_animate: Use "cmd_animation_command" under the "specialized" subdirectory instead, which can support both catalysts and targets. NOTE: There are TWO instances of "cmd_t_animate" in the SE list as of the latest version (1.09.2.318). The second instance is named incorrectly-if you place it, you will see that it is actually "cmd_ai_t_die".

  • cmd_t_*_catalyst: These commands (attack, face, guard) require both a target (pasted into the "Target1" field) and a catalyst (sent from a spatial trigger). When activated, the target will perform the specified action on the catalyst.

  • cmd_t_die: Incorrectly listed (in SE v1.09.2.318) as a second "cmd_t_animate".

  • cmd_t_follow_catalyst: Incorrectly listed (in SE v1.09.2.318) as a second "cmd_t_follow".
Linking Target AI Commands:

Like Catalyst AI Commands, Target AI Commands can also be linked. However, the SCID of the target must be pasted into every gizmo in the chain-the next gizmo will not inherit the previous gizmo's SCID. Note that linked Target AI Commands do not have to refer to the same SCID; each successive linked gizmo can refer to an entirely different Actor. This is sometimes useful in timing events.

Manipulation Commands
<Back to Table of Contents>

These Commands are found under "Game Objects/Gizmos/Commands/specialized".

"Manipulation Commands" is just an arbitrary name to talk about a few commonly used gizmos that affect Actors. Rather than assigning an AI job, most of these gizmos change Actor properties in useful ways.

We'll talk about these four gizmos:
  • cmd_alignment_changer
  • cmd_animation_command
  • cmd_inventory_changer
  • cmd_selection_toggle
The Alignment Changer (cmd_alignment_changer)

The name of this gizmo belies the fact that it is a highly versatile and useful gizmo for Actor manipulation. It accomplishes many basic functions necessary to control Actor behavior, especially during scripted sequences. A single alignment_changer can only affect a single Actor.

In addition to changing the alignment of an Actor, the gizmo can also toggle the following:
  • Whether the Actor animates
  • Actor invincibility
  • Whether the Actor can be hit with weapons or spells
  • Whether the Actor can attack or be attacked
  • Actor visibility
  • Whether the Actor can be "selected" (i.e. can be interacted with by the mouse)
The alignment_changer can be set to one of three basic states at any time, where each state sets one or more Actor properties. The three states are keyed to the reception of the "we_req_use", "we_req_activate", and "we_req_deactivate" messages. There is no using, activating, or deactivating going on; these three messages only serve to change the state of the gizmo, and may as well be named "1", "2", and "3". The gizmo can receive any number of these messages in any order, changing the Actor's properties each time. Let's look at how this works, by checking out the cmd_alignment_changer's properties.

The [cmd_dumb_guy] Block

The [cmd_dumb_guy] block is the component block within the cmd_alignment_changer's properties that controls its function. After a field to identify the target Actor's SCID, there are three blocks of redundant fields, each setting Actor properties for the three messages (we_req_use, we_req_activate, we_req_deactivate). Finishing the block is a number of fields that allow/disallow types of property changes, as well as setting the initial state of the gizmo.

The Target
  • target_actor: SCID of the target Actor. This gizmo will begin affecting the Actor immediately once they are both loaded into the frustum, unless told otherwise (the "start_state" value).
The "we_req_use" State

The "we_req_use" state is commonly used to "suspend" an Actor as soon as he spawns, waiting for either an activate or deactivate message to change his properties. The default values for this state reflect this suspension, but the settings can be changed to any desired value. The Actor's properties will be changed to these values when the cmd_alignment_changer receives a "we_req_use" message.
  • alignment_spawn: (aa_good / aa_neutral / aa_evil) Sets the alignment of the Actor to this value when the gizmo receives a "we_req_use" message. The default for we_req_use is "neutral", ensuring that this Actor will not attack good or evil Actors, or be attacked by them. Default: aa_neutral.
  • life_state_spawn: (ls_ignore, ls_alive_conscious) This value affects whether the game engine considers the Actor to be "alive" or not. According to the engineers, this property was never meant to be exposed in a gizmo; it was supposed to remain an internal engine thing. As a result, nobody is entirely sure what the repercussions are of setting this to "ls_ignore" are. The good news is, because the "may_attack" and "may_attacked" flags now exist, it is never necessary to set this value to "ls_ignore" in order to force other Actors to ignore the target Actor. The "we_req_use" block sets "ls_ignore" by default however; you may consider setting it back to "ls_alive_conscious". Default: ls_ignore.
  • update_anim_spawn: (true / false) Determines whether the Actor can run animations. Default: true.
  • invincible_spawn: (true / false) Determines whether the Actor is invincible or not. Default: false.
  • collide_spawn: (true / false) Determines whether the Actor can be hit by weapons or spells. Default: true.
  • may_attacked_spawn: (true / false) Determines whether the Actor can be attacked by any other Actor. Default: false.
  • may_attack_spawn: (true / false) Determines whether the Actor can attack another Actor. Default: false.
The "we_req_activate" State

The "we_req_activate" state repeats the same properties as the "we_req_use" state with slightly different names ("*_activated" instead of "*_spawn"). The activated state is commonly used to turn an Actor into a normal, evil, attackable monster, and the defaults reflect this.

The "we_req_deactivate" State

The "we_req_deactivate" state repeats the same properties as the previous two states, again with slightly different names ("*_deactivated"). The default values are the same as the activated state: they set an Actor to normal, evil, attackable qualities.

Toggles

The final section of the [cmd_dumb_guy] block tells the gizmo which properties in the above three states to actually change, and which values to ignore. In this way you can limit the gizmo to only changing the alignment and nothing more. This block also allows you to set whether the Actor is selectable or invisible on a we_req_use, and lets you set which state the Actor spawns in.
  • change_alignment: (true / false) Allows/disallows changes to Actor alignment. Default: true.
  • change_life_state: (true / false) Allows/disallows changes to Actor life state. Default: true.
  • change_selection: (true / false) This value does not have a corresponding value in the above three states. If "true", this setting causes the target Actor to be non-selectable (i.e. you cannot interact with the Actor with the mouse, or even get mouse-over text) in the "we_req_use" state only. Default: true.
  • change_visibility: (true / false) Like the previous value, this does not have a corresponding value in the above three states. If "true", this setting causes the target Actor to be invisible in the "we_req_use" state only. Default: false.
  • change_invincibility: (true / false) Allows/disallows changes to Actor invincibility. Default: false.
  • change_collide: (true / false) Allows/disallows changes to Actor collision. Default: false.
  • change_may_attacked: (true / false) Allows/disallows changes to the "may_attacked" property. Default: false.
  • change_may_attack: (true / false) Allows/disallows changes to the "may_attack" property. Default: false.
  • start_state: (0-3) This is an important property, since it allows you to set which state the Actor will be in when he spawns. Default: 0.
    • 0: No change to Actor.
    • 1: The Actor will start in the "we_req_use" state.
    • 2: The Actor will start in the "we_req_activate" state.
    • 3: The Actor will start in the "we_req_deactivate" state.
How to use this gizmo?

This gizmo is used in NIS's all the time, and is useful for many scripted sequences. The most common way to use this gizmo is to change a speaking "evil" actor from aa_neutral to aa_evil after his speech (e.g. Gresh, Swanny, Gom). Another common usage is to make an Actor invisible and non-interactive until needed (e.g. the spider in the intro NIS, Norick, the Goblin Inventor). Once an Actor is finished with, you can make him invisible and non-interactive (e.g. the intro spider and Goblin Inventor again-in fact, there are three different Goblin Inventors in the SP NIS… one on the pipe, and two on the platform: one for going down, and another that appears when the Robosuit is destroyed. Each instance of the GI is made visible/invisible by a cmd_alignment_changer).

The Animation Command (cmd_animation_command)

The animation_command was created after the Catalyst and Target AI "animate" commands, and is a much more powerful gizmo. This command has already been touched on in the "NPC Animations" section of SU: 208A.

This gizmo is useful in calling specific animations for the target Actor. As mentioned in SU:208A, the Actor's base template must have the FourCC animation listed in its [chore_misc] block.

The [cmd_animation_command] Block

Many of the components of this block are adequately documented, and should make sense. Let's go over the more important fields:
  • animate_catalyst: (true / false) If true, the client_scid property is not used. Instead, any catalyst passed to this gizmo with a spatial trigger will be animated instead. Default: false.
  • chore: (chore block) Which [chore_*] block of the Actor's [chore_dictionary] to look in for the FourCC animation. This is almost always "chore_misc". Default: chore_misc.
  • animation: (FourCC animation) This field is where the FourCC animation code is entered. SE will convert this value into a number string. To see the original FourCC text description on later viewings, make sure the FourCC box is checked at the top of the Template Properties tab of the Object Properties window.
The Inventory Changer (cmd_inventory_changer)

This is another important gizmo, simply because it allows you to perform a drop, equip, or delete action on any object in an Actor's inventory--whether the Actor is a catalyst or a target, whether the object is equipped or sitting in general inventory, and whether you are targeting the object by SCID (specific object) or by template (specific kind of object). These qualities make this gizmo much easier to use than the Catalyst AI or Target AI inventory commands.

This gizmo only operates on a single object, even if the gizmo is targeting by template and there are multiple instances of the same templated object in the Actor's inventory. If you know the SCID of the object, it is better to target by SCID.

It seems that the inventory_changer is a one-shot gizmo, meaning that it will perform its action on the target object once only. During testing for this article, it was impossible to have the target Actor drop the same item more than once, even if he picked it up and walked through the "drop" trigger again.

The only bit of trickery in using this gizmo is in triggering it effectively. This gizmo absolutely requires a catalyst in order to operate; otherwise the inventory_changer doesn't know which Actor it should be operating on. We'll talk about this after looking at the [cmd_inv_changer] component block.

The [cmd_inv_changer] Block

This is a short and simple block.
  • object_scid: (SCID) SCID of the target object. Only used if "go_within_sphere" is false.
  • go_within_sphere: (true / false) If true, the "template_name" value is used to target the inventory object. If false, the object SCID is used for identification. Default: false.
  • template_name: (template_name) The template name of the target object. Only used if "go_within_sphere" is true.
  • msg_type: (message) A message to send to "msg_scid" when performing a delete job. This is useful when you want to delete an object from the Player's inventory and then make a corresponding object "appear" in the environment at the same time. Default: we_req_activate.
  • msg_scid: (SCID) SCID of the object to send the "msg_type" message to when performing a delete job.
  • job1: (drop / equip / delete) Which job to execute on the target object. These values are case-sensitive.
    • drop: Actor drops the target object onto the ground.
    • equip: Actor equips the target object.
    • delete: The target object is deleted.
Wiring the Inventory Changer

By itself, the inventory_changer does nothing. You can send it a we_req_activate message from a trigger, and nothing will happen. The inventory_changer needs to know where to look to find the target object, and so needs a catalyst-but there is some flexibility over who the catalyst actually is. This flexibility comes from the fact that we can derive a catalyst from either a normal spatial trigger (i.e. actor/player_within_sphere/bounding_box/node) or the "has_go_within_inventory" condition.

Wiring with a Normal Spatial Trigger

Let's look at an example:
  1. Place the following objects in your test map:
    1. 1 trigger_generic gizmo
    2. 1 cmd_inventory_changer gizmo
    3. 2 cudgels (Game Objects/weapons/clubs/cudgel) away from the trigger_generic
  2. Configure the inventory_changer:
    1. Copy the template name from one of the cudgels (cb_g_c_r_1h_avg)
    2. Open the cmd_inventory_changer and paste the value into the "template_name" field.
    3. Set "go_within_sphere" to "true".
    4. Set "Job1" to "drop".
  3. Configure the trigger_generic:
    1. Copy the SCID of the cmd_inventory_changer.
    2. Open the trigger_generic and add a "party_member_within_sphere" Condition.
    3. Send a "we_req_activate" message to the inventory_changer (paste the SCID).
    4. Set the catalyst for the trigger by setting the "Message Broadcast" field to "all_conditions" (or paste the Condition ID of the party_member_within_sphere condition here).
  4. Save the map and run it.
Once in the game, pick up one of the cudgels and walk into the trigger area. When you walk into the sphere, the cudgel should drop out of your character's inventory to the ground. Note that if you pick up the cudgel and walk back into the trigger, nothing will happen.

If you have the Release DS executable (the .exe with access to the dev console), try the following:
  5. Reload GOs ("reload gos" at the console)
  6. Mouse over valid terrain and add another party member ("add p farmboy" at console)
  7. Give each Party Member a cudgel.
  8. Walk one of the Party Members into the trigger area.
Only the character that walked into the trigger area drops the cudgel, because he is the catalyst of the "party_member_within_sphere" condition-the cmd_inventory_changer gizmo looks at the catalyst to see if the target object is in his inventory, and if it is, the catalyst drops it.

Wiring with "has_go_in_inventory"

Let's look at another more complex example:
  1. Open the same test map from the previous example.
  2. Add a new Condition: has_go_in_inventory. The "party_member_within_sphere" Condition is now the first in the list.
  3. Paste the template name of the cudgel (cb_g_c_r_1h_avg) into the "Template name filter" field of the "has_go_in_inventory" Condition.
  4. Change the catalyst by pasting the Condition ID of the "has_go_in_inventory" Condition into the Action's "Message Broadcast" field.
  5. Save the map and run it (or, if the game is still running, just type "reload gos" at the console to update the game to your latest map).
  6. Give one of your Party Members a cudgel.
  7. Walk the second Party Member (without the cudgel!) into the trigger area.
When the first Party Member walks into the trigger area, the second Party Member, who is not in the trigger area, will drop the cudgel.

The first Condition is true when a Party Member walks into the trigger area, but the second Condition is true when ANY Party Member (in SP) has the cudgel, and the trigger will not fire until BOTH Conditions are true. Because the catalyst has been set to the result of the "has_go_in_inventory" Condition, the cmd_inventory_changer gizmo will operate on the Party Member with the cudgel, whether or not he was the one who walked into the trigger area.

If you change the catalyst by pasting the Condition ID of the "party_member_within_sphere" into the Action's "Message Broadcast" field and repeat the above experiment, the second Party Member will NOT drop the cudgel. The catalyst is now the result of the sphere Condition, and that Party Member does not have a cudgel.

The Selection Toggle (cmd_selection_toggle)

This is a confusing gizmo, since it appears to duplicate part of the function of the alignment_changer. The difference is that the alignment_changer MUST operate on an Actor, whereas the selection_toggle can work on any object.

This gizmo is very similar to the alignment_changer, except that there are only two states instead of three, and you can define which messages toggle the two different states. The selection toggle can change the following properties for any object:
  • Selection: This sets whether the mouse can interact with the target object, including whether a mouse-over description is seen.
  • Usability: This sets whether an Actor can "use" the object, like a door or switch. The object is still visible; it just cannot be used as normal.
  • Visibililty: This sets whether the object is visible or not. This is the only gizmo that can toggle the visibility on a non-Actor object. Note that while an object can be invisible, it may still be selectable, and produce a mouse-over description and a cursor color change.
  • Invincibility: This sets whether the object takes damage or not.
The documentation on the gizmo component fields does a good job of explaining the particulars of the gizmo.

Party Commands
<Back to Table of Contents>

These Commands are found under "Game Objects/Gizmos/Commands/specialized".

There are three commands that fall into the "Party" category:
  • Party Move (cmd_move_party)
  • Party Stop (cmd_stop_party)
  • Party Wrangler (cmd_wrangle_party)
Each of these commands acts only on Party Members, but their behavior changes depending on if you are playing in Single Player (SP) or Multiplayer (MP).
  • In SP, every Party Member belongs to a single Party, so these gizmos act on every Party Member at once, regardless of where they are in the world.
  • In MP, each Player is their own Party, so these gizmos will only act on the Party Member who is the catalyst.
That being said, Party Commands are almost exclusively used in the SP game, specifically during NIS's. All Party Commands require a catalyst in order to function, and require reception of the "we_req_activate" message.

The Party Move Gizmo (cmd_move_party)

The primary purpose of this gizmo is to move the entire Party to a single point on the ground. The Party will orient themselves in a delta formation on arrival (preventing overlap), and they can be optionally made to face in a particular direction. All Party Members in the same frustum as the gizmo will receive the command.

This is the preferred method of forcing the Party to move to a spot, rather than the "cmd_ai_c_move" command, which results in unsightly overlap.

The [cmd_party] Block

The only parameter worth changing in the [cmd_party] block is the FacePoint parameter. If you want the Party to face in a particular direction on arrival, set this value to the SCID of an object within the frustum. The party will then face this object when they get to the Party Move waypoint.

Contrary to the existing documentation, the "move_to_point" functionality does not work, and should not be used. Setting "move_to_point" to true will result in the Party bunching up near the gizmo, and should be avoided.

The Party Stop Gizmo (cmd_stop_party)

The Party Stop gizmo is identical to the Party Move command in every way, except it orders Party Members within the same frustum as the gizmo to "stop" instead of move. Characters that receive a "stop" command will immediately revert to default AI and animations, just as if the Player had issued a "stop" command with the keyboard (S).

The Party Wrangler Gizmo (cmd_wrangle_party)

The Party Wrangler gizmo has already been covered in detail in the "Party Wrangler" section of SU 207: NIS.

Conclusion
<Back to Table of Contents>

We've covered the important Actor and Party Commands available for use within SE. While each of these gizmos is incredibly useful, many of them were not used much in DS1, because they were either tricky to use, did not always work, or nobody knew how they worked. A lot of more complex behavior ended up being driven by other systems, such as skrit.

Luckily, this document you are reading has a lot of information that just wasn't available while the game was being built, so you should have better luck! For most applications, these commands are highly versatile and will get you the behavior you want without too much complication. This document should give you a much-needed "leg up" on understanding how these commands work and how to implement them.

Good luck modding!

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_301 Intro to DS Architecture

Introduction to Dungeon Siege Architecture

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

Game Objects

GO Components

Streaming And The Frustum

Systems

Conclusion

Introduction

This article covers how objects in Dungeon Siege work. We will discuss how objects are defined, the different types of objects, how they are loaded, and the different systems in the game that can manipulate these objects. This article in conjunction with Siege University 200 should provide a good overview of how the game works. Hopefully this article will show you which systems do what; and when you start to write skrits it will give you a good idea where to look to accomplish different tasks.

What You Need For This Tutorial
  • Nothing. This is just an informational article.
What This Tutorial Assumes You Have Already Learned
  • Siege University 200: Dungeon Siege Concepts and Terminology

Game Objects (GOs)
<Back to Table of Contents>

All of the objects in Dungeon Siege are treated in a similar way. All of the monsters, heroes, even a spell in a spell book are all very similar. The differences between the GOs are the components that are attached to them. Attaching the physics component for example will allow an object to simulate moving through the air using physics. A GO without the physics component doesn't have this ability.
Organization of GOs
A good way to think of Game objects is to compare them to organisms. The Linnaean system sorts organisms into a tree hierarchy: they are spilt up into kingdom, phylum, class, order, family, genus and species. "Kingdoms" are the most broad category while "family" or "genus" are much more narrow. For example, animals are in the kingdom Animalia. Chordata (one of the phylums in Animilia) contains all of these creatures that also have a backbone-like structure (as well as a few other characteristics). The description of an organism gets more specific as you move down the tree. At the end it is describing just one organism instead of a group.

Just like an organism has DNA that tells how it is made, Game Objects have templates. These templates are also sorted into a tree. The high level templates are called primary branches or roots. The primary branch templates are: actor, interactive, non_interactive, trap, emitter, command, elevator, special, test, generator and ui.

All templates, primary branch templates included, are a set of components that define how a GO will act. Templates can be specialized to make a new template. A template that specializes inherits all of the attributes of the parent template; this new template can add new components, or change the values in the existing ones. There can be many levels of specialization.

For more information about templates, read Siege U: 201 - Templates. Most of the skrit written for Dungeon Siege took the form of writing special components to attach to GOs to give them special behavior.
When the game loads a GO, the process can be complicated. In a single player game it's pretty straightforward: the game loads a GO, and then it's ready for action. In multiplayer it's a little more complex: all GO creation is handled on the server, the server then tells the clients that need to know about a GO to create them as well. If two players are on opposite ends of the map, then they don't need to know about the objects that the other is seeing.

Some GOs don't ever need to be loaded on any clients. These are called server-only GOs, and these mainly only consist of control gizmos, or triggers. The game automatically detects if a GO can be a server-only GO. Some GOs always need to exist on all of the clients, even if they aren't in that client's frustum. These are called all clients GOs. The players and their inventories fall into this category. The last special type of GO is the omni GO. These aren't placed in the editor like a normal object; instead they are created or cloned in a special way from code or in skrit. These objects don't have a position in the world, so they can never be unloaded. These are useful to keep track of objects that are going to be moving a lot. These are used extensively by the spell system to keep track of enchantments on a GO.

Each component is more than just a list of properties; there are also functions in that component that can be called from skrit to affect that GO. For example, if you want to make a GO run a job, you would access the mind component of that GO and run it from there.

GO Components
These can be added simply by referencing them in the template of an object. Here is a list of most of the available components and what they are used for.

Actor: Handles traditional RPG attributes such as skills and alignment. Stores and manipulates the Status States information. These are used to indicate if a character is in a state, and how long the state lasts. We call anything that has an actor component an Actor. This includes players, monsters, and NPCs. Having an actor component requires you to also have attack, body, defend, physics, and inventory components.

Aspect: The visual representation of a GO. The Aspect also defines how the GO interacts, whether it is currently visible, selectable, useable, collideable or path blocking. The Aspect also contains the life and mana for the GO. Another part of the Aspect is LODFI. LODFI GOs are created locally on each machine; these are non-interactive, and depending on their LODFI value they can be made to disappear with the Game Detail slider. The Aspect component provides control of the Chores that a GO can run, and can be used to set up blending. Having an Aspect component requires that a GO have a Placement component.

Attack: Contains information about how this GO attacks. Used to define the attack information for weapons, projectiles, and Actors. Contains information like damage, reload time, and attack ranges. The Attack component handles loading and launching ammo for projectile weapons. Requires the Common component.

Body: Contains information about how an Actor moves and animates. Contains the bone translator, which maps Bones in the model into common bone names that are used by simulation and effects. Specifies the chores that an object has, which animations those chores contain, and which skrits drive those chores. Requires the Aspect component.

Common: Contains common attributes about a GO. Also contains all of the triggers for an object. The Common component contains membership that is used to determine which GOs can damage each other, immunity to spells, and dynamic party creation.

Conversation: Contains a list of the conversations that this GO can access. The Conversation component has controls to select and activate those conversations. Used in AI talk jobs to control conversations.

Defend: Info about defense such as armor type and defensive value.

Gizmo: Gizmos are objects that show up in the editor, but not the game. If you wanted to make an invisible controller GO, then you would put a gizmo on it so you could see it in SE. Also useful for keeping track of which direction is which on objects where it matters, such as chests.

Gold: Used to set the price of items, also contains info about how gold looks on the ground.

GUI: How an object looks in and interacts with the inventory.

Inventory: Defines the inventory layout. Can be used to list, equip, remove, or put items into a GO's inventory.

Magic: Defines the properties for casting a magic spell. Can store and apply enchantments, these can be for a spell, an enchanted weapon, or even a potion.

Message: Storage for text messages to print to the screen, they are stored here for easy localization.

Mind: Defines properties on how an actor uses AI, specifies which skrits to use for AI. Has several high level AIQuery and Inventory routines to make AI specifically easier. Contains controls to run and manage AI jobs.

Party: Defines party light, sets up how dynamic parties form and the AI that they use. The Party component has controls for adding and removing actors from the party. This component also handles moving the party in formation.

Pcontent: Specifies the pcontent values on an item, what the power level is, what enchantments the object can have, all of this for all of the various quality types of a weapon.

Physics: Defines how an object will fall, burn, and break.

Placement: Stores position information about an object. Can get and set the placement or orientation.

Potion: Manages how a potion represents its full state in the inventory.

Store: Manages NPC stores, which items restock, the prices of the items, and even if the person can put themselves up for hire.

Skrit Components: These can contain custom properties and behavior. Hooking up a new Skrit component is relatively easy: you just need to put the skrit in the right directory, and then the game will automatically detect it and make it available.

Streaming And The Frustum
<Back to Table of Contents>

Nodes are the building block of the world's terrain. Nodes have connectors where they can be connected to other nodes; these connectors are called doors. Doors can be any size to link two nodes. Typically we make the door the full size of the connection between the nodes. Doors are used for path finding and determining which nodes to stream in.

Nodes can be any size or shape. We tend to make then a uniform size and shape to make construction easier; this also limits the number of custom pieces needed. Nodes can be moved with special gizmos called elevators. Elevators take a node and change which door it is connected to. It can even connect it to a completely separate node. Each node can have a "floored" area. The floored area is the part that is walkable by an actor.

A frustum is a big volume that we want the world to be in; the game tries to keep nodes loaded to fill the frustum. The frustum has a width and height; these sizes can be modified as part of a mood. Each character in a game has a frustum. This means that an 8-player MP game has eight frustums.
The center of a frustum is the node that a character is on. This node is called the target node. The game looks at the target node and recursively checks all of the doors on that node, looking for the nodes that are connected by these doors. When the game finds a new node it loads it, then tries to load the nodes connected to it. The game continues to look for new nodes to load in this fashion until it fills up the frustum, or runs to the edge of the world. When the character moves, the target node moves with them. Moving the target node causes the frustum to move as well. When nodes that were loaded are no longer in the frustum they get unloaded.

Each region has a list of the Scids that are in each node. When the node is loaded the game looks at the Scids in that node and loads them as well. Loaded GOs are given Goids to be able to reference them later. When an object is killed or the node that an object is on gets unloaded, then that object starts to expire. Expiration is a timer that an object is going to last for before it gets deleted. If an object is expiring because it left the world, then the node it is in is reloaded then it stops expiring. Goids are a valid way to reference an object until it finishes expiring.

One of the common pitfalls that we run into when making content for the game is how to handle connected objects when they stream in and out. For example, let's say that you wanted to trigger a scripted ambush in the game. You could set up a treasure chest to activate a generator behind the player. The newly-generated monsters could then run towards the player. If the generator was far enough away from the treasure chest, then it might not be in the world when triggered, and nothing would happen.

In addition to the world frustum there is also a view frustum. The way that the view frustum works is similar to that of the world frustum: the game determines which nodes are in the view frustum, and only draws those. Objects are drawn the same way: if the node that an object is in is drawn then the object itself will be drawn. Fog can decrease the amount that is drawn but those nodes are still loaded.

Systems
<Back to Table of Contents>

There are several Game Systems in Dungeon Siege. These control different parts of the game. The systems can be accessed from skrit to perform different actions to affect GOs or the world as a whole. Here is a list of the most important systems, and some of the functions they serve.

AIQuery: AIQuery is used to look at the surrounding area. One of the main uses of these queries is to get the GOs that are near this object. You can either look for one GO or all of them in a certain area. You can also set up filters so you only get certain types of actors. AIQuery also provides distance and visibility checking. You can also use AIQuery to find points on the ground, you can find a point that would have a clear line of sight, or just find a point relative to another object.

CameraAgent: The CameraAgent can be used to control the position of the camera. You can offset the camera position, or transition it to a new point. The CameraAgent is used for NIS camera control.

ContentDb: ContentDB can be used to get information from a GO's template. You can also create formulas and evaluate them with this system.

GameAuditor: The GameAuditor can be used to set global named values. You can use this to set when a global event has occurred, or if there is a global value that needs to be saved it can be stored with this.

GoDb: The GoDB handles low level GO manipulation. You can create new GOs from a template, or make a copy of an existing GO. You can also delete a GO in about 25 different ways. You can use the GoDB to set ScidBits. ScidBits store information about a GO that can store its state. This provides much lower memory usage than just saying everything about an object. Another use of the GoDb is to watch another GO. When you are watching a GO you get a carbon copy of the message that was sent to it.

MCPManager: The MCPManager is used to make Chore requests to the MCP. Chores are used by the AI Jobs. Chores can move the character and play different animations.

Mood: Moods are the ambient mood in the game. Things like fog, rain, and the music playing are all part of the mood. The mood system can be used to set which mood is being played, or what parts are being used from the different players' moods. You can also change which music tracks are playing.

Physics: Physics handles simulation of objects. This is used to explode objects or flip a weapon when it is dropped to the ground. Physics can also be used to create areas of damage, either from explosions, or just an area that does damage.

Rules: The Rules system handles Gameplay code. This includes chances to hit, determining and applying damage, even awarding experience.

SiegeFx: SiegeFX manages effect scripts. You can use SiegeFX to start local or global effects, and when you are done with them you can turn them off. You can also use SiegeFX to store temporary positions.

Server: Server is used to keep track of the players that are in the game. You can get a local player, all of the players in the party, or all of the players in a MP game.

Victory: The Victory System manages parts of the game related to major gameplay events like quests or winning the game itself. You can use the Victory system to assign quests and advance them through their various states. You can then complete the quest when it's done. Chapter transitions are handled through victory. You can also set up victory conditions and check them through the victory system.

World: The World system is useful for drawing debug HUDs in the game.

WorldTerrain: The WorldTerrain system is used to move nodes, and connect or disconnect doors. All of the elevators were written using the world terrain system. The HUB teleport pads are just node transition and connections.

Conclusion
<Back to Table of Contents>

We have talked about how objects work in Dungeon Siege. You should now have a basic understanding about how different objects are constructed. Also how some of the different systems that work on those objects function.

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_302 Teleporters and Displacers

Teleporters & Displacers

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

What This Tutorial Will Teach You

Overview - Instant Elevators (Hub/Displacer Elevators)

No Loading Screens Means Constant Loading, But In Small Chunks

A Tiny Frustum Is The Answer

States Involved

Restrictions To Keep In Mind

There MUST Be A Player On The Elevator

Elevators Can't Connect To The Same Door At The Same Time

Explanation Of The Fields

Important Note About Messaging

How To Keep It From Slowing The Game Down Too Much

Conclusion

Appendix A: An Example

Appendix B: Tips

Introduction

Teleporters are really an advanced elevator type. A strong word of caution is in order: the Siege Engine was never intended to allow teleporting, and this elevator bends that rule pretty far. The explanation for these elevators assumes that the reader has experience with other elevator types. Not all aspects of these elevators will be spelled out in this section, since there is significant similarity to the simpler elevators. The large differences will be covered, and guidelines will be given for their use.

What You Need For This Tutorial
  • Dungeon Siege, updated to version 1.09B or later
  • Siege Editor
  • A test map
What This Tutorial Assumes You Have Already Learned
  • Siege U: 204 - Moods: Understanding how to use moods is a requirement for setting up teleporters.
  • Siege U: 206 - Elevators: Teleporters are really just specialized elevators. You should have some practice with normal elevators before attempting to build a functional teleporter.
What This Tutorial Will Teach You
  • Building Advanced Elevators
  • Preserving System Performance
  • Elevator Restrictions
  • Field Definitions
Overview - Instant Elevators (Hub/Displacer Elevators)
<Back to Table of Contents>

No Loading Screens Means Constant Loading, But In Small Chunks
We have an engine that does away with loading times by constantly loading new stuff on the edge of the frustum as the player moves along. If they were able to move very fast (or teleport), there would suddenly be a whole lot of stuff to load. Far too much; especially in a multiplayer environment where the server would take a massive performance hit. If the frustum were much, much smaller, then this would be plausible. A very small frustum (a 5 meter radius frustum, as opposed to the default 45 meter radius frustum) means that very little content is loaded, so having the player teleport somewhere would not load much-maybe just the nodes adjacent to them, and the objects in those nodes. This is acceptable even on low-end machines.

A Tiny Frustum Is The Answer
So how could we get away with having a small frustum? If the frustum was very small, the player could easily see the edge of the world, breaking their immersion in the game. The answer is to pull the fog in even farther. With the fog pulled in to mask the edge of the world, the player now can't really see anything but their own character. To keep the player from feeling too frustrated by this, we put them in a situation where they can't move around anyway: an elevator ride. Now they can't go anywhere, and we can play special effects as we bring the fog and frustum in to explain why the environment is changing around them. After they teleport, we use a mood with a very long transition time (over 20 seconds) that will push the frustum back out to 45 meters.

States Involved
When these elevators move, they go between 4 states. We created these specifically to function as the hub and displacer platforms in multiplayer_world. State 1 is on the ground at location 1, state 2 is in the air at location 1, state 3 is in the air at location 2, and state 4 is on the ground at location 2. The duration of the moves from state 1 to state 2, and from state 3 to state 4 is something that you can set in the elevator gizmo. However, the transition from state 2 to state 3 happens instantly.

Restrictions To Keep In Mind
<Back to Table of Contents>

There MUST Be A Player On The Elevator
A player has to be on the elevator if it is going to continue to function after it teleports. Otherwise, the elevator leaves all active frustums and stops getting updates. (This typically results in the companion elevator not completing its transition from state 3 to state 4 - it just stays "stuck" up in the air at state 3.) So a very important restriction to keep in mind is that for instant elevators to function correctly, a player must actually ride the elevator.

Elevators Can't Connect To The Same Door At The Same Time
Normally this situation doesn't come up, but now that we have two elevators using the same nodes, it's important to point out. When you move an elevator, it connects to the door of the node that you specified. If there is another node already connected there, it will be unloaded, which is scary and bad. If you are standing on a node when it is unloaded, suddenly you will be completely disconnected from the rest of the game world, floating in void. To avoid this possibility, we have two doors on each of the nodes used in the teleport process. Each teleport pad has two doors; one will use door 1 for each connection along its path, and the companion teleport pad will use door 2 for each connection.

Explanation Of The Fields
<Back to Table of Contents>

The fields in the instant elevator are rather numerous, but they are similar to the simpler elevators. One of the major differences is that the elevator gizmo needs to know how to move both elevator plates. These elevators were meant to work in pairs. When you activate one elevator, the companion elevator is also going to move - they are going to swap places. They each have their own elevator gizmo. This means there are quite a few fields to fill out (each gizmo needs to know all of the nodes and doors involved). Not all fields are listed below, due to their similarity to regular elevators. Remember, location 1 refers to where the elevator node starts. Location 2 refers to the destination for the elevator. Elevator node is the teleport pad itself (the elevator gizmo should be associated with this node). Elevator 2 (or e2) is the companion elevator node, which starts at the destination of the current elevator node.

Important Note About Messaging
When the elevator gizmo moves from state 1 to state 2, it calls that location 1 moving to level 2. It moves from state 2 to state 3 without providing any "hooks" for messaging. When it goes from state 3 to state 4, it calls that location 2 moving to level 1. On the return trip, state 4 to 3 is location 2 moving to level 2, and state 2 to state 1 is location 1 moving to level 1. If you are going to be using the messaging fields, this is important to understand.

other_elevator: the SCID of the elevator gizmo controlling the other displacer node. Entering this information allows both elevators to switch places simultaneously. Otherwise only one elevator would move, and then there would be overlapping nodes, something that the engine was never designed for. (read: Very Bad)

up_effect: the name of a special effect script to play when the elevator moves from state 1 to 2. (see Siege U: 212 - SiegeFX for special effects information)

down_effect: the name of a special effect script to play when the elevator moves from state 3 to 4.

For an example of the values for every field (except the messaging fields), refer to Appendix A.

How To Keep It From Slowing The Game Down Too Much
<Back to Table of Contents>

Make sure that the player has a mood with a small frustum by the time they teleport to their new location. This is totally necessary. Things that can help system performance beyond this involve keeping the destination area clear of all extraneous clutter. The idea is to reduce the amount of loading when they arrive at their new location, so try to keep the number of textures/actors/meshes/etc. that will be loaded down to an absolute minimum. For an example of a good area, look at the hub elevator in region "des_r1" in the multiplayer_world map.

When they get a mood that pushes the frustum back to a normal radius, you can make it take longer than the elevator ride. In most cases, the player will be more concerned with being able to move around than waiting for the fog to get pushed back to its full distance.

Conclusion
<Back to Table of Contents>

Congratulations! You now know how to set up the most complicated elevator we created. You should also now understand how to keep system performance in mind when creating a teleporter. An example has been provided in Appendix A. If you are having trouble setting up a working teleport elevator, try following the example as closely as you can.

Appendix A: An Example
<Back to Table of Contents>

The following are some sample values for nodes, and how each field would be filled out in the appropriate elevator gizmos. If you have never set up a teleport elevator before, fire up SiegeEditor, plunk down these nodes in a region, and follow along. Hopefully this will help answer remaining questions that you might have.

The base node for the example will be t_th01_teleport-hub-bottom-plate-a. The elevator node will be t_th01_teleport-hub-elevator-a.

Elevator gizmo: elevator_instant_4s_1c.

Location A:
t_th01_teleport-hub-bottom-plate-a node GUID: 0xa0001111.
t_th01_teleport-hub-elevator-a node GUID: 0xa0002222.
elevator_instant_4s_1c SCID: 0xaaaa1111.

Location B:
t_th01_teleport-hub-bottom-plate-a node GUID: 0xb0008888.
t_th01_teleport-hub-elevator-a node GUID: 0xb0009999.
elevator_instant_4s_1c SCID: 0xbbbb2222.

Here is how the elevator gizmo at location A would be set up:

other_elevator: 0xbbbb2222
elevator_node: 0xa0002222
elevator_door_location1_level1: 1
elevator_door_location1_level2: 1
elevator_door_location2_level1: 1
elevator_door_location2_level2: 1

This elevator will use door 1 to connect to the nodes along the way, the companion will use door 2. This we avoid any possibility that they will try to connect to the same node at the same time via the same door.

duration_location1_moving_to_lv1: 16
duration_location1_moving_to_lv2: 3
duration_location2_moving_to_lv1: 16
duration_location2_moving_to_lv2: 3

The times for the duration aren't set in stone, they just need to keep the game feeling smooth. You could have the durations for "moving to level 1" take longer, to give yourself more time to transition to a new mood. The reason for "moving to level 2" being 3 seconds: it doesn't need to take a long time to bring the fog/frustum close to the player, but 2 seconds seemed to be a bit too fast for slower systems. Sometimes they would only show a frame or two while moving up, and couldn't really tell what was going on until they had already teleported.

e2_elevator_node: 0xb0009999
e2_elevator_door_location1_level1: 2
e2_elevator_door_location1_level2: 2
e2_elevator_door_location2_level1: 2
e2_elevator_door_location2_level2: 2
connect_node_location1: 0xa0001111
connect_door_location1_level1: 9
connect_door_location1_level2: 11
e2_connect_door_location1_level1: 10
e2_connect_door_location1_level2: 12
connect_node_location2: 0xb0008888
connect_door_location2_level1: 9
connect_door_location2_level2: 11
e2_connect_door_location2_level1: 10
e2_connect_door_location2_level2: 12
up_effect: hub_plate_up
down_effect: hub_plate_down
Material

Material is provided just like the normal elevators (in case you want to use it).

location1_moving_to_lv2_ActionInfo: moodname_1
location1_moving_to_lv1_ActionInfo: moodname_teleport
location2_moving_to_lv2_ActionInfo: moodname_2
location2_moving_to_lv1_ActionInfo: moodname_teleport

The mood named moodname_teleport should be a mood with a short transition time (around 1 or 2 seconds) that brings in the frustum and fog to a very small radius. Moodname_1 and 2 are moods that should have long transition times (at least 10 seconds, though more than 20 seconds would be preferable in most situations) that will push the frustum back out to a normal radius.

Here is how the elevator gizmo at location B would be set up:

other_elevator: 0xaaaa1111
elevator_node: 0xb0099999
elevator_door_location1_level1: 2
elevator_door_location1_level2: 2
elevator_door_location2_level1: 2
elevator_door_location2_level2: 2
duration_location1_moving_to_lv1: 16
duration_location1_moving_to_lv2: 3
duration_location2_moving_to_lv1: 16
duration_location2_moving_to_lv2: 3
e2_elevator_node: 0xa0022222
e2_elevator_door_location1_level1: 1
e2_elevator_door_location1_level2: 1
e2_elevator_door_location2_level1: 1
e2_elevator_door_location2_level2: 1
connect_node_location1: 0xb0008888
connect_door_location1_level1: 10
connect_door_location1_level2: 12
e2_connect_door_location1_level1: 9
e2_connect_door_location1_level2: 11
connect_node_location2: 0xa0001111
connect_door_location2_level1: 10
connect_door_location2_level2: 12
e2_connect_door_location2_level1: 9
e2_connect_door_location2_level2: 11
up_effect: hub_plate_up
down_effect: hub_plate_down
Material
location1_moving_to_lv2_ActionInfo: moodname_2
location1_moving_to_lv1_ActionInfo: moodname_teleport
location2_moving_to_lv2_ActionInfo: moodname_1
location2_moving_to_lv1_ActionInfo: moodname_teleport

The elevator gizmos are triggered via a we_req_activate message, just like normal elevators.

Appendix B: Tips
<Back to Table of Contents>

  • Place use_points for the lever/button that will activate your teleporter, and make sure that they are on the elevator node. This will help ensure that somebody rides the elevator (which is mandatory for it to work correctly).

  • Keep the terrain in a landing zone simple.

  • Allow yourself 2 or 3 "shrubbing" objects to help spruce up the terrain in a landing zone, but no more. Make sure they don't use many textures.

  • If there is an expensive object or texture that must exist at the destination, place a copy of it at the origin, perhaps out of sight of the player. That way it will already be loaded before they even use the teleport.

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_303 Skrit

Skrit

Introduction

What You Need For This Tutorial

What This Tutorial Assumes You Have Already Learned

Overview of Skrit

Skrit Layout

Properties

Variables

Functions

States

Handling Skrit Events

Trigger

Event

Transition

Other Skrit Keywords

This

Owner

SetState

Include

At

Example: Gremal Hunter

Conclusion

Appendix A: Keywords

Appendix B: Skrit Events

Appendix C: World Events

Appendix D: Game Systems and Functions

Introduction

Skrit is the name of the custom programming language used in Dungeon Siege. Skrit was developed internally at GPG by Scott Bilas. Skrit serves as a layer of glue between the content and the code beneath it, and was used to create some of the major game features in dungeon siege. This tutorial should show you where you can use Skrit in DS, and what you can do with it. This article should also serve as a reference on the different things you can do with skrit.

What You Need For This Tutorial
  • Dungeon Siege, updated to version 1.1 or above
  • A text editor
  • Knowledge of another programming language is useful
What This Tutorial Assumes You Have Already Learned
  • Siege U: 200 - Concepts and Terminology
  • Siege U: 201 - Templates
  • Siege U: 301 - Introduction to Dungeon Siege Architecture

Overview of Skrit
<Back to Table of Contents>

To understand how Skrit is used first you need to know a little about the way the game is organized. We call all of the objects in the game GOs (Game Objects). This includes the players, enemies, and trees--even spells in a spell book. Pretty much everything you can see is a GO.

Each GO has a set of components that describe it. The list of components is a blueprint that the game uses to create a GO. These lists of components and values are called templates. For more information on templates see the SiegeU section on creating templates: Siege U: 201 - Templates. When specifying the template of a GO you can hook up C++ components that handle the common or speed critical aspects of the game. If you want to do anything that isn't already supported by the engine then that is where skrit comes in. You can create a component out of skrit to handle the special case features, and due to the ease of development we have created entire systems in the game using skrit.

Each Component has a series of properties that are used by the underlying system to control how a GO works. For example the Aspect component, a C++ component, controls how a GO looks in the game. The Aspect defines the model, texture, scale, and a slew of other properties. Skrit components are hooked up the same way as C++ components, and add special behavior to a GO.

Not all Skrit is hooked up as a GO component. AI behaviors, for example, are also defined in Skrit. The skrits are then hooked into the mind component, a C++ component that manages AI. The mind component then uses the different Skrits to control each job that an actor can run. Each AI job can respond to stimuli in the world and give actions to the actor to run based on those stimuli.

Skrit also controls each animation chore that an actor can run. The Skrit handles updating the animation, and special key events placed in the animation. If an artist makes an attack animation they want to have the damage calculated at a precise time, so they put in an event key when they want the action to occur. The Skrit that is handling the animation can detect that key and respond accordingly. These Skrits also control the blending of different animations. For example, if an archer needs to bend over to shoot something low on the ground then the Skrit combines multiple animations to get the archer shooting in the right place. We even have the ability to tweak an animation, or even animate a whole object procedurally in Skrit.

Here is an example of how to hook up a skrit component to an object.

First we make our skrit, and place it where it needs to go.

Test_Skrit.skrit

   Startup State my_state$
   {
      Event OnEnterState$
         {
            // do something here.
         }
   }
Then we make our test object, put it in an existing .gas file. Our test object in Test.gas:
[t:template,n:test_object]
{
   specializes = special;

   [test_skrit] {}
}
There are a lot of different ways that Skrit components are used:
  • Command gizmos - These objects are placed in the editor and are linked to other objects to manipulate them. These commands can perform actions like moving the camera, moving an actor, or playing a special animation as part of a special scripted sequence, or even something as simple as toggling if a door can be opened. Level designers will place the gizmo, then set up a trigger in the level to activate the gizmo. The gizmo is pretty much just a stripped down GO with a skrit attached that looks for that activate message, then responds accordingly.

  • Skrevs (skrited events) - These are only found in the "Yesterhaven" map that was released for the PC Gamer magazine. When a sequence in the game gets too complex, having separate command gizmos is difficult to debug and time consuming to set up. For these more complex scripting needs we have found that it works out better to combine all of the functionality into one controller gizmo. These are triggered just like a normal Command Gizmo. Setup in the level is as simple as placing the pieces of the event and linking them all to the skrev.

  • Spells - Spells are found in the world as pages for a spell book. The functionality for the spell is contained in a Skrit component attached to the spell object. When the AI wants to cast a spell, it sends a message to the spell. The spell waits for the message from the AI, and then can create visual effects, make explosions, launch projectiles, enchant a hero, or whatever the Skrit is made to do.

  • Interactive objects. - Doors, Chests, Buttons, Levers and every other interactive object in the game. When the player clicks on a useable object the engine sends a message to that object. These objects have Skrit components that wait for a message saying that they were used, then they animate, send messages, block/unblock terrain, drop items, etc.
Skrits interact differently depending on which system is using them. A GO Skrit component is attached to a GO. These skrits can manipulate objects relative to the GO they are a part of. The component could manipulate its GO directly, look at the messages that Go gets, look at objects near that Go, be linked in the editor to another go, do something when the attached go is updated, create new Gos, move Gos, or give them AI commands.

Some Skrits have specific functions that are called from code. The mood manager for example has a Skrit function ManageMoods$ which is the only way it is updated. These types of skrits don't have the normal setup, just the functions.

Skrit interacts with GOs and the world by using function calls in the engine. Dungeon Siege is an object-oriented world, and each GO component or subsystem has functions that can be called to work with that object or system. For example if we have a GO skrit component and want to act upon the object that the skrit is attached to, we can look at the owner of that skrit. If we want to act upon the inventory of the GO that the component is attached to, we call the GetInventory function on the owner GO. More about the different GO components and game systems can be found in the Siege U doc SiegeU 301. A link to lists of the various systems and functions in them can be found in Appendix D.

Skrit Layout
<Back to Table of Contents>

The majority of skrits are built around a state machine. A state machine provides the base for the logic that runs a skrit. State Machines allow you to specify different rules based on what state you are in. Dungeon Siege has states in the game as well as states in skrit. A good example of this are NISs. When the game is in an NIS it has different rules then normal. For example during an NIS the player can't move the camera or issue commands to his or her party. Here is a sample state machine based on a traffic light.

Rules for a traffic light state machine:
Three different states. Green, Yellow, and Red.
A car approaching the light we will treat as an event. We have different rules for what the cars will do when they approach when the light is in the different states. We can also have rules for what will happen when we transition between states.

If the state is green then a driver can go through.
If the state is yellow then the driver will go through if he or she is close, or will stop if not.
If the state is red the driver must stop.
If the state goes from red to green then all stopped cars can go through.
In addition to providing the structure for a skrit, states also separate different parts of the skrit. Each state can have variables, functions, and event handlers that only are active in that state. There can also be global variables functions and event handlers that can be accessed from any state. While states do provide the structure for a skrit, they don't cause anything to happen by themselves. All of the code that gets run in a skrit is the result of an event.

The basic layout for a skrit is as follows. All of the global parts of the skrit and properties can be accessed from anywhere within the skrit.
  • Properties
  • Global Event handlers
  • Global Variables
  • Global functions
  • State Machine
The contents of a state can only be accessed when the state they are in is active. Each state in the state machine can contain:
  • Local Variables
  • Local Event handlers
  • Local functions
Properties
Properties are values that can be used inside the skrit, but they can also be set from outside the skrit. These can be set when a skrit is attached to an object, or when a skrit object is run. Properties are very useful since skrits can be used by multiple objects, you can make them use the same skrit but specify different behavior by using properties.

Syntax
Property <Type> <Variable Name>$ [= <Default Value>] [doc = "<Document Text>"];
Arguments
Type: Type of the object, can be float, int, bool, string as well as types from the game like Goid or SiegePos

Variable Name: Name of the Variable.

Default Value: Value to use if not overwritten.

Document Text: description of the property
Example
property string effect_script$ = "my_script" doc = "Name of the SiegeFx script to play.";
property float delay$ = 3.0 doc = "Delay before starting effect";
Variables
Variables are used to store data to be used later. Variables have a certain scope, if you create a variable inside a function it can only be used in that function. Likewise if you create a variable that isn't inside a function or state then it can be used everywhere. Variables and properties are very similar, the big difference being that you can access properties from outside the skrit where variables can only be accessed inside the skrit.

Syntax
<Type> <Name>$[ = <Initial value> ];
Arguments
Type: The type of data to store in this variable. Skrit supports some of the standard types such as bool, int, float, and string. Skrit can also use types from the game such as SiegePos and Goid.

Name: The name of the variable. This needs to be unique for each variable in the skrit.

Initial value: The value that the variable gets when it is created.
Example
Int Count$ = 1;
Int Number1$;

Count$ += 3;
Number1$ = Count$;
Functions
Functions are a way to keep a skrit readable and prevent duplicated code. With a function you can write some code then call it from multiple other places. Functions ,like variables, have scope. Functions inside states can only be used in those states, and functions not in any state can be used anywhere.

Syntax
   [<Return type> ] <Function name>$[(<Parameter list>)];
   {
      [return <Return value>];
   }
Arguments
Return type: Type to return when the function finishes. You can return any type that you can use as a variable type. This is an optional parameter, you can leave it off or set it to void if you don't want to return anything. If you have a return type then you must include a return in your function.

Function name: This is the name that you use to call the function from other places in the code. Names need to be unique in the skrit.

Parameter list: List of parameters that the function needs to operate. This list is stored in pairs of a type and a parameter name. These parameters will need to be passed to the function where it is called in the code. The pairs are separated by commas and there can be any number of them.
Example

This function will take the two values passed in, then return the sum of those numbers.
   Int Add$( int a, int b )
   {
      return( a + b );
   }
We can then call our function from another place in the code like this. This call will use the function Add$ to put the sumof number1$ and number2$ into the variable Sum$.
   Sum$ = add$( number1$, number2$ );
States
Skrit has a built in state machine and all of the controls needed to make it run. In a skrit with a state machine there is a startup state, this state is the active state when the skrit is created. Only events that are handled by the current active state or are global events are processed.

Syntax
   [startup] state <State name>$
   {
      // events
   }
Arguments
State name: Name of the state. State names are used to switch states. You can name a state pretty much whatever you want without needing to define it first. A skrit state machine starts in the startup state. You can make any state the startup state.
Example
   startup state start$
   {
   }

   state end$
   {
   }

Handling Skrit Events
<Back to Table of Contents>

Skrit is an event driven language. This mean that most of the time it sits idle and only runs when certain special events are triggered. This keeps skrit performing well even though it is a scripting language. The skrit specifies which event it is looking for, and the code that will be executed as a result of that event. Here are some useful events. See the skrit reference for the complete list.
OnEnterState - Event that happens any time a state is entered.
OnExitState - Event that happens any time you leave a state.
OnTimer - Event that happens when a timer that has been created fires.
There are some events that are specific to the system that drives them such as:
OnGoHandleMessage - only available on GoSkritComponents. With this you are able to respond to events that happen to the Go the skrit is attached to.
OnWorldMessage - only available through AI
One of the most common events for skrits to use are message handlers. You can set up a skrit to wait for a specific message, and then do something when it happens. Messages in Dungeon Siege come in many different types. Each type of message has a different World Event that is sends. There are over 100 World Events, here are some of the most useful. You can see the reference at the end of this doc for the complete list.
WE_ENTERED_WORLD - Object entered the world frustum
WE_DAMAGED - Sent to a GO when it is damaged.
WE_EXPLODED - Sent to an object when it explodes.
WE_KILLED - Sent to an object when it is killed.
WE_REQ_ACTIVATE - Sent to objects to tell them to start doing their thing. Frequently used in skrits and triggers in the level.
WE_REQ_CAST - Sent to a spell to tell it to cast itself.
WE_REQ_DEACTIVATE - Turns off activatable objects.
WE_REQ_USE - An object gets this message sent to it when a player wants to "use" it.
WE_TRIGGER_ACTIVATE - Send to a Go to turn on any deactivated triggers
WE_TRIGGER_DEACTIVATE - Send to a Go to turn off any triggers
WE_ANIM_DONE - Sent when the animation for this object is done playing.
Trigger
Triggers are a very fast way to respond to an event. They can only fire on a specific value.

Syntax
   Trigger <event type>( <trigger value> )
   {
      // code
   }
Arguments
Event Type: Type of event to look for

Trigger Value: Value to trigger on
Example

This code block will get run when the skrit timer with ID 1 fires.
   State some_other_state$
   {
      trigger OnTimer$( 1 )
      {
         // code
      }
   }
Event
Event - like a trigger, but you can access the data.

Syntax
   Event <event type>$[( <event properties> )]
   {
      // code
   }
Arguments
Event Type: Type of event to look for.

Event Properties: These are the properties that correspond to the event type that is being used. These Properties can be accessed inside the event handler.
Example

This event handler will get called every time a message is sent to the GO that this skrit is attached to. It only does something when the message contains a specific world event.
   state some_state$
   {
      event OnGoHandleMessage$( eWorldEvent e$, WorldMessage msg$ )
      {
         if( e$ == WE_REQ_ACTIVATE )
         {
            SetState Go$;
         }
         else if( e$ == WE_REQ_DEACTIVATE )
         {
            SetState Stop$;
         }
      }
   }
Transition
Transition - change between states.

Syntax:
   Transition
   {
      -> <state name>: <event type> ( <trigger value> ) [ =
      {
         <optional code to execute during transition>
      }]

      [<optional additional transitions>]
   }
or
   Transition -> <state name>: <event type> ( <trigger value> ) [ =
   {
      <optional code to execute during transition>
   }]
Arguments
State name: Name of the state to switch to.
Event Type: Type of event to look for
Trigger Value: Value to trigger on
Example

This code block will run when the entered world message is received. This will also change the active state in the state machine.
   State another_state$
   {
      transition
      {
         -> New_State$:OnGoHandleMessage( WE_ENTERED_WORLD ) =
         {
            // code
         }
      }
   }

Other Skrit Keywords
<Back to Table of Contents>

This
Skrit has built-in timers that can be used to run code at different times. Timers are part of the skrit, not part of the object they are attached to. You use the keyword to access the timers. Timers are local to the state they are in. If you change states local timers will be deleted. If you want to make a timer in one state and respond to it in another, then you will need to make it a global timer.

Here are the functions used to access timers.

int CreateTimer ( float timeFromNow ); // Create a timer with a automatically assigned ID. This function will return that ID
void CreateTimer ( int id, float timeFromNow ); // Create a timer with the specified ID
int CreateFrameTimer( int framesFromNow ); // Create a timer based on number of frames this function will return an ID
void CreateFrameTimer( int id, int framesFromNow ); // Create a timer based on number of frames with a specified ID

void DestroyTimer( int id ); // Destroy the timer with a certain ID

void SetTimerGlobal ( int id, bool global ); // Change if the timer is local or global
void SetTimerRepeatCount( int id, int count ); // Make a timer repeat for the specified number of times. -1 for infinite
float AddTimerSeconds ( int id, float extraTime ); // Add time to a timer. Will return the amount of time remaining.
void ResetTimerSeconds ( int id, float timeFromBase ); // Set the timer to a new time based on when it was created
void SetNewTimerSeconds ( int id, float timeFromNow ); // Set the timer to a new time based on the current time

Example
This.CreateTimer( 1, 15.3 ); // make a timer that will fire in 15.3 seconds
This.SetTimerGlobal( 1, true ); // make the timer global
This.SetTimerRepeatCount( 1, -1 ); // set the timer to repeat forever
Owner
The owner of a skrit is what the skrit is a part of. This is normally a GoSkritComponent for skrits attached to objects or an aspect for animation skrits. You can use the owner variable to access the object that a skrit is attached to.

Example

This will store the Goid of the object that a SkritComponent is attached to in the variable my_object$

   Goid my_object$;

   my_object$ = owner.Goid;
SetState
The active state of a skrit can be changed a couple of ways. The first is transitioning when receiving an Event. The second is setting the state directly. You can set the active state by using the setstate command. This will change the state when getting to the end of the current block of code. If you want to change the state immediately you can use the return command after setting the state.

Syntax
Setstate $<state name>;
Arguments
State name: State to transition to.
Example
   Startup State Waiting$
   {
      trigger OnGoHandleMessage$( WE_ENTERED_WORLD )
      {
         SetState Go$;
      }
   }

   State Go$
   {

   }
Include
Can include other skrits. This is useful if you have common functions that you would like to use in multiple skrits. This reduces the amount of time spent in maintenance, and also keeps skrits simple.

Syntax
Include <Skrit name>
Arguments
Skrit name: Skrit to include
Example
   #include "k_inc_spl_utils"
At
When trying to time multiple actions in skrit it would be unwieldy to try to have separate states for every action. You can use the At keyword to run a function at a specific time after entering a state.

Syntax
<function name>$ [at ( <time> | <frame count> frames | <msec time> msec )]
{

}
Arguments
Function name Name of the function
Time Time in seconds to wait before executing function
Frame Count Frames to wait before executing function
Msec Time Time in milliseconds to wait before executing function
Example
   State Timed_Actions$
   {
      step_1$ at (1 frames )
      {
         // after 1 frame in this state execute this code.
      }

      step_2$ at (2.0 )
      {
         // after 2 seconds in this state execute this code.
      }
   }

Example: Gremal Hunter
<Back to Table of Contents>

The following example was taken from the Mod Gremal Hunter. This Mod was developed at GPG to serve as an example for mod developers like you. The idea behind Gremal Hunter is that the players enter an enchanted forest area where many gremals live. The players may choose the path of "Good" (Players that use nature magic) or "Evil" (Players that use combat magic). The Good player will save Gremals from cages that appear around that map, the good gremals then follow their good master to try to help him fight against the evil player. The Evil player will hunt wild Gremals, and gremals that are helping the good player. The following example is how one would go about creating the skrit that creates the gremals in cages, and handles the good player releasing them from those cages. This skrit will reside on the cage that contains the gremal, and will have access to all of the messages that that cage receives.

Click here to download Gremal Hunter.

State Machine
First thing we want to do is set up that state machine. It's good practice to have a Startup state, then transition out of it before you do any real work. Since DS has a streaming world you can run into some problems if you try to execute skrit as soon as the state machine starts running.
   //wait here until we want to spawn our gremal.
   Startup state startup$
   {
   }

   //spawn the gremal, wait until our cage is used.
   state spawn$
   {
   }

   //Go here if we don't want to spawn the gremal.
   state finish$
   {
   }
State Transitions
Now that we have a base state machine set up we want to be able to transition between the states. We want to have a gremal in the cage as soon as possible. So we want to go to the spawn state as soon as the cage enters the world.

There are a lot of different ways that we could set up the transition to the spawn state. Below are some of the common ways that we change between the different states.

Triggers are nice since they are very efficient, they are also very simple.
   trigger OnGoHandleMessage$( WE_ENTERED_WORLD )
   {
      SetState Spawn$;
   }
Using an event we get access to the message, this is good if we need to see whom the message is from, or look at data passed with the message.
   event OnGoHandleMessage$( eWorldEvent e$, WorldMessage /*msg$*/ )
   // note the variable msg$ is commented out since it isn't
   // used in this event.
   {
      if( e$ == WE_ENTERED_WORLD )
      {
         SetState Spawn$;
      }
   }
Transitions are good if you need to organize a lot of state changes. It makes it very easy to read a lot of transitions, can see everything together. We only need one transition here though.
   transition
   {
      -> Spawn$:OnGoHandleMessage( WE_ENTERED_WORLD );
   }
We just want to do something when we get the event so we will just use the trigger. So our new skrit will look like this:
   Startup state startup$
   {
      //wait until we are notified that this GO
      //has entered the world.
      trigger OnGoHandleMessage$( WE_ENTERED_WORLD )
      {
         // Change the state
         SetState Spawn$;
      }
   }

   //spawn the gremal, wait until our cage is used.
   state spawn$
   {
   }

   //Go here if we don't want to spawn the gremal.
   state finish$
   {
   }
Creating GOs
Now that we have set up the basics of setting up a skrit state machine we can move into the specifics of this example.

There are several ways that GOs can be referenced. Scids, Goids, and GO pointers. Scid stands for "Static Content Identifier" these are generated by Siege Editor (SE) and are unique for every object that is placed in a map. When an object is loaded it gets a Goid associated with it. Goid stands for "Game Object Identifier" and should be valid until the game object is unloaded. We also have GO pointers. There are no guarantees how long a GO pointer will be valid for. Only objects that are placed in the editor have Scids. It's most common to store the goid for an object and use that to get the GO and modify that.

There are a couple ways to create a GO, we can use Pcontent, delayed Pcontent, or just clone the GO.

Pcontent is nice since it allows you to create any number of objects, and makes it really easy to specify which ones to create. It doesn't work with Actors like the gremal though. Delayed Pcontent has all of the advantages of pcontent, but can be used to create actors, and can be created multiple times. Both can cause hitching if too much is created, but delayed causes the hitching right when you are trying to use the object. Cloning the object has less overhead then using the pcontent system, but only allows you to create one GO.

To create our caged gremal. We only need to create one object so we can just clone him. We want to create the Gremal as soon as we enter the Spawn state. So we will use the event OnEnterState to know when to create him.

Our skrit has become:
   // waiting till it is ok to spawn.
   Startup state startup$
   {
      trigger OnGoHandleMessage$( WE_ENTERED_WORLD )
      {
         SetState Spawn$;
      }
   }

   // spawn the gremal.
   state spawn$
   {
      //first create a goid so we can reference our gremal later.
      Goid newGoid$;

      // As soon as we enter the spawn state make the gremal
      event OnEnterState$
      {
         //make a clone request. we want to use the template name of
         // the gremal we want to create to make the Clone request.
         GoCloneReq cloneReq$ = MakeGoCloneReq( "gpg_gremal_caged" );

         // We want to create a gremal in the same place as the cage.
         // To get the position of the cage we first need to get a GO
         // pointer for the cage. Owner is the GoSkritComponent, and
         // we can use this to get the GO that the skrit is attached
         // to. owner.Go is that GO. After we have the Go of the cage
         // we can access the components on that cage. Placement is
         // the name of one of the C++ components that a Go can have.
         // One of the properties of the Placement component is the
         // position.
         cloneReq$.StartingPos = owner.Go.Placement.Position;

         // We also want the spawned Gremal to have the same
         // orientation of the cage. We can get the Orientation
         // the same way that we get the placement.
         cloneReq$.SetStartingOrient = owner.Go.Placement.Orientation;

         // Make sure the gremal is on the ground. :)
         cloneReq$.SnapToTerrain = true;

         // Finally submit the request to create the gremal. This will
         // return the Goid of the new gremal so we can reference
         // it later.
         newGoid$ = GoDb.SCloneGo( cloneReq$ );
      }
   }

   state finish$
   {
   }
Interacting with GOs
Now that we have our Gremal in the cage we want to be able to have the cage interact with the player. Any object that is useable (an option in the aspect component of the template) will display a different cursor showing that the object is useable. When the player clicks on the useable object then the engine sends a message to that object with the world event WE_REQ_USE. The object then needs to have a skrit that looks for the message and responds accordingly. We can look at the use message and figure out who is trying to use the cage by looking at whom the message came from.

We want to find out when a player is trying to use the cage. When they use it we will check if they are a "good" player, and if they are then break open the cage and have the gremal follow them. If the player is "evil" then we won't let them open the cage, but will instead tell them they are not allowed to use it.

The way we know that we are being used is that we get the WE_REQ_USE message. So we will use the OnGoHandleMessage event. We also need access to the contents of the message so we can't use a trigger, we need use it as an event:
   // waiting till it is ok to spawn.
   Startup state startup$
   {
      trigger OnGoHandleMessage$( WE_ENTERED_WORLD )
      {
         SetState Spawn$;
      }
   }

   // spawn the gremal.
   state spawn$
   {
      //first create a goid so we can reference our gremal later.
      Goid newGoid$;

      // As soon as we enter the spawn state make the gremal
      event OnEnterState$
      {
         // Make a clone request. we want to use the template name of
         // the gremal we want to create to make the Clone request.
         GoCloneReq cloneReq$ = MakeGoCloneReq( "gpg_gremal_caged" );

         // We want to create a gremal in the same place as the cage.
         // To get the position of the cage we first need to get a GO
         // pointer for the cage. owner is the GoSkritComponent, and we
         // can use this to get the GO that the skrit is attached to.
         // owner.Go is that GO. After we have the Go of the cage we can
         // access the components on that cage. Placement is the name of
         // one of the C++ components that a Go can have. One of the
         // properties of the Placement component is the position.
         cloneReq$.StartingPos = owner.Go.Placement.Position;

         // We also want the spawned Gremal to have the same orientation
         // of the cage. We can get the Orientation the same way the we
         // get the placement.
         cloneReq$.SetStartingOrient = owner.Go.Placement.Orientation;

         // Make sure the gremal is on the ground. :)
         cloneReq$.SnapToTerrain = true;

         // Finally submit the request to create the gremal.
         // This will return the Goid of the new gremal so we can
         // reference it later.
         newGoid$ = GoDb.SCloneGo( cloneReq$ );
      }

      // look for messages that this Go receives.
      event OnGoHandleMessage$( eWorldEvent e$, WorldMessage msg$ )
      {
         // A Go is going to get a lot of messages. We need to make sure
         // to respond only to the one we are looking for.  so we need
         // to check the event to see if it is the use event.
         if( e$ == WE_REQ_USE )
         {
            // Now that we know that we received the use event we need
            // to figure out which player used the cage. We can do this
            // by inspecting the message That was passed.

            // Need to save the Goid of the person who used the cage.
            // This is who will be the master of the gremal.
            Goid Master$;

            // Look who sent the use message store that ID in Master$
            Master$ = msg$.GetSendFrom();
         }
      }
   }

   state finish$
   {
   }
Now that we know who used the cage we can check to see what actions we should perform. For gremal hunter the nature magic users are the "good" side, and combat magic users are the "evil" side. We need to make sure that the player doesn't have any combat magic skill if they want to free a gremal. Player Skills are part of the Actor component on a GO. So we take the Goid we got from the message, then get the Go of the player. From the players Go we can get access to the Actor component, then look at the players skills.
   // waiting till it is ok to spawn.
   Startup state startup$
   {
      trigger OnGoHandleMessage$( WE_ENTERED_WORLD )
      {
         SetState Spawn$;
      }
   }

   // spawn the gremal.
   state spawn$
   {
      //first create a goid so we can reference our gremal later.
      Goid newGoid$;

      // As soon as we enter the spawn state make the gremal
      event OnEnterState$
      {
         // Make a clone request. we want to use the template name of
         // the gremal we want to create to make the Clone request.
         GoCloneReq cloneReq$ = MakeGoCloneReq( "gpg_gremal_caged" );

         // We want to create a gremal in the same place as the cage.
         // To get the position of the cage we first need to get a GO
         // pointer for the cage. owner is the GoSkritComponent, and
         // we can use this to get the GO that the skrit is attached
         // to.
         // owner.Go is that GO. After we have the Go of the cage we
         // can access the components on that cage. Placement is the
         // name of one of the C++ components that a Go can have.
         // One of the properties of the Placement component is
         // the position.
         cloneReq$.StartingPos = owner.Go.Placement.Position;

         // We also want the spawned Gremal to have the same
         // orientation of the cage. We can get the Orientation
         // the same way the we get the placement.
         cloneReq$.SetStartingOrient = owner.Go.Placement.Orientation;

         // Make sure the gremal is on the ground. :)
         cloneReq$.SnapToTerrain = true;

         // Finally submit the request to create the gremal.
         // This will return the Goid of the new gremal so we
         // can reference it later.
         newGoid$ = GoDb.SCloneGo( cloneReq$ );
      }

      // look for messages that this Go receives.
      event OnGoHandleMessage$( eWorldEvent e$, WorldMessage msg$ )
      {
         // A Go is going to get a lot of messages. We need to make
         // sure to respond only to the one we are looking for.
         //  So we need to check the event to see if it is the
         // use event.
         if( e$ == WE_REQ_USE )
         {
            // Now that we know that we received the use event we need
            // to figure out which player used the cage. We can do this
            // by inspecting the message that was passed.

            // need to save the Goid of the person who used the cage.
            // This is who will be the master of the gremal.
            Goid Master$;

            // Look who sent the use message This is going to be
            // the store that ID in Master$
            Master$ = msg$.GetSendFrom();

            if( master$.Go.Actor.GetSkillLevel( "Combat Magic" ) > 0.01 )
            {
               // code to execute if the player has chosen
               // the evil path.
            }
            else
            {
               // code to execute if the player is good.
            }
         }
      }
   }

   state finish$
   {
   }
Printing Messages
For the final part of our skrit we want to handle the case that the player trying to use our cage is the wrong alignment. We need to communicate this to the player. To do this we can print text to the screen saying what is going on. Text we want to print to the screen is stored in the template of an object to allow for easier translation to different languages. We can use the goid that we got from the WE_REQ_USE message to figure out which machine to write the text on. Report.Translate will select the correct version of the localized text to print out. We didn't localize this text so this will only be able to select the English version. Here is how we write out the text.
Report.SScreen( master$.Go.Player.MachineId,
Report.Translate( owner.go.getmessage( "too_evil" ) ) );
Final Template
   [t:template,n:gpg_cage_gremal]
   {
         specializes = base_breakable_wood;
         doc = "gremal in cage";
         category_name = "test";

         [aspect]
         {
            model                    = m_i_glb_cage;
            is_collidable            = true;
            scale_base               = 0.33;
            does_block_path          = false;
            megamap_icon             = <none>;
            is_invincible            = true;
            bounding_volume_scale    = 1.0;
            is_usable                = true;
            use_range                = 1.2;
            draw_selection_indicator = false;
         }
         common:screen_name = "Good characters can release
                               this caged gremal";

      // hook up the skrit to the cage object.
      [gpg_gh_generator_cage] {}

      [messages]
      {
         [too_evil]
         {
            screen_text = "You have chosen the path of EVIL,
                           you can't release gremals now.";
         }
      }
      [physics]
      {
         explosion_magnitude = 2.75;
         break_sound = s_e_env_break_container_wood;
         [break_particulate]
         {
            frag_glb_cage_02 = 1;
            frag_glb_cage_03 = 1;
            frag_glb_cage_04 = 6;
            frag_glb_cage_05 = 1;
         }
      }
   }
Final Skrit
///////////////////////////////////////////////////////////////////
//
// File     :  gpg_gh_generator_cage.skrit
// Author(s):  Eric Tams
//
// Copyright © 2002 Gas Powered Games, Inc.  All rights reserved.
//-----------------------------------------------------------------
//  $Revision:: $              $Date:$
//-----------------------------------------------------------------
///////////////////////////////////////////////////////////////////
//
//	Custom generator to handle the caged gremals.
//
///////////////////////////////////////////////////////////////////

// reserved tags
property bool _server_only$ = true;
property string _doc$ = "spawns a gremal, makes it guard the person
                                    that frees it from it's cage.";
property string _required_component$ = "placement";

// exported properties
property string child_template_name$="gpg_gremal_caged"
                doc="Template Name of Actor to Spawn.";

owner = GoSkritComponent;

///////////////////////////////////////////////////////////////////

// We want to wait until the object is in the world before we start
// to do anything. This is usually just a good practice to get into.
startup state Startup$
{
   trigger OnGoHandleMessage$( WE_ENTERED_WORLD )
   {
      // Since the editor and the game both run the skrits we need
      // to make sure that we don't spawn a gremal when the map is
      // loaded in the editor.
      if (godb.IsEditMode())
      {
         // Oh no! We are in the editor, Finish up we don't want
         // to do anything.
         SetState Finish$;
      }
      else // if in the game just spawn the gremal.
      {
         // create the Gremal.
         SetState Spawn$;
      }
   }
}

// We enter this state after we have entered the world. We are
// going to create a gremal, then wait for someone to try to
// open the cage.
state Spawn$
{
   // create a variable to store our gremals goid so we
   // can find him later.
   Goid newGoid$;

   // As soon as we enter the spawn state make the gremal
   event OnEnterState$
   {
      // Create a gremal in the same place as the cage.
      GoCloneReq cloneReq$ = MakeGoCloneReq( child_template_name$ );
      cloneReq$.StartingPos = owner.Go.Placement.Position;
      cloneReq$.SetStartingOrient = owner.Go.Placement.Orientation;
      cloneReq$.SnapToTerrain = true;
      newGoid$ = GoDb.SCloneGo( cloneReq$ );
   }

   // When we receive a message check to see if someone is
   // trying to use the cage.
   event OnGoHandleMessage$( eWorldEvent e$, WorldMessage msg$ )
   {
      if( e$ == WE_REQ_USE ) // is it the use world event?
      {
         if ( newgoid$.isvalid )
         {
            // look to see who sent the request to use.
            // That will be the person that freed the gremal.
            Goid Master$;
            Master$ = msg$.GetSendFrom();

            // Need to make sure that this guy is good. We don't
            // want to even break the cage if he is evil.
            if(master$.Go.Actor.GetSkillLevel("Combat Magic") > 0.01)
            {
               // Print a translated message to the machine of the player
               // who tried to open the cage telling them that they
               // have chosen the path of evil and can't open the
               // cage anymore.
               Report.SScreen(master$.Go.Player.MachineId, Report.Translate
               ( owner.go.getmessage( "too_evil" ) ) );
            }
            else
            {
               if( Master$.IsValid() )
               {
                  // set up the master link with the gremal.
                  // This activates special code on our gremal.
                  SendWorldMessage( WE_REQ_ACTIVATE, Master$, newGoid$, 1 );

                  // upgrade the gremal to a melee version instead of the
                  // caged version.
                  PostWorldMessage(WE_REQ_ACTIVATE, Master$, newGoid$, 2, .2);

                  // bust the cage.
                  Physics.SExplodeGo( owner.goid, 3, MakeVector(0,3,0) );
               }
            }
         }
      }
   }
}

state Finish$
{
}

Conclusion
<Back to Table of Contents>

This concludes the SiegeU article on skrit. You should now understand how some of the common parts of skrit work. This article contains a lot of reference information, so hopefully it will be useful for you when you are writing skrits of your own. Good luck!

Appendix A: Keywords
<Back to Table of Contents>

if
else
while
break
continue
forever
return

bool
int
float
string
void
shared
property

hidden

sitnspin
abort
msec
frames
doc

event
trigger
at

state
poll
transition
and

startup
setstate

true
false
null

this
vm
owner

include
option
only

Appendix B: Skrit Events
<Back to Table of Contents>

General

OnConstruct - called right after skrit object construction
OnDestroy - called right before skrit object destruction
OnLoad - called right after loading a skrit object from a saved game
OnConstructShared - same as OnConstruct except for the shared implementation
OnDestroyShared - same as OnDestroy except for the shared implementation
OnLoadShared - same as OnLoad except for the shared implementation
OnEnterState - called right after a state change, upon entering a state
OnExitState - called right before a state change, upon leaving a state
OnTransitionPoll - called when dynamic transitions are about to be polled
OnTimer - called when a skrit timer expires

NeMa

OnUpdate - called each time an animation state machine is updated
OnStartChore - called when a new chore is started on an aspect

Skrit components

OnGoCommitCreation - called when a Go is committing creation (second thread! careful!)
OnGoCommitImport - called after finalizing an imported character in a multiplayer game
OnGoShutdown - called when a Go is shutting down
OnGoPreload - called after committing creation (preload other Go's etc. here)
OnGoHandleMessage - called when a Go receives a world message directly
OnGoHandleCcMessage - called when a Go is cc'd on a world message due to watching
OnGoUpdate - called each sim, don't do anything too expensive here
OnGoUpdateSpecial - called each sim for player characters only (special use only!)
OnGoDraw - called each sim to permit extra drawing
OnGoResetModifiers - called when a Go's modified values should be reset to natural values
OnGoRecalcModifiers - called when a Go's modified values should be recalculated
OnGoLinkParent - called on a new child right after it gets a new parent
OnGoLinkChild - called on a new parent right after it gets a new child
OnGoUnlinkParent - called on a child right after it loses its parent
OnGoUnlinkChild - called on a parent right after it loses its child
OnGoDrawDebugHud - (dev-only) called each sim when visible to draw debug info
OnGoDump - (dev-only) lets a component participate in a Go dump

Skritbot

OnBotHandleMessage - called when a world message is sent
OnBotHandleUiAction - called when a UI action is executed
OnBotHandleUiWindowMessage - called when a UI window message is sent
OnBotHandleUiInterfaceMessage - called when a UI interface message is sent
OnBotUpdate - called each sim
OnBotSysUpdate - called each sim (ignores paused state of game)
OnBotDraw - called each sim to permit drawing at correct time

AI job

OnWorldMessage - called when a Go's mind receives a direct world message
OnCcWorldMessage - called when a Go's mind is cc'd on a world message
OnJobInit - called on job construction (set up the job here)
OnJobInitPointers - called on load game (init cached pointers here)

Appendix C: World Events
<Back to Table of Contents>

WE_INVALID
WE_UNKNOWN

System
WE_CONSTRUCTED - this should get sent to any GO right after it's been constructed and as the last thing in the init/load procedure
WE_DESTRUCTED - this should be sent to any GO right before it's deleted.
WE_JOB_DESTRUCTED - sent just to job when it's being deleted during normal gameplay. we_destructed is sent to job on gomind shutdown
WE_ENTERED_WORLD - object entered the world frustum
WE_LEFT_WORLD - object left the world frustum
WE_FRUSTUM_MEMBERSHIP_CHANGED - when frustum membership of go changes... data1 contains OLD membership
WE_FRUSTUM_ACTIVE_STATE_CHANGED - when the active frustums mask changes, this is broadcast. data1 contains OLD mask
WE_UPGRADED - object has been upgraded by siege (visible for screen player)
WE_DOWNGRADED - object has been downgraded by siege (visible for screen player)
WE_EXPIRED_AUTO - object has automatically expired outside of world frustum and will be deleted next
WE_EXPIRED_FORCED - object has been force-expired and will be deleted next
WE_PRE_SAVE_GAME - called right before the game is saved
WE_POST_SAVE_GAME - called right after the game is saved
WE_POST_RESTORE_GAME - called right after the game is restored
WE_TERRAIN_TRANSITION_DONE - Notify object that it's task is complete
WE_WORLD_STATE_TRANSITION_DONE - state change that was requested is done
WE_CAMERA_COMMAND_DONE - Notify object that camera command is done
WE_PLAYER_CHANGED - called right after player reassignment, old player* is stored in data1
WE_PLAYER_DATA_CHANGED - broadcast when any stats on a Player change
WE_SCIDBITS_CHANGED - sent to an object when its scidbits change
WE_MP_MACHINE_CONNECTED - when a machine connects to our box
WE_MP_MACHINE_DISCONNECTED - when a machine connects to our box
WE_MP_FAILED_CONNECT - when this machine attemps to connect to a remote session bug has been banned
WE_MP_SESSION_CHANGED - a session has been enumerated or changed, index in data1
WE_MP_SESSION_TERMINATED - session removed from the enumeration, index in data1
WE_MP_SESSION_ADDED - session added to the enumeration, index in data1
WE_MP_SESSION_CONNECTED - send after netpipe finished the host or connect procedure
WE_MP_PLAYER_CREATED - sent whenever a player object is created on local machine
WE_MP_PLAYER_DESTROYED - sent whenever a player object is destroyed on local machine
WE_MP_PLAYER_READY - player is now ready, playerid in data1
WE_MP_PLAYER_NOT_READY - player no longer ready, playerid in data1
WE_MP_PLAYER_WORLD_STATE_CHANGED - whenever the player object's world state changes, this gets broadcast
WE_MP_PLAYER_SET_CHARACTER_LEVEL - whenever the player's character level is updated
WE_MP_SET_MAP - sent when the server selects a map
WE_MP_SET_MAP_WORLD - sent when the server selects a map world
WE_MP_SET_GAME_TYPE - sent when the server selects a game type
WE_MP_TERRAIN_FULLY_LOADED - when no new siege nodes have been loaded this sim... only sent in JIP mode

UI
WE_SELECTED - object was selected
WE_DESELECTED - object was deselected
WE_FOCUSED - object was focused (set to first in selection)
WE_UNFOCUSED - object was unfocused (unselected probably)
WE_HOTGROUP - object entered the hot group
WE_UNHOTGROUP - object left the hot group
WE_MOUSEHOVER - object had a mouse over begin
WE_UNMOUSEHOVER - object lost its mouse over

Basic Gameplay Events
WE_COLLIDED - Collision occured with terrain or game object with damage
WE_GLANCED - Collision occured with terrain or game object without damage
WE_DAMAGED - Sent when to a go when it is damaged.
WE_DROPPED - Object was dropped, also received when an object first enters the world.
WE_EQUIPPED - Sent when an actor equips an item
WE_EXPLODED - Sent to an object when it explodes.
WE_GO_TIMER_DONE - go components may send themselves this message
WE_GOT - sent when an actor picks up an item.
WE_HEALED - unused
WE_KILLED - Sent to an Object when it is killed.
WE_LEVELED_UP - unused
WE_LOST_CONSCIOUSNESS - sent to a player character when they go unconscious.
WE_PICKED_UP - sent when an actor picks up an item or gold.
WE_REQ_ACTIVATE - sent to objects to tell them to start doing their thing. Frequently used in skrits and triggers in the level.
WE_REQ_CAST - Sent to a spell to tell it to cast itself.
WE_REQ_CAST_CHARGE - Sent to a spell when the casting starts, used to start charge up effects
WE_REQ_DEACTIVATE - Turns off activatable objects.
WE_REQ_DELETE - request deletion for self
WE_REQ_DIE - send to an actor to make it die.
WE_REQ_TALK_BEGIN - Sent to an actor when they start a conversation with another actor.
WE_REQ_TALK_END - Sent to an actor when they finish a conversation with another actor.
WE_REQ_USE - an object gets this message sent to it when a player wants to "use" it.
WE_RESURRECTED - unused
WE_SPELL_COLLISION - from = GOID_INVALID if terrain collision OR goid of game object that was hit, Data1 = SiegePos
WE_SPELL_EXPIRATION_TIMER_RESET - special command given from one skrit to another for extending spell duration
WE_SPELL_SYNC_BEGIN - For synchronizing effects with skrit
WE_SPELL_SYNC_END - For synchronizing effects with skrit
WE_SPELL_SYNC_MID - For synchronizing effects with skrit
WE_START_BURNING - Makes this go catch on fire
WE_START_SIMULATING - Tells a game object to start simulating itself using physics
WE_STOP_BURNING - Makes the go stop burning if on fire
WE_TRIGGER_ACTIVATE - Send to a Go to turn on any deactivated triggers
WE_TRIGGER_DEACTIVATE - Send to a Go to turn off any triggers
WE_UNEQUIPPED - Sent when an actor un-equips an item
WE_WEAPON_LAUNCHED - Sent to the ammo when it is launched.
WE_WEAPON_SWUNG - unused

MCP and Anim
WE_MCP_ALL_SECTIONS_SENT - sent by MCP whenever all the the PLAN sections for a GO have been transmitted to the CLIENTS
WE_MCP_CHORE_CHANGING - Sent when a Go starts running a new chore.
WE_MCP_FACING_LOCKEDON - Sent when a Go finishes turning to face it's goal object.
WE_MCP_FACING_UNLOCKED - Sent if a Go wasn't able to face its target or if its target left the world.
WE_MCP_GOAL_CHANGED - unused
WE_MCP_GOAL_REACHED - unused
WE_MCP_INVALIDATED - sent if the object you were tracking is no longer valid
WE_MCP_SECTION_COMPLETE_WARNING - Sent when a Go is near the end of its current section
WE_MCP_SECTION_COMPLETED - Sent when a Go completes its section. WE_MCP_NODE_BLOCKED - The required connections between nodes have changed
WE_MCP_DEPENDANCY_CREATED - Sent when a dependency on another Go's plan is created.
WE_MCP_DEPENDANCY_REMOVED - Sent when a dependency is removed.
WE_MCP_DEPENDANCY_BROKEN - Sent if a path is blocked or the target of a dependency is killed
WE_MCP_MUTUAL_DEPENDANCY - sent when two attackers become dependent on each other.
WE_ANIM_LOOPED - nema event 'loop'
WE_ANIM_DONE - nema event 'done'
WE_ANIM_ATTACH_AMMO - nema sends this message when ammo is to be created/attached
WE_ANIM_WEAPON_FIRE - nema sends this message upon ammo release for projectile and contact for melee
WE_ANIM_WEAPON_SWUNG - nema sends this message upon melee attack begin/end (data1 is begin=0/end=1)
WE_ANIM_SFX - Special Effect (SFX number is stored in data1)
WE_ANIM_DIE - Sent at the instant the character 'expires'
WE_ANIM_OTHER - other misc nema event (stored in data1)

AI sensors
WE_ALERT_ENEMY_SPOTTED - sent to friends when an actor spots an enemy.
WE_ALERT_PROJECTILE_NEAR_MISSED - sent when a projectile almost hits a target.
WE_ATTACKED_MELEE - Sent to an actor when they are attacked, but not hit.
WE_ATTACKED_RANGED - Sent to an actor when they are shot at with a ranged weapon.
WE_ENEMY_ENTERED_INNER_COMFORT_ZONE - Sent when an enemy enters the inner comfort zone range.
WE_ENEMY_ENTERED_OUTER_COMFORT_ZONE - Sent when an enemy enters the outer comfort zone range.
WE_ENEMY_SPOTTED - Sent when an enemy enters sight range, and a visibility check succeeds
WE_ENGAGED_FLED - sent to actor: engaged object has started to flee
WE_ENGAGED_HIT_KILLED - sent to actor: the engaged object was hit and killed by the actor
WE_ENGAGED_HIT_LIVED - sent to actor: engaged object was hit and still lived
WE_ENGAGED_INVALID - sent to actor: ERROR condition - engaged object has become invalid
WE_ENGAGED_KILLED - sent to actor: the engaged object was killed, but not by the actor
WE_ENGAGED_LOST - sent to actor: engaged object was probably unloaded
WE_ENGAGED_LOST_CONSCIOUSNESS - sent to actor: engaged actor has lost consciousness
WE_ENGAGED_MISSED - sent to actor: engaged object was attacked and missed
WE_FRIEND_ENTERED_INNER_COMFORT_ZONE - sent to actor: friend entered inner comfort zone
WE_FRIEND_ENTERED_OUTER_COMFORT_ZONE - sent to actor: friend entered outer comfort zone
WE_FRIEND_SPOTTED - sent to actor: friend entered sight range.
WE_GAINED_CONSCIOUSNESS - sent when an unconscious player character goes to positive health.
WE_JOB_CURRENT_ACTION_BASE_JAT_CHANGED - Sent when the current job isn't of the same type as the last job ran.
WE_JOB_FAILED - some objects may receive this message at the UNsuccessful end of a job
WE_JOB_FINISHED - some objects may receive this message at the successful end of a job
WE_JOB_REACHED_TRAVEL_DISTANCE - sent when the job has moved the actor past x distance from the time the job has started
WE_JOB_TIMER_DONE - when a SimpleStateMachine (SSM) timer expires, the SSM sends itself this message
WE_LIFE_RATIO_REACHED_HIGH - Sent when an actors health crosses the threshold to the high ratio
WE_LIFE_RATIO_REACHED_LOW - Sent when an actors health crosses the threshold to the low ratio
WE_MANA_RATIO_REACHED_HIGH - Sent when an actors mana crosses the threshold to the high ratio
WE_MANA_RATIO_REACHED_LOW - Sent when an actors mana crosses the threshold to the low ratio
WE_MIND_ACTION_Q_EMPTIED - when the last action in the action queue was just deleted, the mind will message itself this message
WE_MIND_ACTION_Q_IDLED - when the action Q has idles for some fixed amount of time, the GO receives this message
WE_MIND_PROCESSING_NEW_JOB - when new action starts running this message is sent
WE_REQ_JOB_END - when job is requested to end in a way that honors atomic states and graceful shutdown, unlike Job::MarkForDeletion()
WE_REQ_SENSOR_FLUSH - if an actor receives this message, he will flush his sensors if the Goid in Data1 appears in his visible object list

Appendix D: Game Systems and Functions
<Back to Table of Contents>

There are two files with extended information available for download:
FEX listing
Help Log

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.


su_304 Deep Modding

Deep Modding: .dsdll Extensions

Introduction

What You Need For This Tutorial

Building and Distributing a .dsdll

Usage Notes

Reference for dsdll.h

Frequently Asked Questions

Conclusion

Introduction

This article is about how to extend the Dungeon Siege engine with new functions that you create. Even though there are thousands of functions available to be called from skrit, sometimes you just have to write your own function to get what you want done. For example, say you are creating a mod that needs to talk to a database to register player stats, or you want to do something simple like query the system time in order to make your mod "real time". To do either of these things you will need to create a set of functions in an engine extension that can be called from within your mod's skrit code.

In a nutshell, to extend Dungeon Siege with new engine functions for skrit to use:
  1. Create a dynamically linked library (DLL) and set its output extension to be .dsdll rather than .dll.
  2. Export the functions using the compiler's "declspec( dllexport )" tag that you want the game to see.
  3. Put the resulting .dsdll file in the same directory as DungeonSiege.exe.
It's pretty simple! Most of this doc is about the rules governing what you can and cannot export, and some of the quirks in the system.

What You Need For This Tutorial
  • Dungeon Siege v1.1 or above

    The .dsdll features were added after Dungeon Siege originally shipped, so make sure to get the latest version through your ZoneMatch AutoUpdate feature.

  • A Microsoft C++ compiler

    This is not absolutely required, as Delphi and other languages are perfectly capable of creating .dsdll's, however it is easiest with a Microsoft compiler so that the name mangling format is guaranteed to be compatible.

    Visual C++ 6 and Visual C++.NET are guaranteed to work for creating .dsdll's, and it's been reported that Visual C++ 5 works fine as well though I have not verified this myself.

  • The WebMod sample

    This is a little mod I put together that contains a sample .dsdll, mod, and map. It demonstrates how to download a web page, process it a bit, and put the results up onscreen. Not terribly exciting, but the most important part of the sample is the dsdll.h file, which is a set of macros, docs, and some helper templates. Be sure to check this file out!

    The mod can be downloaded by clicking here.

  • The DbgHelp.dll redistributable

    This is a DLL that is required by the game when using .dsdll's to decode the mangled names that the C++ compiler exports. It's only required in Win98 and WinME, as Win2K and WinXP both have the file already.

    The latest version of DbgHelp.dll can be found in Microsoft's "Debugging Tools for Windows" (freely available on their web page) or in the System32 directory of Win2k/XP, plus I've also included it in the WebMod sample for convenience. To make sure that your .dsdll-based mod runs on all versions of Windows, DbgHelp.dll should be installed to the same directory as DungeonSiege.exe.

  • FuBi documentation on the web

    This is not required reading, but if you want to know how the system works underneath, I have written some detailed documentation on FuBi, our function binder that drives much of the low level systems of Dungeon Siege. You can find information including much of the source code to the system here:
    http://www.drizzle.com/~scottb/gdc/ (see GDC 2001 material)
    As this was written nearly two years ago, some of it may be out of date (FuBi has progressed considerably since then) but it's probably about 90% accurate. For the original paper on FuBi, you can see my "gem" from the first Game Programming Gems book:
    http://www.drizzle.com/~scottb/publish/gpgems1_fubi.htm
    This paper is even older, but it provides a good walkthrough of the problem that FuBi was intended to solve: the lack of reflection capability in C++.

Building and Distributing a .dsdll
<Back to Table of Contents>

Here is how to build a basic .dsdll with a single function:
  1. Start a new DLL project in Visual C++.

  2. Modify the linker settings to change the name of the output file to have an extension of .dsdll.

  3. Create a .cpp file and add it to the project - it must be compiled as C++, not C, for this to work. Here is a basic sample:
    // dlltest.cpp
#include "windows.h"
#include "dsdll.h"

BOOL APIENTRY DllMain( HANDLE, DWORD, LPVOID )
{
   return TRUE;
}

class DllTest
{
   FEX static const char* GetTestString( void )
   {
      return ( "Hi there!!!\n" );
   }
};
    The FEX keyword is actually a macro that comes from the dsdll.h file. More detailed information on the macros available in this file is included in a later section of this article.

  4. Compile and link the .dsdll and then copy it to the same directory as DungeonSiege.exe. You may find it convenient to just modify the project settings to output to that directory directly so you don't need to copy the file manually (or you can set a post-link build step to do so). Another option is to run Dungeon Siege with a shortcut that sets the dsdll_path command-line option (or in the INI file) to point to the location of your .dsdll. For example:
    dsdll_path=c:\mystuff\dsdll\debug
    You can verify that your functions have been exported by running the "Depends.exe" utility that is distributed with the Microsoft Platform SDK and looking at the export table.

  5. The next time you launch the game your new functions will be available. Here's an example of how to use the function created in #3 above:
    Report.ScreenF( "Your dsdll says: %s", DllTest.TestString );
    This ought to print the text "Your dsdll says: Hi there!!!" to the top of the screen. You can insert this code in any skrit in the game and it will work. You can do other things like assign it to a string:
    string s$ = DllTest.TestString;
    In short, it's just like any other function in the game, the engine doesn't know the difference!
The .dsdll is simply a plain DLL renamed to .dsdll and loaded with exported functions. As such, it can do whatever it likes in the system, calling out to the Internet, playing sounds, reading the keyboard, using files on the hard drive, etc. - there are no limitations placed on what DLL's can do in Windows.

Distributing your .dsdll-based mod is about the same as distributing any other mod, with two exceptions. First, you must install a special file DbgHelp.dll into the same directory as DungeonSiege.exe ; second, you must create a shortcut or batch file that launches your mod. See the FAQ entry below called "I can't play multiplayer games with my friends any more! What's wrong?" for more information on this.

Usage Notes
<Back to Table of Contents>

During system initialization, the game looks for all .dsdll files found in the dsdll_path directory (which defaults to the game's EXE dir), then sorts them alphabetically and iterates through them for import. Importing is done by iterating through the export table on each .dsdll and de-mangling each entry found there (this is what the DbgHelp.dll is needed for), then mapping the function and its entry point into the system.

There are a lot of rules and oddities about how the system works, so let's just go through those as a set of randomly ordered notes. Note that most of the following is pulled straight from the documentation at the top of dsdll.h.

These are specifically supported in exports:
  • Functions with external linkage - this includes nonstatic global functions and static or nonstatic class member functions. They may be overloaded by name, but be careful, as skrit's overload resolution algorithm isn't the best.

  • Any number of parameters per function, including variable argument (...) lists. Exported functions can also return parameters.

  • All built-in types (char, int, float, etc. but not doubles!) are supported for parameters or return values.

  • Strings (const char*) are specifically supported through some special case code.

  • Pointers and references to user-defined types (classes, structs, etc.) are ok as parameters and return values.

    The system automatically "understands" any type you throw at it. If you have two functions that mess with pointers or references to a class called Jooky then it will just work. To the engine it's always a 4-byte pointer, and all it does is make sure that the types match before permitting the calls to compile. This feature is used in many parts of the game, where pointers are queried from one system and then passed to others, without skrit messing with the pointer at all.

  • Functions that match this signature:
    T GetSomeVariable( void ) const
void SetSomeVariable( const T& | T )
    Will get mapped in skrit as a variable. While it's still possible to call those functions directly using the full function names, you can treat them as the variable "SomeVariable". If the Get version is left out, then it's write-only, and if the Set version is left out, it's read-only.
There are also some important restrictions:
  • __fastcall is not supported as a calling convention.

  • Don't prefix any names with FUBI_ or allow any names to contain '$' in the middle of the name - these are reserved as special tags that FuBi uses for documentation and internal functions attached to exports. If you look closely at the macros in dsdll.h you'll see how this works.

  • Skrit is case-insensitive and FuBi has to accommodate this. Do not export two functions with the same name but different case (for example, Test() and TEST()).

  • For consistency reasons, do not use underscores in any exported names - some_function_call() is bad, SomeFunctionCall() is good.

  • If a SetXXX and GetXXX pair exists but the parameters do not match as described above, it will print an error.

  • "Complicated" exports won't even parse properly and you'll get an error from FuBi about how it isn't able to understand some function you're trying to export. This will typically happen with templates or functions that take function pointers as parameters. Not supported.

  • Unions are not supported. Never will be.

  • Passing complex types by value is not supported. If you export a function that takes a GUID by value, you'll get an error. Pass it by reference and it'll be okay. Specific support can be added to allow passing these by value if you tag the type as POD (and it really has to be plain old data! no ctor/dtors called!).

  • Virtual functions are supported but are very unsafe. A virtual function exported to FuBi will get called directly. The vtbl is not consulted ('cause I don't know how to query its contents without a PDB) to figure out which version of the function to call. To prevent Bad Things happening from this I issue a warning for exported virtual functions. Work around this by exporting a non-virtual function that just redirects the call to a virtual function. Then the compiler will do the proper vtbl work. Use FUBI_RENAME (documented below) to give it the same name as the virtual function from Skrit's point of view.

  • Sometimes the DbgHelp.dll symbol undecoration code just bugs out. This happens more often on Win9x for some reason. Tweak the prototype until it stops.

  • Exports of constructors, destructors and overloaded operators are not supported.

  • Exporting functions from nested classes and namespaces is not supported (though documentation and singleton exporting of nested types is supported), mainly because Skrit doesn't need this complication.

  • Data exports (exporting a variable directly rather than using FUBI_VARIABLE and function-based Get/Set exports) will always get scary warnings. Don't export data.

Reference for dsdll.h
<Back to Table of Contents>

The dsdll.h file (included in the WebMod sample) contains a set of macros and templates that will help make it easier to export functions and types via .dsdll into the game engine. This section documents each of them.

FEX

Put this in front of any function in order to export it. It just resolves to a dllexport tag.

FUBI_DOC( NAME, PARAMS, DOCS )
FUBI_MEMBER_DOC( NAME, PARAMS, DOCS )

These macros will insert documentation about a specific function into the type system, which can be retrieved using the various commands in the Help namespace. Be sure to use the MEMBER version when documenting a member function of a class, and the non-MEMBER version when documenting globals. Here is an example from the game's C++ SkritSupport.h file:
FEX vector_3& MakeVector( float x, float y, float z );
FUBI_DOC( MakeVector, "x,y,z", "Returns a vector composed of x, y, and z.
This is a temporary and will change the next time this function is
called so do not store the results." );
Put the macro next to the function that it is documenting - it is a standalone statement that exports a function which is meant to document another function. NAME is the unquoted name of the function, PARAMS is a quoted string containing a comma-delimited list of parameter names, and DOCS is a quoted string containing documentation on the function.

FUBI_CLASS_DOC( NAME )

This macro is similar to the FUBI_DOC except that it is intended to doc a class or type. Just put it inside of a class definition, where DOCS is a quoted string containing documentation on the type.

FUBI_RENAME( NAME )

This can be used to rename a function from how C++ sees it to how skrit sees it. This is most useful for wrapping virtual functions. Because skrit does not have access to the vtable, the compiler needs to provide the necessary lookup code for virtual functions. To use this macro, wrap it around the function name that is being declared. Here is an example:
virtual void SetVisible( bool bVisible );

FEX void FUBI_RENAME( SetVisible )( bool bVisible )
   { SetVisible( bVisible ); }
This sample is pulled from the code for the UIWindow class. The first function SetVisible() is virtual and for a call from skrit to call the proper dynamically bound method, the compiler must generate a function to select it. The second function that is wrapped with FUBI_RENAME is that function, and will be the one that is called from skrit.

FUBI_VARIABLE ( T, PREFIX, NAME, DOCS )
FUBI_VARIABLE_BYREF( T, PREFIX, NAME, DOCS )
FUBI_VARIABLE_READONLY( T, PREFIX, NAME, DOCS )
FUBI_VARIABLE_BYREF_READONLY( T, PREFIX, NAME, DOCS )
FUBI_VARIABLE_WRITEONLY( T, PREFIX, NAME, DOCS )
FUBI_VARIABLE_BYREF_WRITEONLY( T, PREFIX, NAME, DOCS )

These macros are all variations on a simple concept of simultaneously declaring and exporting functions to set/get members of a class. The READONLY postfix means that the variable cannot be modified from skrit (i.e. no "Set" function), the WRITEONLY postfix means that the variable cannot be read from skrit (i.e. no "Get" function), and the BYREF tag means that set/get is to be done by reference, not by value (needed for non-builtins). Here is an example from the game's code:
struct JobResult
{
   FUBI_VARIABLE_BYREF( double, m_, TimeFinished, "Time when job was finished.");
   FUBI_VARIABLE( eJobResult, m_, Result, "Result at job finish.");
   FUBI_VARIABLE( DWORD, m_, Count, "Times this job was attempted");
   FUBI_VARIABLE( eJobAbstractType, m_, Jat, "Job Abstract Type");
   FUBI_VARIABLE( eJobTraits, m_, Traits, "Job Traits");
   FUBI_VARIABLE( eActionOrigin, m_, Origin, "Job assignment origin");
   FUBI_VARIABLE( Goid, m_, GoalObject, "Job param - goal object");
   FUBI_VARIABLE( Goid, m_, GoalModifier, "Job param - goal modifier");
   FUBI_VARIABLE_BYREF( SiegePos, m_, GoalPosition, "Job param - goal position");
};
The parameters to feed the macros are: T is the type of the variable, PREFIX is the C++ prefix to put on the variable (our convention is m_VarName so m_ is our prefix), NAME is the name of the variable, and DOCS is a quoted string describing the variable.

FUBI_CLASS_INHERIT( T, BASE )
FUBI_CLASS_INHERIT2( T, BASE1, BASE2 )
FUBI_CLASS_INHERIT3( T, BASE1, BASE2, BASE3 )
FUBI_CLASS_INHERIT4( T, BASE1, BASE2, BASE3, BASE4 )

These macros tell the type system about class inheritance. This is useful in skrit so that a pointer to a derived class can be implicitly upcasted to a base class by the skrit compiler. This is a convenience for skrit so that a pointer to a derived class can have base class methods called on it. The T, BASE1…BASE4 parameters are all just the unquoted names of your types, where up to four base classes are supported per type. Put the macro inside of the class definition to export the relationship. Here is an example from the UIEditBox type to say how it is derived from UIWindow:
class UIEditBox : public UIWindow
{
   FUBI_CLASS_INHERIT( UIEditBox, UIWindow );
   ...
Note that skrit does not provide downcasting capabilities, because it has no way to verify if a pointer to a base actually points to a derived type instead. For this you should export your own functions to do the casting, and verify that the cast will work properly before returning the casted pointer. Here is an example from the UI again, for casting a pointer to a UIWindow to the derived UIEditBox:
FEX UIEditBox* QueryDerivedEditBox( UIWindow* base )
{
   if ( (base != NULL) && (base->GetType() == UI_TYPE_EDITBOX) )
   {
      return ( (UIEditBox*)base );
   }
   return ( NULL );
}
FUBI_SINGLETON_CLASS( T, DOCS )
FUBI_SINGLETON_NESTED_CLASS( T, NAME, DOCS )

These macros tell the engine about your singleton types (singletons are objects that have one and only one instance in the system). They are designed to be used with the Singleton templates provided in dsdll.h but basically require that you declare a static function GetSingleton() in your class that returns a reference to the class's singleton.

Put these macros inside of the class definition. The parameters to pass into the macro are "T", the name of the type, and "DOCS", a quoted string documenting the class. These macros actually contain the FUBI_CLASS_DOC macro and pass the DOCS parameter through to it.

The NESTED version of this macro is meant for nested types, where NAME is the name of the nested type combined with the name of its outer type. For example a nested class Tel inside outer class Baz would be named BazTel for the NAME parameter, but left as Tel for the T parameter.

FUBI_POD_CLASS( T )

This macro tells the engine that the given type is POD, or "plain ol' data", meaning that it does not contain any advanced types (such as std::strings) or pointers, and does not require a constructor or destructor to set up or shut down instances of the type. This makes it easy to save/load instances of the class, send them over the network, or pass them by value to local functions.

Note that .dsdll's will probably not find this macro useful currently because savegame and network functions are not exportable, but the macro will remain for future mod capability expansion.

Singleton <T>
AutoSingleton <T>
OnDemandSingleton <T>

These are all variations on a theme of automating registration of class singletons. Just inherit your type from one of them in order to have it automatically registered. Singleton is for where you allocate/deallocate the singleton yourself, AutoSingleton is used for the compiler to automatically allocate it (statically), and OnDemandSingleton is used to tell the compiler to generate an instance on demand (i.e. the first time it is referenced). For more details, see the documentation inside the dsdll.h file. Background info on the concept of singleton registration via this recursive template trick is documented in another Gem at this address:
http://www.drizzle.com/~scottb/publish/gpgems1_singleton.htm

Frequently Asked Questions
<Back to Table of Contents>

Here are some questions I've been asked since .dsdll's were introduced to the community, plus a few other miscellaneous things that didn't really fit anywhere else in this article.

Coding is too difficult for me, but I need .dsdll functions for my mod! What can I do?

While writing a .dsdll may be difficult for some, it's not a problem for programmers! Why not grab a spot on SourceForge and start a "public .dsdll" project that contains all kinds of functions that anyone might need? Set up a process where people can request features (like "I need a standard deviation function!!") and programmers quickly write and export them, then rebuild a new version of the .dsdll for people to download. One uber-.dsdll could easily serve the vast majority of extra-engine needs for the community.

Can I call engine functions from my .dsdll?

The answer to this is currently "no" for security reasons, although community members have already discovered and published at least one safe workaround that does not break the security model. Be sure to check the forums on the Dungeon Siege community sites for more information.

Can I create my own networked (RPC) functions?

No. FuBi requires direct access to a number of internal Dungeon Siege systems that we simply cannot export. If you want to pass messages over the network, then you will need to set up and communicate through your own sockets via .dsdll functions.

Can I override engine functions with my own?

Yes, mostly! FuBi is built in such a way that it doesn't care where its functions come from, and when it finds duplicate functions it will just ignore the second version. So to override game functions, you just need to make sure that your functions are scanned before the game imports its own functions. Dungeon Siege supports this directly if you rename the DLL's extension to .dsdl0 (that's a zero at the end). The system scans functions in this order: .dsdl0 files alphabetically, DungeonSiege.exe, then .dsdll files alphabetically.

Important: the signatures for your override functions must be identical to the functions they're trying to override, otherwise they will either get ignored, or you'll get a crash.

Unlike normal .dsdll exports, a .dsdl0 that overrides game functions can have crazy unexpected behavior. Your overrides will be called for skrit and incoming RPC's, but not for in-game C++ code (those will still call the old versions). If messing with .dsdll's is considered deep modding, then using .dsdl0 overrides is extreme level 150 burrowing-to-the-Earth's-core modding. So use them with extreme caution, or you may shoot your eye out!

Can I extend the "dev console" with .dsdll's?

Yes! The console lets you type skrit code in directly by prefixing it with a forward slash. So to call any .dsdll functions of yours from the console, just put a slash at the front before typing in the console. You won't have the fancy auto completion, of course, but you'll be able to type in whatever you like, which can be very powerful. To speed your development efforts even further, you may find it useful to create a special debug.dsdll file that contains all kinds of reporting and analysis functions. Then keep a text file open that is filled with commonly used skrit code to access those functions. You can copy commands as needed to the clipboard, then alt-tab back into the game and paste the command directly into the console. It helps to run the game in windowed mode (fullscreen=false on the command line or in the INI file).

What about viruses??

This question comes up a lot, and comes from the basic fact that a DLL has full access to the entire system, and can infect it with a virus, or even reformat the hard drive. This fear is not unique to Dungeon Siege, in fact most games (notably the first person shooters) require you to download and run DLL's as part of any mod that is created. A Dungeon Siege mod that does not use .dsdll's is only capable of "playing in the sandbox" of the engine, and will be safe.

If you would like to learn more about what exactly is involved here, I suggest searching through the Half-Life forums to see how their community has handled this. It is the biggest moddable game on the market, with millions of players.

I can't play multiplayer games with my friends any more! What's wrong?

Simply having a .dsdll available directly modifies the type system of the game, which is nearly guaranteed to cause a latent crash or unexpected behavior in multiplayer if it's not identical to other games joining in. So for security and compatibility reasons, the game takes a checksum of all its .dsdll files on startup, and includes this in the multiplayer info packet, which is used to determine when it's possible for two games to join each other. If one person has a .dsdll installed, then either everyone else must have the same file, or that one person must delete it (or move it elsewhere).

The recommended way to use a .dsdll is demonstrated in the WebMod sample, which is intended to be launched using the webmod.bat file. This will choose the proper .dsdll file, and install the mod and map into the game's resource space during startup. A shortcut can be created by an installer to accomplish similar results (our Yesterhaven map did this as another example).

I love Delphi and hate C++, what can I do?

Have no fear! It's still possible to make .dsdll's, it's just going to be more work. There are a couple options. First is to reverse-engineer the name-mangling format of C++ and modify the function exports from your Delphi DLL to match your function prototypes, or perhaps write a postprocessor to rename your functions directly in the export table to match how the game is expecting. This is a ton of work, of course.

Another option is to learn enough of C++ to write a proxy .dsdll that routes calls from the game straight through to your Delphi-based DLL. This will let you write 99% of your code in your favorite language without having to get your hands too dirty with C++.

How do I export a class?

The most important thing to do is make sure that the .dsdll takes care of all the memory management for your class. To do this, export a function to create an instance, and another to destroy it. The skrit can call the create function to acquire an instance of the class, and then call member functions on it or pass it around to other systems. When it's done with it, it can call the destroy function to delete it. It's also worthwhile including some automatic garbage collection code to clean up as a safety measure.

Conclusion
<Back to Table of Contents>

Well that's it for this article! Hopefully this has provided you with enough information to create your own extensions to Dungeon Siege. Be sure to check out the WebMod sample and the dsdll.h in detail before diving into your own project.

©2002 Gas Powered Games, Corp. All rights reserved.
Dungeon Siege® is a registered trademark of Gas Powered Games, Corp. in the United States and/or other countries. Windows is either a registered trademark or trademark of Microsoft Corp. in the United States and/or other countries. All other brand names and products are trademarks of their respective companies. All information within this document is the copyrighted property of Gas Powered Games, Inc. Do not duplicate or redistribute images, text or materials from this document without the permission of Gas Powered Games.