Scripting tutorial

From Dragon Age Toolset Wiki
Jump to: navigation, search
Scripting

The final piece of the puzzle in our little tutorial game is getting the MONSTERS_SLAIN flag set on our plot so that when the hero succeeds in his quest, the quest giver will know about it. This is done via scripting.

Scripting is a programming language with a syntax similar to C. This tutorial assumes a small amount of programming knowledge but hopefully it will be possible even for one with no experience to pick up the basics here.

Dragon Age's event-driven model

In Dragon Age scripts are primarily event-driven. An "event" is a package of information in a special format that gets passed around in the game to trigger behaviour. They are generated by other scripts or the game engine and are passed to an object's event-handling script.

Events have a type, a target object, a time delay and a package of parameters (arbitrary number of ints, objects, floats and strings).

As an example, the "EVENT_TYPE_ATTACK_IMPACT" event is sent by the game engine whenever an attack hits a target. It contains the identities of the attacker and the target, whether the attack was a critical hit, and the amount of damage that the attack did. It is sent to an event-handling script attached to the attacker, which takes the information it contains and handles it in whatever way is appropriate (reducing the target's hit points, usually).

For our purposes right now we're not going to need to generate any events of our own. We have set all of the hut monsters to be members of team 1, and the game has a built-in feature that will generate an event when all of the members of a particular team of creatures has been killed. Our new script will respond to that event by setting the plot flag so that when the hut monsters are all defeated, the plot will change to indicate the quest objective has been accomplished.

An enormous list of events for all occasions can be found at the event page. The one we're going to want our script to look for is EVENT_TYPE_TEAM_DESTROYED, which is sent when the last member of a team of creatures is destroyed. The event contains the team number within it.

Creating a basic event-handling script

Start by creating a new script resource. We'll call our script "hut_monsters_slain".

In most cases when a script is run the scripting engine will start with a function named "main". So for most scripts you'll want to start with this very basic beginning:

void main()
{

}

The code that will perform our tasks will be inserted between the { and } brackets. main doesn't return any results directly, so the function's return type is "void", and it takes no parameters, so the parameter list is an empty ().

Event type constants

Next we're going to need to make sure the script will know what we're talking about when we make references to events. The tag that tells us the type of each event is actually represented internally by a number, but since it would be impossible for a programmer to keep track of all the different event numbers without making hard-to-find mistakes Dragon Age has instead used named constants to represent them in a more human-readable name.

Those constants are defined in the script events_h. The "_h" suffix stands for "header", which is a special class of script that contains only various definitions meant to be included in other scripts and doesn't do anything on its own.

To include the header file, add this line to the top of our script:

#include "events_h"

All #include commands should be at the top of your script before anything else, but if there's more than one it shouldn't matter what order they are in.

To see whether our script now knows what EVENT_TYPE_TEAM_DESTROYED means you can use the constant browser on the right edge of the script editor to check if it's listed. The browser defaults to showing a list of all defined functions (the "f()" button), to set it to show all defined constants click on the "C" button.

Scripting constant browser.png

Even without any included files the list of defined constants is going to be quite long, and for scripts with many include files it can be very difficult to find any particular constant - especially if you aren't sure exactly what its name is. To find constants more easily, type any part of its name into the filter field at the top.

Scripting constant browser filtered.png

To insert the constant into the script at the location where the cursor is currently located, simply double-click on the constant in the constant browser. This avoids any risk of making a typo. You can also type the beginning of the constant (or any function, etc.) and press Ctrl-Space to see a list of matching values.

Extracting information about the current event

When an event is sent to a script the script's main function will be run, but it won't know anything about the event that caused it to be run without some explicit instructions.

First, you will need to retrieve the package of data that is the event. We'll define a variable named ev that will hold this package, and set it to the "event" data type. To retrieve the current event and stick it into that variable, we'll use the engine-defined function GetCurrentEvent.

The first line of the main function will therefore be: event ev = GetCurrentEvent();

Every event will have an event type. As described above, this event type is represented internally by an integer. We'll create an integer variable named nEventType to hold it, and use the GetEventType function to extract it from ev. The next line will be:

int nEventType = GetEventType(ev);

The rest of the information packaged with the event depends on exactly what kind of event it is, so the rest of the code we write will be event-specific. To make sure only the correct code is run we'll use a switch statement. We're only looking for one particular event so we could just as easily have used an if statement but a switch is more extendable for future use.

switch(nEventType)
    {
         case EVENT_TYPE_TEAM_DESTROYED:
         {
             //our event-specific code goes here
             break;
         }
    }

We know from the documentation for the EVENT_TYPE_TEAM_DESTROYED event, that it comes with a single piece of information; the team number. It's an integer, so we can extract it with the GetEventInteger function. GetEventInteger takes two parameters, the event object (which we've stored in the variable ev) and the index of the integer we want, in this case 0 (this is listed in the documentation).

We also know that the team number we gave the hut monsters is 1, so we want to update the plot only when we receive an event indicating that team number 1 has been destroyed. We can do this with an if statement:

if (GetEventInteger(ev,0) == 1)
{
    //code to update the plot here
}

Interacting with plots

To interact with a plot's flags you'll need to have some way for the script to know where the plot flags are and what they're named. This is done in a similar manner to how we told the script what the event types were named; by including the plot file in the script.

Plots are not the same as regular scripts, though, so when including them they are distinguished from regular scripts through the use of a plt_ prefix in their name. The plot we wish to interact with is called clear_the_hut, so at the top of the script file you'll need to put the line:

#include "plt_clear_the_hut"

Now your script will have access to the plot's flags and will be able to read or change them. You'll find that several new constants have been defined; PLT_CLEAR_THE_HUT contains the plot object, and the integer constants MONSTERS_SLAIN, QUEST_ACCEPTED and REWARD_RECEIVED identify the flags within the plot.

Changing the state of the flags is done with the WR_SetPlotFlag function that is defined in the include file wrappers_h (wrappers_h contains a variety of utility functions that handle operations requiring low-level access to the game. Add #include "wrappers_h" to the top of your script). In our case, we want to set the plot flag MONSTERS_SLAIN. The WR_SetPlotFlag function takes three parameters; the plot identifier, the flag identifier, and the state we want to set it to:

WR_SetPlotFlag(PLT_CLEAR_THE_HUT, MONSTERS_SLAIN, TRUE);

Associating an event script with an object

The script is almost complete now, but until we've linked it up to some object in the game that can receive events it will never actually be run.

Most objects in the game will have a field named "Script" shown in the object inspector. According to the documentation for the EVENT_TYPE_TEAM_DESTROYED event it is sent to the area object the team is in, so we'll want to open the hut_interior area now and go to its Script entry. The default event script for areas is area_core, which handles basic area events in a default way.

This is where you'll want to put the hut_monsters_slain script. Click on the ellipsis (Ellipsis.png) button and select the new script. It will replace area_core, and now when there are events sent to this area our new script will be triggered and handle them.

Passing execution to other event handlers

There is just one problem remaining to be resolved now. As mentioned above, the area_core script normally handles a variety of events that are sent to areas. Now that we've replaced the default event handler with our custom script, and our script only responds to one specific event, there's nothing responding to the rest of the events coming into the area any more. Our custom script has preempted and therefore effectively disabled all the default area event processing by not responding to every possible area event.

We don't want to re-implement all that default event handling in our own script, for a variety of reasons; it's a lot of work, we could make mistakes, and it would be difficult to update the default event handling across all of our areas in the future. Fortunately there's an easy way to tell Dragon Age to use another script to handle events; namely, the HandleEvent function. It takes two parameters, the event object to be handled and an identifier for the script that should handle it.

A set of constants defining identifiers for the common default scripts is in the global_objects_h header file, but we won't need to include that because wrappers_h includes it already. So all we need to do is insert the following line after our switch statement:

HandleEvent(ev, RESOURCE_SCRIPT_AREA_CORE);

(The RESOURCE_SCRIPT_AREA_CORE constant is defined in global_objects_h, which we don't need to include explicitly in this case because it's already included in wrappers_h.)

With this addition, whenever any event is sent to the hut_interior area, first our script will run and process the event, and then it will send it on to area_core to handle the default area processing normally done when no custom event handling script is being used.

The final text of our script, then, is:

#include "events_h"
#include "plt_clear_The_hut"
#include "wrappers_h"

void main()
{   
    event ev = GetCurrentEvent();
    int nEventType = GetEventType(ev);
    
    switch(nEventType)
    {
         case EVENT_TYPE_TEAM_DESTROYED:
         {
              if(GetEventInteger(ev,0) == 1)
              {
                   WR_SetPlotFlag(PLT_CLEAR_THE_HUT, MONSTERS_SLAIN, TRUE);
              }
              break;
         }
    }
    HandleEvent(ev, RESOURCE_SCRIPT_AREA_CORE);
}


Language: English  • русский