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[]
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 frustumWE_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 triggersWE_TRIGGER_DEACTIVATE
- Send to a Go to turn off any triggersWE_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 procedureDESTRUCTED
- 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 shutdownENTERED_WORLD
- object entered the world frustumLEFT_WORLD
- object left the world frustumFRUSTUM_MEMBERSHIP_CHANGED
- when frustum membership of go changes... data1 contains OLD membershipFRUSTUM_ACTIVE_STATE_CHANGED
- when the active frustums mask changes, this is broadcast. data1 contains OLD maskUPGRADED
- 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 nextEXPIRED_FORCED
- object has been force-expired and will be deleted nextPRE_SAVE_GAME
- called right before the game is savedPOST_SAVE_GAME
- called right after the game is savedPOST_RESTORE_GAME
- called right after the game is restoredTERRAIN_TRANSITION_DONE
- Notify object that it's task is completeWORLD_STATE_TRANSITION_DONE
- state change that was requested is doneCAMERA_COMMAND_DONE
- Notify object that camera command is donePLAYER_CHANGED
- called right after player reassignment, old player* is stored in data1PLAYER_DATA_CHANGED
- broadcast when any stats on a Player changeSCIDBITS_CHANGED
- sent to an object when its scidbits changeMP_MACHINE_CONNECTED
- when a machine connects to our boxMP_MACHINE_DISCONNECTED
- when a machine connects to our boxMP_FAILED_CONNECT
- when this machine attemps to connect to a remote session bug has been bannedMP_SESSION_CHANGED
- a session has been enumerated or changed, index in data1MP_SESSION_TERMINATED
- session removed from the enumeration, index in data1MP_SESSION_ADDED
- session added to the enumeration, index in data1MP_SESSION_CONNECTED
- send after netpipe finished the host or connect procedureMP_PLAYER_CREATED
- sent whenever a player object is created on local machineMP_PLAYER_DESTROYED
- sent whenever a player object is destroyed on local machineMP_PLAYER_READY
- player is now ready, playerid in data1MP_PLAYER_NOT_READY
- player no longer ready, playerid in data1MP_PLAYER_WORLD_STATE_CHANGED
- whenever the player object's world state changes, this gets broadcastMP_PLAYER_SET_CHARACTER_LEVEL
- whenever the player's character level is updatedMP_SET_MAP
- sent when the server selects a mapMP_SET_MAP_WORLD
- sent when the server selects a map worldMP_SET_GAME_TYPE
- sent when the server selects a game typeMP_TERRAIN_FULLY_LOADED
- when no new siege nodes have been loaded this sim... only sent in JIP mode
UI[]
SELECTED
- object was selectedDESELECTED
- object was deselectedFOCUSED
- object was focused (set to first in selection)UNFOCUSED
- object was unfocused (unselected probably)HOTGROUP
- object entered the hot groupUNHOTGROUP
- object left the hot groupMOUSEHOVER
- object had a mouse over beginUNMOUSEHOVER
- object lost its mouse over
Basic Gameplay Events[]
COLLIDED
- Collision occured with terrain or game object with damageGLANCED
- Collision occured with terrain or game object without damageDAMAGED
- 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 itemEXPLODED
- Sent to an object when it explodes.GO_TIMER_DONE
- go components may send themselves this messageGOT
- sent when an actor picks up an item.HEALED
- unusedKILLED
- Sent to an Object when it is killed.LEVELED_UP
- unusedLOST_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 effectsREQ_DEACTIVATE
- Turns off activatable objects.REQ_DELETE
- request deletion for selfREQ_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
- unusedSPELL_COLLISION
- from = GOID_INVALID if terrain collision OR goid of game object that was hit, Data1 = SiegePosSPELL_EXPIRATION_TIMER_RESET
- special command given from one skrit to another for extending spell durationSPELL_SYNC_BEGIN
- For synchronizing effects with skritSPELL_SYNC_END
- For synchronizing effects with skritSPELL_SYNC_MID
- For synchronizing effects with skritSTART_BURNING
- Makes this go catch on fireSTART_SIMULATING
- Tells a game object to start simulating itself using physicsSTOP_BURNING
- Makes the go stop burning if on fireTRIGGER_ACTIVATE
- Send to a Go to turn on any deactivated triggersTRIGGER_DEACTIVATE
- Send to a Go to turn off any triggersUNEQUIPPED
- Sent when an actor un-equips an itemWEAPON_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 CLIENTSMCP_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
- unusedMCP_GOAL_REACHED
- unusedMCP_INVALIDATED
- sent if the object you were tracking is no longer validMCP_SECTION_COMPLETE_WARNING
- Sent when a Go is near the end of its current sectionMCP_SECTION_COMPLETED
- Sent when a Go completes its section. :MCP_NODE_BLOCKED
- The required connections between nodes have changedMCP_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 killedMCP_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/attachedANIM_WEAPON_FIRE
- nema sends this message upon ammo release for projectile and contact for meleeANIM_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 succeedsENGAGED_FLED
- sent to actor: engaged object has started to fleeENGAGED_HIT_KILLED
- sent to actor: the engaged object was hit and killed by the actorENGAGED_HIT_LIVED
- sent to actor: engaged object was hit and still livedENGAGED_INVALID
- sent to actor: ERROR condition - engaged object has become invalidENGAGED_KILLED
- sent to actor: the engaged object was killed, but not by the actorENGAGED_LOST
- sent to actor: engaged object was probably unloadedENGAGED_LOST_CONSCIOUSNESS
- sent to actor: engaged actor has lost consciousnessENGAGED_MISSED
- sent to actor: engaged object was attacked and missedFRIEND_ENTERED_INNER_COMFORT_ZONE
- sent to actor: friend entered inner comfort zoneFRIEND_ENTERED_OUTER_COMFORT_ZONE
- sent to actor: friend entered outer comfort zoneFRIEND_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 jobJOB_FINISHED
- some objects may receive this message at the successful end of a jobJOB_REACHED_TRAVEL_DISTANCE
- sent when the job has moved the actor past x distance from the time the job has startedJOB_TIMER_DONE
- when a SimpleStateMachine (SSM) timer expires, the SSM sends itself this messageLIFE_RATIO_REACHED_HIGH
- Sent when an actors health crosses the threshold to the high ratioLIFE_RATIO_REACHED_LOW
- Sent when an actors health crosses the threshold to the low ratioMANA_RATIO_REACHED_HIGH
- Sent when an actors mana crosses the threshold to the high ratioMANA_RATIO_REACHED_LOW
- Sent when an actors mana crosses the threshold to the low ratioMIND_ACTION_Q_EMPTIED
- when the last action in the action queue was just deleted, the mind will message itself this messageMIND_ACTION_Q_IDLED
- when the action Q has idles for some fixed amount of time, the GO receives this messageMIND_PROCESSING_NEW_JOB
- when new action starts running this message is sentREQ_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:
[Note: Microsoft still hosts these two documents as of this article posting.]