Icemage's Dungeon Siege™ Skrit Skribblings

An Unofficial Skrit Tutorial

How to Read Skrit Components, version 2.00.02



PURPOSE 

A lot of people are mystified by Skrit and how it functions in Dungeon Siege. This document is intended to dispel some of that confusion, and will hopefully help some aspiring modders out there.  

DISCLAIMERS

Any errors in this document are strictly the author's.  Use of the knowledge contained herein is at your own risk.  The author is not responsible for any data corruption, lost time, or other consequences of any application of this document.  Dungeon Siege™ is a registered trademark of Gas Powered Games, Inc. and Microsoft, Inc.

Copyright ©2002 by Icemage.  All rights reserved.  This document may not be reproduced or distributed in whole or in part without the express written consent of the author.

THE BASICS

So what is a Skrit component?

Well, there are a few common characteristics shared by Skrit components, so let's start with those:

(A) Most Skrit components reside in one of several places: **
          Logic.dsres/world/contentdb/components :
                These Skrits define Skrit code that customizes behavior of various objects in the
                game, including spells, items, and non-player characters (NPCs, such as
               shopkeepers and townsfolk)
          Logic.dsres/auto : 
                These Skrits define Skritbots, automated code which handle various in-game duties.
                Note : Skritbots are currently non-functional in DS version 1.00 and 1.09B.
          Logic.dsres/art/animations : 
                These Skrits handle the work of dealing with specialized animation jobs.
          Logic.dsres/global : 
                These Skrits are accessible by all game elements, and cover a variety of tasks, such
                as controlling moods and generating special effects for spells and items.
          Logic.dsres/world/ai/jobs : 
                These Skrits define how game objects with Artificial Intelligence routines (such as
                characters and monsters) do things like walk, attack, fidget, etc .

(B) Skrit components are text files written in Skrit, and have a filename extension of ".skrit". **

(C) Skrit components are "called" by various things in the game, most often a GO (Skrit shorthand for Game Object) that has to do something unusual to the game environment. 

** The exception to this rule is "embedded" Skrit, which is Skrit code which resides in a ".GAS" file, more commonly known as a template file.  If you are just starting to work with Skrit, don't worry about this type of Skrit code.  Embedded Skrit is an advanced topic that lies beyond the scope of this document, and is very rarely used in any case.  

Ok, so now that you have an idea of how to recognize a Skrit component, what sort of things make up a Skrit component?

Let's take a look at a simple Skrit, spell_lightning.skrit, that can be found inside the structure of the Logic.dsres file:

Logic.dsres
    >art
    >auto
    >config
    >ui
    >world
        >ai
        >contentdb
            >components
                >chests
                >commands
                >dev
                >doors
                >elevators
                >emitters
                >fx
                >generators
                >generic
                >levers
                >map_specific
                >spells
                    >spell_lightning.skrit
                >test
                >traps
                >weapons
            >templates
        >global
        >maps


Code from spell_lightning.skrit:

/////////////////////////////////////////////////////////////////////////
//
// File : spell_lightning.skrit
// Author(s): Eric Tams
//
// Copyright © 2001 Gas Powered Games, Inc. All rights reserved.
//-----------------------------------------------------------------------
// $Revision:: $ $Date:$
//-----------------------------------------------------------------------
//
// Wrapping lightning damage into the attack component for a spell.
//
/////////////////////////////////////////////////////////////////////////

property string effect_script$="" doc="Effect script to call for effect";
property float dur$=1.0 doc="How long the effect plays.";

owner = GoSkritComponent;
#include "k_inc_spl_utils"

GoID target$;
GoID caster$;

startup state Idle$
{
    event OnGoHandleMessage$( eWorldEvent e$, WorldMessage msg$ )
    {
        if( e$ == WE_REQ_CAST )
        {
            target$ = MakeGoID( msg$.GetData1() );
            caster$ = msg$.GetSendFrom();

            if( !target$.IsValidMp || !caster$.IsValidMp )
            {
                SetState Finished$;
                return;
            }

            PackLightning$( dur$ );

            Siegefx.SRunScript(effect_script$,target$,caster$,params$,owner.GoID, e$ );

            this.CreateTimer( 1, dur$ );
        }
    }
    transition -> Finished$: OnTimer( 1 );
}

state Finished$
{
    event OnEnterState$
    {
        PostWorldMessage( WE_REQ_DELETE, owner.GoID, owner.GoID, 10 );
    }
}


This Skrit does something very simple:  It takes a spell effect and adds lightning damage to it.  

It does so in several distinct steps:

Step 1  The Skrit does some preparation before the spell is cast.
Step 2 The Skrit waits for a successful attempt to cast the spell.
Step 3 The Skrit adds the lightning damage to the effect to be generated by the spell.
Step 4 The Skrit calls for the spell's visual effects to begin.
Step 5 The Skrit waits for the spell's effect to end.
Step 6 The Skrit cleans up and deletes the spell from the game.


Let's take a item-by-item look at the code itself now to see how this is accomplished.

At the top of this Skrit, the first thing you'll see is a bunch of lines that begin with a double slash //.  All text following double slashes represent a commentComments are used to explain and document the code, but do not have any effect on the way the code actually runs (they are ignored by the program).    There are actually several different ways to comment out code, but for simplicity we'll just use this one for now.  

In this case, the comments remind us about the name of the skrit, the author, some copyright information, some version information, and a brief description of what the code actually does.

Step 1  The Skrit does some preparation before the spell is cast.

Now, the first two non-commented lines we see in this Skrit are:

property string effect_script$="" doc="Effect script to call for effect";
property float dur$=1.0 doc="How long the effect plays.";

...
...
Huh? What does all this stuff mean?

property string effect_script$="" doc="Effect script to call for effect";
property float dur$=1.0 doc="How long the effect plays.";

The property keyword indicates that the item is a characteristic value that can be set by whatever is calling the Skrit.  Don't worry if you don't understand this explanation for now, more will be revealed below.

property string effect_script$="" doc="Effect script to call for effect";
property float dur$=1.0 doc="How long the effect plays.";

The string and float keywords indicate the data type of each property.  Data types refer to how the game code represents values that need to be tracked or remembered.

Some common data types used in Skrit are:

Int 
"Int" stands for "integer" values, which are non-fractional positive or negative numbers.

Float
"Float" stands for "floating point decimal" values, which are rational numbers (numbers that can have fractional parts).  Note that the decimal point is optional for non-fractional values assigned to a Float.

String
In common programming lingo, a string refers to a text value that is stored and manipulated by a program.  In Skrit, string values should be enclosed in quotation marks(").

Bool
"Bool" stands for a "Boolean" value.  Named after George Boole, a 19th century mathematician, Boolean values can only have one of two values: True or False.

In this case, the first property is a string (a text value).
The second line tells the game that the second property is a float (a decimal value with fractions).

property string effect_script$="" doc="Effect script to call for effect";
property float dur$=1.0 doc="How long the effect plays.";

These two items tell the game what the name of the variables are. 

REMINDER: User-defined values must ALWAYS end in a "$".

The values after the = signs indicate the default value. If the object that calls the Skrit component doesn't specify a value for a property, the default value is the value assigned to the property.

So, in this case, if no value is otherwise given for effect_script$, it defaults to an empty string ("");
if no value is given for dur$, it defaults to a value of 1.0.

property string effect_script$="" doc="Effect script to call for effect";
property float dur$=1.0 doc="How long the effect plays.";

The doc="<some text>" portion of the command gives a bit of description to each value (to help remind you what that value represents later if you want to come back and edit the code).  

Note the semicolons (;) at the end of each line. The semicolons are EXTREMELY important. Dungeon Siege will not process your Skrit code correctly without a semicolon at the end of each Skrit command, so pay close attention to them!  If you get an error, the very first thing you should look for is a missing semicolon!

owner = GoSkritComponent;

owner is a special value recognized by Skrit.  It generally refers to the GO (Game Object) that called the Skrit code.  In this case, GoSkritComponent is a function that gets the GO of the spell which called this Skrit.  Note that, because owner is automatically recognized by Skrit, it is not a user-defined value and so you don't need to add the "$" symbol on the end (this is an exception to the REMINDER above)

#include "k_inc_spl_utils"

The #include instruction should be familiar to those who have learned C++ or Java programming, but for the uninitiated, this instruction simply tells the game to go look for the code from the file "k_inc_spl_utils.skrit" and add it to this one.  This is important because the code needs a function that "k_inc_spl_utils.skrit" provides (PackLightning$).  Note that the Skrit file called by a #include instruction can be located anywhere inside the Logic.dsres/logic/world/components folder, or one of its sub-folders.

NOTE: This instruction does NOT use a semicolon at the end, as it is not a Skrit command.  It is actually something called a "compiler directive," which is a complex techno-babble term for a very simple concept.   A compiler directive is an instruction that is processed before any of the code actually gets translated into something the computer can use.  What this means is that the game actually deals with compiler directives before generating the code that runs the game - in this case, it needs to "include" the code from another Skrit file to get the required code for the PackLightning$ function before it starts translating into "computer-speak."

GoID target$;
GoID caster$;

These two lines tell the game that you want to create two variables (a variable is a value that can be changed by the code) to save two GoID values, called target$ and caster$.  So what's a GoID? A GoID is a Game Object Identifier.  One way to think of a GoID is like the address for your house.  No other house has the same address.  And so it is with GoIDs :

GoIDs are unique number values which every object in the game possesses.  

No two objects in Dungeon Siege can possess the same GoID, so you can use them to keep track of things in the game without risk of getting the wrong object.


Ok, now what?

Ok, before we get further into this, I'll give a brief overview of the concepts of state-based virtual machines, states, transitions, triggers and events.  Stay with me, folks - this isn't as bad as it sounds.

What is a state-based virtual machine?

In plain English:

A state-based virtual machine is a machine which operates differently under different conditions (called states)

That's a bit vague, so I'll try to give you some real-world examples:

Example 1: A light switch

Ok, this is one of the simplest machines you use every day.  You flip the switch, it turns the light off.  You flip it again, it turns the light on.  So how does this make it a state-based virtual machine?

State A: Light is OFF
State B: Light is ON

In State A and State B, the same action (flipping the switch), produces a different effect depending on the State that exists at the moment.  If the light is OFF (State A), it changes or transitions to State B.  If the light is ON (State B), the switch transitions to State A

Thus:

A state is a distinct condition which determines how the machine behaves.
A
transition is a change from one state to another state.

Example 2:  A traffic light

I'll assume everyone knows how traffic lights work. 

Green means Go.
Yellow means Caution.
Red means Stop.

How can a traffic light be a state-based virtual machine?

Remember that our simple definition above is that we need a machine that works differently under specific conditions.

State A: Green light
State B: Yellow light
State C: Red light

For most typical traffic lights, State A (Green) transitions to State B (Yellow) after a certain amount of time.  Then State B (Yellow) transitions to State C (Red) after another period of time.  Lastly, State C (Red) transitions to State A (Green) after yet another pause, and the process starts all over again.

Are you starting to see how this works?  Great!

What is a trigger?

A trigger is a condition which causes the machine to exhibit a specific behavior.

In Example 1 above, the trigger could be considered to be flipping the light switch (causing a transition).
In Example 2, the trigger is the timer in the traffic signal, causing the transitions between states.

So what is an event?

Events are a special set of triggers that the machine keeps track of automatically.   In Example 1, one way we could define the way a light switch works would be:

Startup State A: Light is ON
    Trigger:  Flip switch
        Command: Transition to State B
    Event: OnEnterState
        Command: Turn light ON

State B: Light is OFF
   Trigger:  Flip switch
        Command: Transition to State A
    Event: OnEnterState
        Command: Turn light OFF

This example assumes we start with the light ON (Startup State A).  A Startup State is the beginning condition that we start the machine in.  In this case, when we start up, we "enter State A," which is caught by the OnEnterState event (literally "do this/these commands when we enter this state").  That event turns the light ON (if it wasn't on already).

Now, the machine (the light switch) waits until something happens to force it to do something.  In this case, when we flip the switch. we cause the trigger to activate the command to transition to State B.

Once we've transitioned to State B, the OnEnterState event of State B runs, and turns the light OFF, and we go into a wait again. The next time the switch is flipped, it will trigger the transition to State A.

Let's look at this same example again, only from a Skrit-like point of view:

Startup StateA$ // Light is ON
{
    Trigger FlippedSwitch$
        {
             SetState StateB$; //Transition to StateB$
         }
    Event OnEnterState$
        {
            Light_Is_On$ = True;
        }
}

StateB$ //Light is OFF
{
   Transition -> StateA$ : FlippedSwitch$; //Transition to StateA$

    Event OnEnterState$
    {
        Light_Is_On$ = False;
    }
}


Note how I've added the brackets { } to enclose each set of items following a State, Event, or Trigger definition. Remember also that // denotes where a comment begins - it's bits of commentary text used to explain what's happening to us humans, and is not read by the machine.

Now, look at the line:

   Transition -> State A$ : FlippedSwitch$; //Transition to StateA$

You're probably scratching your head saying "Huh? What's that colon doing there? And why aren't there any brackets?"

This is the way triggered transitions are written in Skrit:  

 Transition -> State : Condition;  //Transition to State when Condition is met

When the Condition becomes True, the game transitions to the specified state.

This is functionally equivalent to:

    Trigger Condition
        {
             SetState State; //Transition to State
         }

...which is the version you see in State A$.

Now you know about state-based virtual machines, states, transitions, triggers and events!  

See, that wasn't so hard, was it?

Moving on...

Now let's analyze the next block of code from spell_lightning.skrit:

startup state Idle$
{
    event OnGoHandleMessage$( eWorldEvent e$, WorldMessage msg$ )
    {
        if( e$ == WE_REQ_CAST )
        {
            target$ = MakeGoID( msg$.GetData1() );
            caster$ = msg$.GetSendFrom();

            if( !target$.IsValidMp || !caster$.IsValidMp )
            {
                SetState Finished$;
                return;
            }

            PackLightning$( dur$ );

            Siegefx.SRunScript(effect_script$,target$,caster$,params$,owner.GoID, e$ );

            this.CreateTimer( 1, dur$ );
        }
    }
    transition -> Finished$: OnTimer( 1 );
}

Armed with your knowledge of states, events and transitions, you now can tell several things immediately about this code:

> The Skrit starts off in the Idle$ state due to the "startup" keyword.
> The Skrit looks for something called a eWorldEvent.
> The Skrit transitions to the Finished$ state when timer #1 finishes its countdown.

Now I'll fill in the gaps for you:

Step 2  The Skrit waits for a successful attempt to cast the spell.

The Idle$ state is the starting condition for this Skrit.  While in the Idle$ state, the code waits until something forces a transition to another state.  There are two conditions that will cause a transition from the Idle$ state.  One is an event, the other is a triggered transition.

    event OnGoHandleMessage$( eWorldEvent e$, WorldMessage msg$ )

This event occurs whenever anything in the game generates or "posts" a message to the engine (this sort of message is called an eWorldEvent).  The game keeps track most things by having everything post messages about what it is doing, so this event will check every message that gets sent to the GO(Game Object) that called the Skrit (this is why the event is called OnGoHandleMessage$).  

So how does it know which one it's looking for?  That's where the next line comes in:

        if( e$ == WE_REQ_CAST )
        {
            ...
        }

Experienced coders will recognize the form of a if statement, but for those who don't know what that is, I'll cover it now.  If you are already familiar with if statements, just click here.

An if statement is a simple yes-or-no test.  
If the condition specified inside the parentheses () is True, then the game will execute the  command(s) enclosed by brackets { }.  
If the condition is False, then the game will skip all of the commands enclosed by the brackets { }.

If statements in Skrit take one of several forms:

Form 1: Simple If statement

If ( condition )
    {
    Do_This_Command;
    Do_This_Other_Command;
    ...
    }

This is the standard form of an If statement as described above.

Form 2: If...Else statement

If ( condition )
    {
    Do_This_Command_If_Condition_Is_True;
    Do_This_Command_If_Condition_Is_True;
    ...
    }
Else
   {
   Do_This_Command_If_Condition_Is_False;
   Do_This_Command_If_Condition_Is_False;
   ...
   }

This form adds a new element to the If statement: an Else clause.  An Else clause tells the code what to do when the Condition is False, rather than True.

Let's look at an example:

If ( treat$ = "cookie")
 {
 Eat ( treat$ );
 Smile;
 }
Else
 {
 GetNewTreat;
 }

So if we have a "cookie" we'll eat it, and smile, otherwise, we go get a new treat.  I guess we're picky about our desserts!

Form 3: If...Else If statement

If ( condition1 )
    {
    Do_This_Command_If_Condition1_Is_True;
    Do_This_Command_If_Condition1_Is_True;
    ...
    }
Else If ( condition2 )
   {
   Do_This_Command_If_Condition2_Is_True_But_Condition1_Is_False;
   Do_This_Command_If_Condition2_Is_True_But_Condition1_Is_False;
   ...
   }

This third form is simply an If statement attached to the Else clause of another If statement.  You can connect any number of If statements together this way, which will cause the game to run the commands for the first condition it finds to be True.

In this case, our code is looking for an eWorldEvent (stored in the variable e$) called WE_REQ_CAST, which is triggered whenever a GO is used to cast a spell.  If this condition is met, then the code inside the brackets { } is run:

            target$ = MakeGoID( msg$.GetData1() );
            caster$ = msg$.GetSendFrom();

The first line here gets the GoID (Game Object Identifier, see the definition above if you missed it) of the target of the spell.
The second line gets the GoID of the caster of the spell.

            if( !target$.IsValidMp || !caster$.IsValidMp )
            {
                SetState Finished$;
                return;
            }

This if statement tests two things.  The exclamation point ( ! ) stands for "NOT."  The double pipe ( || ) stands for "OR."

If we re-read this, we get, in plain English:

"If the target is NOT valid in multiplayer OR the caster is not valid in multiplayer, then set the state to Finished$(clean up details before exiting) and return (exit immediately from this Skrit)."

Step 3 The Skrit adds the lightning damage to the effect to be generated by the spell.
            PackLightning$( dur$ );

... calls a function from "k_inc_spl_utils.skrit".  You'll recall from above that the line:

#include "k_inc_spl_utils"

...adds the code from that skrit file to this one when the game goes to generate the machine code.  In this case, the PackLightning$ function is the one that actually adds the lightning damage into the spell mechanics.
 

Step 4 The Skrit calls for the spell's visual effects to begin.
Siegefx.SRunScript(effect_script$,target$,caster$,params$,owner.GoID, e$ );

This confusing little command creates a visual effect with the SiegeFX engine.

effect_script$ specifies the name of the script that will create the visual effect.
target$ is the GoID of the target of the spell.
caster$ refers to the caster's GoID.
params$ is borrowed from "k_inc_spl_utils.skrit", which allows you to add extra options for some effects.
owner.GoID is the GoID of the spell that called this Skrit.
e$ is the eWorldEvent that triggered the effect (in this case, WE_REQ_CAST).

            this.CreateTimer( 1, dur$ );

This command tells the game to set up a timer, with an identifying number 1, which counts down for a number of seconds equal to dur$ (specified in the property section at the top of the Skrit code ).

So the OnGoHandleMessage$ event looks for an attempt to cast the spell which called the Skrit, tells the game engine to add lightning damage to the effect specified by the spell, then runs the effect itself.  If either the caster or the target is illegal for the spell, the code aborts immediately.

Step 5 The Skrit waits for the spell's effect to end.
    transition -> Finished$: OnTimer( 1 );

This is a simple triggered transition.  Once timer #1 (called by the this.CreateTimer( 1, dur$ ); command) hits 0.0 seconds remaining, the game will transition to state Finished$.

Step 6 The Skrit cleans up and deletes the spell from the game.
state Finished$
{
    event OnEnterState$
    {
        PostWorldMessage( WE_REQ_DELETE, owner.GoID, owner.GoID, 10 );
    }
}

This part should be easy for you to figure out now.  Once the Skrit transitions to state Finished$, it posts a message to the game engine asking to delete the specified Game Object (in this case, the copy of the GO of the spell which called this Skrit).

CONCLUSION

As you can see, there's a fair amount of complexity in reading and understanding the Skrit code, but it's certainly not impossible, nor particularly difficult once you get some practice with it.  This tutorial should give you the basic tools you need to get a handle on what's happening in the code.

If you have questions, please feel free to post a question the forums at www.DSRealms.com.  I'm always happy to help others understand the game.

 

Happy Skritting!

Icemage






VERSION HISTORY

1.00: 6/10/02
    First release.

2.00: 6/12/02
    Numerous changes.
    - Fixed color coordination.  Made color use more consistent, and brighter.
    - Fixed numerous typos, grammatical errors, and punctuational errors.
    - Added table structures for readability
    - Made the document friendlier to 800x600 screen sizes
    - Expanded and clarified explanations for file locations.
    - Expanded explanation of base data types
    - Expanded explanation of If...Else structures

2.00.01: 6/12/02
    Handed the document to
Xaa, who writes HTML pages and HTML e-books for a living, and he shrieked in terror at the vivid colors before completely re-formatting the document.
    - He also added links to cross-referenced information within the document to make it easier to navigate.

2.00.02: 10/18/02
  
Fixed a few formatting and typographical errors.