Scripting overview

From Dragon Age Toolset Wiki
Revision as of 03:56, 16 July 2009 by Jassper (Talk | contribs) (Arrays)

Jump to: navigation, search

Scripting is used whenever the designers need control over the game's behaviour. The syntax for DA scripts is very similar to the C programming language.

Detailed documentation for the scripting language can be found in the "dascript.chm" file, available in your "Dragon Age\packages\core" directory.

The main uses for it are to:

  • Trigger plot events
  • Script conversation
  • Handle creature and party member AI
  • Automate client behaviour using the client scripting language
  • Script abilities - skills, talents, and spells

DA also has a client-side scripting language whose primary responsibility is automated testing of the game. See client scripts for more details.

AI Scripts are event based. Objects will have only one event-handling script assigned to them that will be run whenever they receive a game event. This script will receive the event type as a parameter and will need to determine the appropriate response for itself.

There are several file extensions associated with scripts:

  • NSS - script source file
  • NCS - compiled script
  • NDB - debug information file (required for using the script debugger to trace through this file)

Script editor

Scripts are created in the Designer Toolset using the Script Editor. Once created, scripts must be assigned to such things as Creatures before they can be used. Once assigned, both the referencing object and the script must be exported in order to be viewed in the game. Exporting happens automatically or can be done manually by the designer during testing.

The script editor supports the following features:

  • Undo and redo
  • Find and replace
  • Bookmarking: View -> Other Windows -> Bookmarks
  • Function, variable, constant, and template browser (visible on the right-hand side of the screen).
  • Auto-complete when typing function and variable names
  • Syntax highlighting
  • Auto-indention
  • Brace matching/highlighting
  • Ability to test compile a script
  • Jump to definition

To the right of the script editor is a sidebar with a browser that can display all currently-defined functions, variables, or constants, or a set of 'template' scripts that can serve as a starting point when writing a script from scratch. The browser also has a filter field where you can type in a partial string and have the list automatically exclude everything that doesn't include it in the name.

This is the constant browser:

Scripting constant browser.png

Here it is with "team" typed into the filter field, displaying only constants with names that contain the substring "team" (not case sensitive):

Scripting constant browser filtered.png

The templates that come with Dragon Age include frameworks for the most common event scripts found in the game. You can add your own templates quite easily, just create a text file with the template code in it and place it in the Dragon Age\Toolset\scripttemplates directory with the others.

Scripting template browser.png

Dragon Age's toolset supports Doxygen-style comments. If you double-click on a function name a help window will pop open to display its documentation. You can also jump to a function's definition (provided it isn't an engine-defined function in script.ldf) by right-clicking on it and selecting "go to definition".

Scripting help window.png

Writing scripts

For a script to compile, it needs a body:

void main()
{
}

The parameters on this can't be changed or the script won't compile.

Scripts associated with a dialog node take this alternate form:

int StartingConditional()
{
    return 1;
}

Including files

Other script files can be included by using the #include directive.

#include "rules"

void main()
{
    DoSomethingOrOther();
}

The included file is inserted into the main file before the script is compiled. Any number of files can be included.

No other C-style preprocessor commands are supported.

Variable Types

In Neverwinter Nights, variables for various objects could be created and set with functions such as SetLocalInt(). Dragon Age does not have analogous functions. Instead, all of the variables that a given object is able to have values assigned to must be defined ahead of time in a 2DA. This more restrictive approach is intended to improve performance and reduce the opportunity for errors (for example, making a typo in the name of a variable and losing track of it as a result).

The scripting language natively supports:

  • int - integers.
    int iInteger = 1234;
  • float - floating-point numbers.
    float fFloat = 0.1234f;
  • char - a single character.
    char cCharacter="c";
  • string - text strings.
    string sString = "Hello, World!";
  • vector - a series of floats that define an X, Y, Z coordinate in a map.
    float vVector = Vector(0.0f, 1.1f, 2.2f);
  • object

The server language has been expanded to support these engine defined structures:

  • event - events are signals that are sent to scripts by other scripts or by the world engine.
  • location
  • command - a command for a creature or object to do something. Commands are constructed using functions that return them, and then are added to a target's command queue with the AddCommand function.
  • effect
  • itemproperty
  • player

!!not sure which group this belongs to:!!

  • resource

Users can define their own structures:

struct Quaternion
{
    float w,x,y,z;
};

struct Quaternion Quat(float w, float x, float y, float z)
{
    struct Quaternion q;
    q.w = w;
    q.x = x;
    q.y = y;
    q.z = z;

    return q;
}

void main()
{
    struct Quaternion a = Quat(0.0f, 0.0f, 0.0f, 0.0f);
}

All of these types are always passed by value, which means a new copy is made whenever they are passed into a function or copied to another variable.

This means that most functions that are supposed to modify a structure in some way need to return the modified one:

void main()
{
    effect e1 = Effect(100);
    effect e2 = SetEffectType( e, 101 ); // e1 != e2 now
}

Arrays

Variables can be declared as arrays for any type except user-defined structs. Designers can create their own arrays of any major data type (int, string, object). Many functions will also return arrays (for example, creatures in an area will be returned as an array, and the action queue will be returned as an array).

These are different from C-style arrays in that they can be resized like STL vectors, and their notation is slightly different.

Arrays also behave differently from other objects in the scripting language because they are passed by reference. If an array is passed into a function and modified:

void main()
{
    // replace "()" with square braces
    int() i;  // new array of integers with a size of zero 
    i(0) = 5; // value of array position 0 is now 5
    i(1) = -1; // value of array position 1 is now -1

    int() j = i; // i and j point to the same array
    j(1) = 12; // value of array position 1 for both i and j is now 12!

    // no more replacing "()" with square braces

    SortArrayDescending( i );  // i and j are both sorted now, this function has no return value
}

GetArraySize(array) returns the size of an array.

Events

Events are a package of information that can be passed around in the game to trigger some behaviour. They are usually passed off to an object’s script for handling, but there are a few engine-only event types that are not exposed to the end-users.

Scripting Events have an integer type, target object, a time delay and a package of parameters (arbitrary number of ints, objects, floats and strings). Certain event types will be defined by the engine and referenced as #defines in the scripts.

Events are handled in the scripting language with the following types of commands:

  • Event() Constructor — Scripters can create events of any type and signal them to other objects
  • Signal Event commands — Events can be signalled to a single object, by proximity or to all objects with a certain group id
  • Parameter access — All of the parameters on an event can be get and set through scripting.
  • Event handling — Events can be passed to other script files through the HandleEvent command. This command will be run inline so that hierarchies of event-handling behaviour can be built.

See Events for a complete listing of event types.

A typical event-handling script would have the form

void main()
{
    event ev = GetCurrentEvent();
    int nEventType = GetEventType(ev); //extract event type from current event
    int nEventHandled = FALSE; //keep track of whether the event has been handled
    switch(nEventType)
    {
         case EVENT_TYPE_AREALOAD_SPECIAL:
         {
             ...
             nEventHandled = TRUE; //set this if no further action is required for this event
             break;
         }
    }
    if (!nEventHandled) //If this event wasn't handled by this script, let the core script try
    {
        HandleEvent(ev, RESOURCE_SCRIPT_AREA_CORE);
    }
}

In the event that you want to intercept some events but leave others to be handled by a default script (for example if you're overriding one aspect of a creature's event response but the rest of the default creature_core responses responses are fine) you can pass execution to the default script with the following:

    HandleEvent(ev, RESOURCE_SCRIPT_CREATURE_CORE);

(constants for referencing core script resources are available in the "global_objects_h" include file)

Resources

Use cutscenes_h include file for cutscene-related functions:

const resource CUTSCENE_GAME_INTRO = R"game_intro_.cut"; functions begin with CS_ prefix

Functions

There are two ways to discover a function and what it does.

If you type in the name of a function that's been defined and then an opening bracket, a tooltip will pop up showing the function's parameter types and return type.

Alternately, if you have the help window open (View -> Other Windows -> Help Window), then whenever you select a function in the function browser a help page describing the function will be displayed.

Passing parameters to scripts

To pass parameters to scripts they must be called with the 'runscript' command. This can be useful for writing debug scripts or changing actions based on input. To accept parameters simply add this line to your script:

string sParams = GetLocalString(GetModule(),"RUNSCRIPT_VAR");

You can then type 'runscript scriptname parameter1 parameter2 ... paramaterN' to pass parameters.

Some examples:


// Debug script: heals the object with the given tag. If tag is invalid, nothing happens
void main()
{
 
    string sTag = GetLocalString(GetModule(),"RUNSCRIPT_VAR");    
    object oCreature = GetObjectByTag(sTag);
    effect   eHeal = EffectHeal(25.0f)
    //ApplyEffect...
}


// Debug script: applies the visual effect passed in as parameter to the player
void main()
{
 
    string sEffect = GetLocalString(GetModule(),"RUNSCRIPT_VAR");    
    int    nEffect = StringToInt(sEffect);
    effect eVfx = EffectVisualEffect(nEffect);
    //ApplyEffect...
}

Error messages

See Script error for details of how to correct errors in scripts.

Timing

Commands, delayed events and object destruction all take place after the current script is finished running. Plot flags set in script are set in-line but the triggered plot script runs before the flag is actually set (this is clarified a bit in the plot script template).

Given that ExecuteScript is deprecated, HandleEvent should be used when you want the script to run in-line and DelayEvent should be used when you want to delay execution.

To delay execution when dealing with creatures, custom AI and (safe) command complete are nice tools. DelayEvent should be used whenever you know how long you want to wait before the event fires. (It can also be used in quite small time increments to give the illusion of smoothly sliding placeables with SetPosition or interpolate between two different atmosphere/lighting settings, but the overhead of this is noticeable if overused).