Setting up Campaigns

From The DarkMod Wiki
Jump to navigationJump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

This article is an attempt to describe the various options mappers need to consider when setting up campaigns or multi-mission packages (I'll refer to campaigns for both of these for the sake of simplicity). Campaign support has been added in TDM 1.06.

Map Files

Regular, single-map TDM missions are packed in PK4 files, campaigns are no different. If your campaign consists of three missions ("red.map", "blue.map" and "green.map"), all three of them still go into the same maps folder in your PK4 file.

The actual order in which the missions are to be played is defined through the tdm_mapsequence.txt file, which should be added to the PK4 (just next to the readme.txt). Following the example of the "red", "blue" and "green" maps above the map sequence file would look like this:

Mission 1: red
Mission 2: blue
Mission 3: green

Looking at that, the syntax of the file is pretty much self-explanatory. The first mission has the number one, the map file is mentioned after the colon and the following space. You don't need to specify the ".map" extension of the map file that is handled automatically. See the main tdm_mapsequence.txt article for further reference, for this article it's enough to have the above lines I suppose.

Briefings

Once the map sequence is defined one needs to take care of the briefing. If you like to keep it simple, just refer to the Briefing article in the Text-only Briefing section. Each map needs its own xdata block with the briefing and you'll be done.

For advanced setups like the "Timed Flowing Briefing" mentioned in the Briefing article there are a few modifications to be made to your GUI code. As the mainmenu_briefing.gui file is the same for the whole campaign your GUI needs to know which map the player is about to play next. For this purpose a new GUI variable gui::CurrentMission has been introduced which can be used in if-else GUI code blocks in the BriefingStateInit windowDef. For single-missions, mappers just called the GUI animation block like this:

windowDef BriefingStateInit
{
	noTime 1

	// Init methods have onTime 10 instead of onTime 0.
	onTime 10
	{
		// ... stuff

		set "BriefingAnimation::visible" "1";
		resetTime "BriefingAnimation" 0;

		// ... stuff
	}
}

For multi-mission setups an if-else block should be added to switch to the correct briefing animation for the current map:

windowDef BriefingStateInit
{
	noTime 1

	// Init methods have onTime 10 instead of onTime 0.
	onTime 10
	{
		// ... stuff

		if ("gui::CurrentMission" == 1)
		{
			set "BriefingMission1Animation::visible" "1";
			resetTime "BriefingMission1Animation" 0;
		}
		else if ("gui::CurrentMission" == 2)
		{
			set "BriefingMission2Animation::visible" "1";
			resetTime "BriefingMission2Animation" 0;
		}

		// ... stuff
	}
}

Of course you'll need to define a BriefingMissionXAnimation block for each mission. You can use the instructions in the Briefing article to learn how to do that, just keep in mind to change the windowDef's name such that you don't end up with duplicate names for your windowDefs (this will lead to warnings in the console at game startup at best).

Starting the correct mission briefing animation is only half the rent, you'll need to stop the correct animation as well when the player proceeds to the objectives screen:

windowDef BriefingStateEnd
{
	noTime 1

	onTime 0
	{
		// ... stuff

		if ("gui::CurrentMission" == 1)
		{
			set "BriefingMission1Animation::visible" "0";
			set "BriefingMission1Animation::notime" "1";
		}
		else if ("gui::CurrentMission" == 2)
		{
			set "BriefingMission2Animation::visible" "0";
			set "BriefingMission2Animation::notime" "1";
		}

		// ... stuff
	}
}

If you don't do that the player might see remnants of the previous mission's briefing when proceeding to the next map briefing.

The same holds analogously for the BriefingSkip windowDef which is used to skip the briefing on mouse clicks:

windowDef BriefingSkip
{
	notime 1

	onTime 0
	{
		// ... stuff

		// Stop the animation
		if ("gui::CurrentMission" == 1)
		{
			resetTime "BriefingMission1Animation" 0;
			set "BriefingMission1Animation::notime" "1";
		}
		else if ("gui::CurrentMission" == 2)
		{
			set "BriefingMission2Animation::visible" "1";
			resetTime "BriefingMission2Animation" 0;
		}
	}
}

Briefing Videos

It's possible to define briefing/intro videos for each mission in a campaign. To enable videos mappers need to include a mainmenu_custom_defs.gui file in their PK4 and define a few variables in there. You can look at the default file in the TDM PK4s to get more info and read some comments. I'll include the most important settings briefly here:

Enable SDK-controlled briefing videos by adding this line to your mainmenu_custom_defs.gui file (or uncomment the line if it is present already but commented out):

#define BRIEFING_VIDEO_CONTROLLED_BY_SDK 1

Next you'll need to specify which video to be played for each mission. These are bit more complex:

#define MM_BRIEFING_1_VIDEO_MATERIALS "video/tdm_briefing_video;guis/video/credits/typing_sequence01;"
#define MM_BRIEFING_1_VIDEO_LENGTHS "4000;5000"
#define MM_BRIEFING_1_VIDEO_SOUND_CMD "music video/tdm_briefing_video_sound;"

Easy to see, these three definitions refer to the first mission in the campaign, the prefix is "MM_BRIEFING_1".

The MM_BRIEFING_1_VIDEO_MATERIALS line specifies the various ROQ materials that should be played (in this order). ROQ files have a maximum length of 60 seconds, so TDM added support to a sequence of ROQs to allow for longer videos. The materials should be separated by semicolons. Be sure to define the playback duration of each clip in the VIDEO_LENGTHS line.

The next line MM_BRIEFING_1_VIDEO_LENGTHS corresponds to the MATERIALS line above and contains the playback duration of each ROQ clip. The time is specified in milliseconds, multiple times are separated by semicolons.

The last line in this triple is defining the sound to be played along the video: MM_BRIEFING_1_VIDEO_SOUND_CMD. Sounds don't have a length limit, so there is no need to define multiple sounds like with the ROQ clips. Be sure to include the "music" command in the string and to refer to a valid sound shader.

The original TDM mainmenu_custom_defs.gui file contains a lot of comments about these defs, please take a look there to learn more.

Side note: the SDK-controlled video is also available in ordinary (single) mission packs. You don't need to build a full-blown campaign (with tdm_mapsequence.txt, etc.) to use this option, it's enough to make the correct #DEFINE statements above.

De-Briefing Videos

With TDM 1.06 and upwards it's possible to have a video played back immediately after "Mission Complete", so-called de-briefings.

The mechanism to set these up is very similar to the above "SDK-controlled" briefing videos, all you need is to add the correct #DEFINE lines in your mainmenu_custom_defs.gui file:

#define MM_DEBRIEFING_1_VIDEO_MATERIALS "guis/video/credits/typing_sequence01;"
#define MM_DEBRIEFING_1_VIDEO_LENGTHS "2000;"
#define MM_DEBRIEFING_1_VIDEO_SOUND_CMD "music tdm_mainmenu_background_music;"

This example will play a credits video after "Mission Complete" for 2 seconds (= 2000 milliseconds). Please refer to the above "Briefings" section to learn about the precise meaning of these lines, they are pretty much analogous.

Side note: this option is not exclusive to campaigns, you can have a de-briefing video in single-mission setups just as well.

Conditional Objectives

You can have your mission objectives depend on the state of objectives in previous missions. It's possible to set an objective in the second mission to "COMPLETE" if the player managed to fulfill an objective in the first mission.

Objective conditions editor.png

Consider a campaign consisting of two maps: red.map and blue.map, played in this order. You have an optional objective in red.map telling the player to find 50 loot in gold. If the player manages to complete that objective the mandatory objective in mission 2 "Find enough gold for your rent" should already be marked as "Completed". Such a thing can be achieved through a set of spawnargs that have been introduced in TDM 1.06 (see the Objectives article for reference), but you can do it through the Objectives Editor just as fine.

I'll assume the use of the Conditions editor is fairly straightforward, but feel free to ask specific questions in our forums.

Scripting

There are a few script methods which are specifically designed to be used in maps. See the file tdm_events.script for more details about them.

Use the getCurrentMission() method on the sys entity to retrieve the current mission number. Note that the mission numbers are 0-based in the code, the function will return 0 for the first mission in a campaign. The following snippet will print the number to the console:

sys.println("Current Mission: " + sys.getCurrentMissionNum());

Furthermore there are a couple of script events that can be used to store data in between missions. This data is stored in the so-called "persistent level info", which is designed to store key/value pairs.

The script events are very similar to the routines used to query entity spawnargs (which is not surprising, as the persistent level info is using the same C++ class idDict to store the data). Since the persistent level info is not associated to any entity, these methods need to be called on the sys object. See doom_events.script file for further reference.

// Sets a key/value pair that persists between maps, overwriting existing keys with the same name
scriptEvent	setPersistantArg( string key, string value );
// Returns the string for the given persistent arg
scriptEvent	string	getPersistantString( string key );

// Returns the floating point value for the given persistent arg
scriptEvent	float	getPersistantFloat( string key );

// Returns the vector for the given persistent arg
scriptEvent	vector	getPersistantVector( string key );

As example, you can store a number value like this (all values are stored as string)

sys.setPersistantArg("dummyvar", "20");

Later on you receive the number value like this:

float testvar = sys.getPersistantFloat("dummyvar");

If you ever need to clear out the entire level info there's a separate routine for that:

// clears data that persists between maps
sys.clearPersistantArgs();

Inventory Items

The player's inventory contains all the keys, tools, lockpicks, lantern as well as all the weapons. The mapper can define which of these should be kept when proceeding to the next mission. Items that are allowed to be kept are called "persistent", and the corresponding spawnarg controlling that property is called "inv_persistent".

Tools & Items

By default regular inventory items are not marked as persistent, i.e. the inv_persistent key is set to "0". Once the mapper flips that spawnarg to "1" the item will be kept throughout the following missions. The item will be kept until the end of the whole campaign, unless:

  • the player drops the item
  • it is removed from the inventory by script or other events
  • a rule on the atdm:campaign_info entity is forcing the item to be removed after "Mission Complete" (see below).

Even though TDM 2.02+ will resolve naming conflicts across maps, map authors should try to make sure that persistent items have unique names. For example, if the first map has a persistent item named "My Water Bottle", none of the subsequent maps should have an item named "My Water Bottle". If you must have a second water bottle, name it "My Second Water Bottle", or something along those lines.

If you have a persistent stackable item in map N, and you don't consume it in that map, and you have another instance of the same stackable item in map N+1 (whether it's marked 'persistent' or not), when you pick it up, it will be added to the stack count of map N's item and become persistent.

Weapons & Arrows

All weapon inventory items are persistent by default including their ammo, so without further action the player will keep all his weapons and all the arrows when proceeding to the next mission. However it's possible to define ammo limits on the atdm:campaign_info entity, which is described below.

Important: it's not advisable to mess with the inv_persistent spawnarg on weapon items - it's recommended to leave that spawnarg at the default value and control the carry-over by the rules on the campaign info entity.

Inventory Item and Weapon Limits

An atdm:campaign_info entity can be placed in a map to control which items make it to the next mission (and their amount) - and which should be dropped. The rules defined on the atdm:campaign_info entity will be enforced after the "Mission Complete" event, when the map is still active, i.e. it's "filtering" outgoing items. Each map can have its own campaign info entity to define its own set of rules.

Please refer to the main atdm:campaign_info article to get precise info on how to set up these rules.

Loot Limits

Special handling is possible for the amount of loot that is carried over and usable in the shop of the next mission. These so-called "loot carry-over" rules are defined on the shop entity of the respective map, e.g. the shop entity in Mission B will define the loot carry-over roles applicable to the loot collected in Mission A.

Refer to the Shop article for further reference.