Module tutorial

From Dragon Age Toolset Wiki
Jump to: navigation, search

The simple demo module that is included with the Dragon Age Toolset is intended to show off a variety of methods that will be commonly used in real stand-alone modules. The documentation on this website will walk you through the major steps required to set it all up, but the demo module itself also has documentation embedded within it in the form of comments; it is intended to be explored in the designer toolkit and dissected to see how things tick.

One thing that this demo doesn't demonstrate is the art of writing. As such the 'plot' of the adventure is somewhat nonsensical.

This tutorial also assumes that you've read many of the other basic tutorials elsewhere on this site and are familiar with the basic functioning of the toolkit. Links will be provided directing you to more specific information if needed.

Organization

There are two major groups of resources that are used when creating a Dragon Age module; designer resources and art resources. Designer resources are housed in a database whereas art resources take the form of conventional files on your file system.

For your art resources the most convenient approach it is probably to create a directory tree with subdirectories sorting your art resources by what regions of the game they appear in. The directory tree should be someplace easily accessible and with full read/write priviledges - somewhere in your "My Documents" folder is a good place to put them.

In our example we're going to have three areas:

  • A swampy outdoor area with a small tavern in it
  • The tavern's interior
  • An outdoor roadway

Naming conventions

See Naming conventions

Designer resources will have a three-letter extension, much like a regular file system file, but this is not editable and does not frequently come up.

Resources in the designer toolkit can also be placed into folders, again much like a regular file system. Unlike most file systems, though, you don't need to explicitly create a folder in order to put a resource into it. Just edit the resource's "folder" property and if the folder didn't have anything in it before it'll be created automatically. Folders that don't have any resources in them are removed automatically.

Here's a preview of what we're going to put into the demo module, to illustrate how this naming convention looks in practice. We'll be using the four-letter prefix "demo" instead of the usual three-letter one for clarity.

NOTE: when you create new items, it's best to stick to unique naming, specially if items belongs to your module. For example: your module UID is "asar" so item, createre, area or any other file should begin with asar_ (asar_mysword).

Setting up character generation in a new module

The most basic steps of creating a new module are described in "creating a module". A Dragon Age module, when first created, lacks many very basic components such as the ability to generate a character or an area to start in.

Note that you can actually start creating areas and testing content before you set up character creation if you prefer, the game will provide a player with a simple (and very weak!) default character if character generation isn't performed. You can also override the default starting area and waypoint every time you export for testing purposes, using the "Export Options" found under the "Tools" menu, so you can skip directly to the area you're working on without needing to write debugging scripts.

We're going to follow the flow of the finished game for now, though, so after creating the module the first thing we'll want to do is create a module script that triggers character generation.

Once you've created the initial empty module you'll need to create a module script. We call it "demo_module". The event that we'll want to handle character generation in is EVENT_TYPE_MODULE_START, which is sent to the module script only once when the game is first started up.

To call the default character generation system, simply have this event call the following functions:

PreloadCharGen causes the game to load up various resources that will be used in character generation, and StartCharGen causes the game to show the character generation GUI to the player.

Creating our areas

This module will be very simple, consisting of only three areas - only two of which will have any significant amount of detail. They will be named:

  • demo100ar_wilderness
  • demo200ar_tavern
  • demo300ar_road

Three level layouts (LVL files) are included with the module for use in these areas; ost101d (a swampy wasteland with a small wooden structure), hrt002d (a small tavern interior), and lgt600d (a section of roadway). The naming convention for level layouts used internally at BioWare is specific to the single player campaign, the meaning of these filenames is not relevant to this basic tutorial.

The area editor (and the game itself) cannot use a LVL file directly, it must be compiled into a more efficient form for use. Open up each LVL file in the level editor and, under tools, select "Post to local" to compile the level and export it to an appropriate directory where the game and toolset will be able to find it.

Once you've done this you can select the layout in the area editor's "Area Layout" property field.

Placing area transitions and setting up the world map

See the Area tutorial for details on how to link areas up with level transitions. The transition from demo100ar_wilderness to demo200_tavern is straightforward and is basically the same as what's shown in that tutorial.

The transition between demo100ar_wilderness and demo300ar_road, however, is going to be handled via the world map instead. The demo's map is a resource named demo000mp_world.

To create an area transition that sends the player to the world map, set the PLC_AT_DEST_AREA_TAG to the string "world_map". The placeable_core script recognizes this string as a special override that triggers a map transition by sending the module script an EVENT_TYPE_BEGIN_TRAVEL event.

Note that you can have multiple world maps in a module, but under the default code you can't directly specify which one an area transition will send the player to. Instead, you will need to call the script function WR_SetWorldMapPrimary to specify which world map is the currently active "primary" one. In this demo the code that performs this is also in the EVENT_TYPE_MODULE_START event of the module script, since we only have one map and it only needs to be set as primary once. If you have multiple maps in your module you'll need to call this function again later from some other scripted event to change it.

Here, then, is the complete code for the demo module's EVENT_TYPE_MODULE_START event:

        case EVENT_TYPE_MODULE_START:
        {
            object oMapId = GetObjectByTag("demo000mp_world");
            WR_SetWorldMapPrimary(oMapId);
 
            PreloadCharGen();
            StartCharGen(GetHero(),0);
            break;
        }

Although the default placeable_core script has code in it to recognize that the "world_map" string should lead to a world transition and generates an EVENT_TYPE_BEGIN_TRAVEL event, the default module_core script doesn't actually do anything with this event. This is because the map transition code used in Dragon Age's single player world map transitions is rather complex and has to handle many special cases that depend on plot elements specific to the single player campaign, so BioWare never implemented a basic default - it would have had to be overridden completely anyway.

Fortunately it's quite easy to add one, though there's a slight hitch; the area transition code needs to be split over two different events.

  • In EVENT_TYPE_BEGIN_TRAVEL you'll be able to get the target area and waypoint tags from the event object, and then call the WorldMapStartTravelling function to cause the map to start animating the map trail path the player is following.
  • While the map trail animation is playing, the EVENT_TYPE_WORLDMAP_PRETRANSITION event is called. This is the event where you'll want to call the UT_DoAreaTransition function to start the loading process of the destination area. The area will load at the same time that the map trail animation is playing, making the transition seem more seamless for the player. Note, however, that EVENT_TYPE_WORLDMAP_PRETRANSITION's event object doesn't contain the destination area and waypoint tags as members; you'll need to use local variables in the module's variable table to preserve these strings and pass them on from EVENT_TYPE_BEGIN_TRAVEL.

The basic code to handle map travel, then, is:

        ////////////////////////////////////////////////////////////////////////
        // Sent by: The engine
        // When: the player clicks on a destination in the world map
        ////////////////////////////////////////////////////////////////////////
        case EVENT_TYPE_BEGIN_TRAVEL:
        {
            string sSource = GetEventString(ev, 0); //area tag source location
            string sTarget = GetEventString(ev, 1); // area tag target location
            string sWPOverride = GetEventString(ev, 2); // waypoint tag override
            if (sSource != sTarget)
            {
                //store target area's tag to a local module variable
                SetLocalString(GetModule(), "WM_STORED_AREA", sTarget);
                //store target waypoint tag
                SetLocalString(GetModule(), "WM_STORED_WP", sWPOverride);
                //initiate the map's travelling animation. The engine will
                //send EVENT_TYPE_WORLDMAP_PRETRANSITION once it's started.
                WorldMapStartTravelling();
            }
        }
 
        ////////////////////////////////////////////////////////////////////////
        // Sent by: The engine
        // When: the world map has begun its "travelling" animation
        ////////////////////////////////////////////////////////////////////////
        case EVENT_TYPE_WORLDMAP_PRETRANSITION:
        {
            //retrieve the target area tag we stored in EVENT_TYPE_BEGIN_TRAVEL
            string sArea = GetLocalString(GetModule(), "WM_STORED_AREA");
            //retrieve the target waypoint tag
            string sWP = GetLocalString(GetModule(), "WM_STORED_WP");
            //execute the area transition to that target.
            UT_DoAreaTransition(sArea, sWP);
            break;
        }

We'll need to come back to the module script later on to add some plot-specific code, but for now this is all that's needed to enable a basic adventure to stand on its own.

Introductory cutscene

After the player has finished character generation we play an introductory cutscene for him before the game starts.

The cutscene itself is quite simple, consisting of a few camera movements and an actor (substituted by the player) playing a walking animation. See cutscene tutorial for how to create a cutscene.

To make it play when the game begins we need to call it at the appropriate time with the CS_LoadCutscene function. The appropriate time has to be after the area that the cutscene is located in as loaded, otherwise the cutscene won't be able to play. The event that's best suited for this is EVENT_TYPE_AREALOAD_SPECIAL, which is called on an area's event script after the area has loaded but before any of the other game systems (AI and so forth) have been enabled. So in this case we want to insert the function call into the area script for the starting area, demo100ar_wilderness. We only want the cutscene to play the first time we enter this area so we've wrapped the function call in a plot flag check;

        case EVENT_TYPE_AREALOAD_SPECIAL:
        {
            if (!WR_GetPlotFlag(PLT_DEMO000PL_MAIN, DEMO_INTRO_COMPLETE))
            {
                CS_LoadCutscene(R"demo100ct_intro.cut");
                WR_SetPlotFlag(PLT_DEMO000PL_MAIN, DEMO_INTRO_COMPLETE,TRUE); //sets the plot flag to ensure we don't repeat the intro cutscene.
            }
            break;
        }

The main plot file

For this demo module, all of the major events relating to the development of the plot are kept track of in the demo000pl_main plot file. This also allows for a centralized place to put scripting that deals with these events.

The plot outline of the demo module is:

  1. Player encounters the innkeeper outside his inn and speaks with him, receiving the quest to retrieve the innkeeper's sword.
  2. Player enters the inn. As he approaches the lever that will open the door to the room the sword is in, the bandit that's taken over the inn initiates conversation with the player. This leads to combat.
  3. Player pulls the lever, blowing up the door to the room the sword is in
  4. Player retrieves the sword
  5. Player returns the sword to the innkeeper, who offers to join the party. A new area also opens up on the world map at this point.

We'll cover each of these events in a separate section of this documentation.

You may also note that many of the constants used in the code have been separated out into a "demo_consts_h" script file, even if they are only currently used in one particular place. This is not strictly necessary, and in fact the toolset doesn't recognize the "_h" suffix as having any special significance; whenever you save an _h script the toolset will try to compile it and complain about the lack of a main() or StartingConditional() function (you can ignore this complaint). However, it makes typos much easier to find and debug, and should you need to change a constant later on this approach ensures that you can always catch every instance of it.

Giving the player an item

The first thing that happens if the player talks to the barkeep and accepts the quest is that the barkeep will give him a key. The key is a "plot item", meaning it will be shown in a separate section of the player's inventory and can't be given away or destroyed by the player, but the mechanism used here can give the player non-plot items as well.

The function UT_AddItemToInventory takes a resource variable as its parameter. It causes a new instance of the item to be created and added. If you call it multiple times with the same resource, earlier copies won't be affected - multiple copies of the item will be created. You'll see one way of removing an object from the player's inventory at the end of the quest when the innkeeper takes back his sword.

Using a trigger to initiate conversation

We want the bandit and his loyal patrons to attack the player before the lever can be pulled. A trigger has been placed in such a manner that the player will have to pass through it before reaching the lever; the trick is now to use that trigger to make the things we want to have happen, happen.

When a trigger is entered by any entity the trigger's event script is sent an EVENT_TYPE_ENTER event. Note that this is sent every time the trigger is entered, by any entity, so the first thing we'll need to do in the event script is a test to ensure that the triggering entity is the player or one of his party members.

The IsPartyMember function tests to see whether an object is player-controllable, which is true for the player's character and for any creature currently in the player's party. We call it on the event's creator to check if this is the right person; if it's not the event script does nothing.

If we wanted the bandit and patrons to simply charge and attack the player, we'd have the trigger call UT_TeamGoesHostile(BANDIT_TEAM); directly (the bandit and patrons' creature templates are all set to the same team so that we don't need to set each one hostile individually). In this case an unexplained attack would be strange, so instead a conversation has been set up that has the bandit and patrons explain themselves before attacking. So we've set the trigger script to call UT_Talk instead.

Once this has been called, we never want the trigger to fire again for anyone. It is important to note that setting a trigger to "inactive" will not work; an inactive trigger still fires EVENT_TYPE_ENTER whenever an entity enters it. There are several approaches that can be used here:

  • Set the event script to check the trigger's active status and not perform any action if it's inactive
  • Use a plot flag to make the code only fire once
  • Call Safe_Destroy_Object(OBJECT_SELF, 0); to destroy the trigger entirely

Since there will be no circumstances where we'll need the trigger to become active again in the future, destroying the trigger is the simplest and most foolproof way of accomplishing this.

Using a placeable to call a script

The lever placeable has a "use" option that allows the player to flip it from one state to the other. When this happens an EVENT_TYPE_USE event is sent to the placeable's event script. We've added a custom event script to the lever to intercept this event and do something special; demo200pl_security_lever.nss.

Rather than handling all the details of what should happen when the lever is pulled right in this script, though, we've instead put them into the main plot file's script. The custom placeable script serves only to set the appropriate main plot file flag.

Troubleshooting

Failure to exit combat mode after defeating the bandits in the inn

Try clearing out the folder My Documents\BioWare\Dragon Age\packages\core\override. As of version 1.0.982.0 of the Dragon Age Toolset, the export process deposits certain files to that override folder, which is currently causing conflicts within the game. One immediate indication of this may be that the player character is initially in their undergarments during character generation.