Dungeon Siege Wiki
Fellow adventurers!
This guide is a direct upload from the original author and is no longer supported by Microsoft or Gas Powered Games. It is also not supported by the wikia's administration or members. Any errors encountered are a result of the original publication.

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[]


Overview of Skrit[]

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: SU 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 301: Introduction to Dungeon Siege Architecture. A link to lists of the various systems and functions in them can be found in Appendix D.


Skrit Layout[]

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[]

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[]

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[]

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[]

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.

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[]

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[]

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[]

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[]

WE_INVALID
WE_UNKNOWN

System[]

CONSTRUCTED - this should get sent to any GO right after it's been constructed and as the last thing in the init/load procedure
DESTRUCTED - this should be sent to any GO right before it's deleted.
JOB_DESTRUCTED - sent just to job when it's being deleted during normal gameplay. we_destructed is sent to job on gomind shutdown
ENTERED_WORLD - object entered the world frustum
LEFT_WORLD - object left the world frustum
FRUSTUM_MEMBERSHIP_CHANGED - when frustum membership of go changes... data1 contains OLD membership
FRUSTUM_ACTIVE_STATE_CHANGED - when the active frustums mask changes, this is broadcast. data1 contains OLD mask
UPGRADED - object has been upgraded by siege (visible for screen player)
DOWNGRADED - object has been downgraded by siege (visible for screen player)
EXPIRED_AUTO - object has automatically expired outside of world frustum and will be deleted next
EXPIRED_FORCED - object has been force-expired and will be deleted next
PRE_SAVE_GAME - called right before the game is saved
POST_SAVE_GAME - called right after the game is saved
POST_RESTORE_GAME - called right after the game is restored
TERRAIN_TRANSITION_DONE - Notify object that it's task is complete
WORLD_STATE_TRANSITION_DONE - state change that was requested is done
CAMERA_COMMAND_DONE - Notify object that camera command is done
PLAYER_CHANGED - called right after player reassignment, old player* is stored in data1
PLAYER_DATA_CHANGED - broadcast when any stats on a Player change
SCIDBITS_CHANGED - sent to an object when its scidbits change
MP_MACHINE_CONNECTED - when a machine connects to our box
MP_MACHINE_DISCONNECTED - when a machine connects to our box
MP_FAILED_CONNECT - when this machine attemps to connect to a remote session bug has been banned
MP_SESSION_CHANGED - a session has been enumerated or changed, index in data1
MP_SESSION_TERMINATED - session removed from the enumeration, index in data1
MP_SESSION_ADDED - session added to the enumeration, index in data1
MP_SESSION_CONNECTED - send after netpipe finished the host or connect procedure
MP_PLAYER_CREATED - sent whenever a player object is created on local machine
MP_PLAYER_DESTROYED - sent whenever a player object is destroyed on local machine
MP_PLAYER_READY - player is now ready, playerid in data1
MP_PLAYER_NOT_READY - player no longer ready, playerid in data1
MP_PLAYER_WORLD_STATE_CHANGED - whenever the player object's world state changes, this gets broadcast
MP_PLAYER_SET_CHARACTER_LEVEL - whenever the player's character level is updated
MP_SET_MAP - sent when the server selects a map
MP_SET_MAP_WORLD - sent when the server selects a map world
MP_SET_GAME_TYPE - sent when the server selects a game type
MP_TERRAIN_FULLY_LOADED - when no new siege nodes have been loaded this sim... only sent in JIP mode

UI[]

SELECTED - object was selected
DESELECTED - object was deselected
FOCUSED - object was focused (set to first in selection)
UNFOCUSED - object was unfocused (unselected probably)
HOTGROUP - object entered the hot group
UNHOTGROUP - object left the hot group
MOUSEHOVER - object had a mouse over begin
UNMOUSEHOVER - object lost its mouse over

Basic Gameplay Events[]

COLLIDED - Collision occured with terrain or game object with damage
GLANCED - Collision occured with terrain or game object without damage
DAMAGED - Sent when to a go when it is damaged.
DROPPED - Object was dropped, also received when an object first enters the world.
EQUIPPED - Sent when an actor equips an item
EXPLODED - Sent to an object when it explodes.
GO_TIMER_DONE - go components may send themselves this message
GOT - sent when an actor picks up an item.
HEALED - unused
KILLED - Sent to an Object when it is killed.
LEVELED_UP - unused
LOST_CONSCIOUSNESS - sent to a player character when they go unconscious.
PICKED_UP - sent when an actor picks up an item or gold.
REQ_ACTIVATE - sent to objects to tell them to start doing their thing. Frequently used in skrits and triggers in the level.
REQ_CAST - Sent to a spell to tell it to cast itself.
REQ_CAST_CHARGE - Sent to a spell when the casting starts, used to start charge up effects
REQ_DEACTIVATE - Turns off activatable objects.
REQ_DELETE - request deletion for self
REQ_DIE - send to an actor to make it die.
REQ_TALK_BEGIN - Sent to an actor when they start a conversation with another actor.
REQ_TALK_END - Sent to an actor when they finish a conversation with another actor.
REQ_USE - an object gets this message sent to it when a player wants to "use" it.
RESURRECTED - unused
SPELL_COLLISION - from = GOID_INVALID if terrain collision OR goid of game object that was hit, Data1 = SiegePos
SPELL_EXPIRATION_TIMER_RESET - special command given from one skrit to another for extending spell duration
SPELL_SYNC_BEGIN - For synchronizing effects with skrit
SPELL_SYNC_END - For synchronizing effects with skrit
SPELL_SYNC_MID - For synchronizing effects with skrit
START_BURNING - Makes this go catch on fire
START_SIMULATING - Tells a game object to start simulating itself using physics
STOP_BURNING - Makes the go stop burning if on fire
TRIGGER_ACTIVATE - Send to a Go to turn on any deactivated triggers
TRIGGER_DEACTIVATE - Send to a Go to turn off any triggers
UNEQUIPPED - Sent when an actor un-equips an item
WEAPON_LAUNCHED - Sent to the ammo when it is launched.
WEAPON_SWUNG - unused

MCP and Anim[]

MCP_ALL_SECTIONS_SENT - sent by MCP whenever all the the PLAN sections for a GO have been transmitted to the CLIENTS
MCP_CHORE_CHANGING - Sent when a Go starts running a new chore.
MCP_FACING_LOCKEDON - Sent when a Go finishes turning to face it's goal object.
MCP_FACING_UNLOCKED - Sent if a Go wasn't able to face its target or if its target left the world.
MCP_GOAL_CHANGED - unused
MCP_GOAL_REACHED - unused
MCP_INVALIDATED - sent if the object you were tracking is no longer valid
MCP_SECTION_COMPLETE_WARNING - Sent when a Go is near the end of its current section
MCP_SECTION_COMPLETED - Sent when a Go completes its section. :MCP_NODE_BLOCKED - The required connections between nodes have changed
MCP_DEPENDANCY_CREATED - Sent when a dependency on another Go's plan is created.
MCP_DEPENDANCY_REMOVED - Sent when a dependency is removed.
MCP_DEPENDANCY_BROKEN - Sent if a path is blocked or the target of a dependency is killed
MCP_MUTUAL_DEPENDANCY - sent when two attackers become dependent on each other.
ANIM_LOOPED - nema event 'loop'
ANIM_DONE - nema event 'done'
ANIM_ATTACH_AMMO - nema sends this message when ammo is to be created/attached
ANIM_WEAPON_FIRE - nema sends this message upon ammo release for projectile and contact for melee
ANIM_WEAPON_SWUNG - nema sends this message upon melee attack begin/end (data1 is begin=0/end=1)
ANIM_SFX - Special Effect (SFX number is stored in data1)
ANIM_DIE - Sent at the instant the character 'expires'
ANIM_OTHER - other misc nema event (stored in data1)

AI sensors[]

ALERT_ENEMY_SPOTTED - sent to friends when an actor spots an enemy.
ALERT_PROJECTILE_NEAR_MISSED - sent when a projectile almost hits a target.
ATTACKED_MELEE - Sent to an actor when they are attacked, but not hit.
ATTACKED_RANGED - Sent to an actor when they are shot at with a ranged weapon.
ENEMY_ENTERED_INNER_COMFORT_ZONE - Sent when an enemy enters the inner comfort zone range.
ENEMY_ENTERED_OUTER_COMFORT_ZONE - Sent when an enemy enters the outer comfort zone range.
ENEMY_SPOTTED - Sent when an enemy enters sight range, and a visibility check succeeds
ENGAGED_FLED - sent to actor: engaged object has started to flee
ENGAGED_HIT_KILLED - sent to actor: the engaged object was hit and killed by the actor
ENGAGED_HIT_LIVED - sent to actor: engaged object was hit and still lived
ENGAGED_INVALID - sent to actor: ERROR condition - engaged object has become invalid
ENGAGED_KILLED - sent to actor: the engaged object was killed, but not by the actor
ENGAGED_LOST - sent to actor: engaged object was probably unloaded
ENGAGED_LOST_CONSCIOUSNESS - sent to actor: engaged actor has lost consciousness
ENGAGED_MISSED - sent to actor: engaged object was attacked and missed
FRIEND_ENTERED_INNER_COMFORT_ZONE - sent to actor: friend entered inner comfort zone
FRIEND_ENTERED_OUTER_COMFORT_ZONE - sent to actor: friend entered outer comfort zone
FRIEND_SPOTTED - sent to actor: friend entered sight range.
GAINED_CONSCIOUSNESS - sent when an unconscious player character goes to positive health.
JOB_CURRENT_ACTION_BASE_JAT_CHANGED - Sent when the current job isn't of the same type as the last job ran.
JOB_FAILED - some objects may receive this message at the UNsuccessful end of a job
JOB_FINISHED - some objects may receive this message at the successful end of a job
JOB_REACHED_TRAVEL_DISTANCE - sent when the job has moved the actor past x distance from the time the job has started
JOB_TIMER_DONE - when a SimpleStateMachine (SSM) timer expires, the SSM sends itself this message
LIFE_RATIO_REACHED_HIGH - Sent when an actors health crosses the threshold to the high ratio
LIFE_RATIO_REACHED_LOW - Sent when an actors health crosses the threshold to the low ratio
MANA_RATIO_REACHED_HIGH - Sent when an actors mana crosses the threshold to the high ratio
MANA_RATIO_REACHED_LOW - Sent when an actors mana crosses the threshold to the low ratio
MIND_ACTION_Q_EMPTIED - when the last action in the action queue was just deleted, the mind will message itself this message
MIND_ACTION_Q_IDLED - when the action Q has idles for some fixed amount of time, the GO receives this message
MIND_PROCESSING_NEW_JOB - when new action starts running this message is sent
REQ_JOB_END - when job is requested to end in a way that honors atomic states and graceful shutdown, unlike Job::MarkForDeletion()
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[]

There are two files with extended information available for download:

SiegeU FEX listing
SiegeU Help Log

[Note: Microsoft still hosts these two documents as of this article posting.]