Campaign Infrastructure

Overivew

Currently there are dozens, if not hundreds, of files that need creating / editing, which is just insane and a massive barrier to adoption. In particular, a most of the files are merely references to other files, which in turn go on to reference yet more files - it's like 1980's database design.

IMHO we should completely redesign the way cams are defined to massively de-cruft the process:

  • Eradicate /messages folder (with possible exception of *.rmsg which could live somewhere more appropriate... like in research.ini!)
  • Eradicate the myriad of other files associated with campaign missions (including .lev!)

By significantly reducing the number of files, it will be easier for people to develop, document and maintain campaigns.

Made some edits since last version of this page: Specifically, I now agree with Emdek that mission unlocking and 'home base' stuff should be handled in scripts associated with missions.

<campaign>.ini

One of these per cam, using standard .ini file format.

They live in new /campaigns folder. Each one must have a unique name, otherwise they overwrite an existing campaign.

Modlets can be used to tweak campaigns! For example, add or change a mission.

An example of the structure I propose is shown below, I'll explain what each bit means afterwards...

Proposed format for <campaign>.ini
[campaign]
id       = "cam5"
name     = _("Resurrection")
missions = 4
// requires = cam1, cam2, cam3, cam4:8
tip      = _("In a universe far, far away... <basically a paragraph describing the cam>")
 
[mission_1]
name     = _("Choose your destiny")
mission  = campaigns/cam5/cam5m1.ini

[mission_2]
name     = _("Rescue the survivors")
// requires = cam5:1
// base     = cam5:1
mission  = campaigns/cam5/cam5m2.ini
optional = 1
 
[mission_3]
name     = _("Unknown transmission")
// requires = cam5:1
// base     = cam5:1
mission  = campaigns/cam5/cam5m3.ini
 
[mission_4]
name     = _("The final battle")
// requires = cam5:3
mission  = campaigns/cam5/cam5m4.ini

Property types: "Campaign" & "Mission"

This bit isn't needed any more as scripts will handle mission unlocking and 'where the base is' in away missions.

A number of data types are already defined in .ini file format. For campaigns, two new types will be required: Campaign and Mission

The syntax for a Campaign property value is: <campaignID>[:<missionNumber>]

The syntax for a Mission property value is: <campaignID>:<missionNumber>

So, in Campaign values the mission is optional. In Mission values, the mission must be specified (obviously!).

Examples:

  • cam3 = Campaign 3
  • cam4:2 = Campaign 4, mission 2

In addition,the Campaign data type can be specified as a list. For example:

  • cam3, cam4:2 = "campaign 3" and "campaign 4, mission 2"

[campaign] section

This section is a mandatory singleton, and defines some basic data about the campaign as a whole.

PropertyTypeMandatoryNotes
idString(tick)

The id of this campaign.

Ideally this should also be the folder name where mission files are stored, eg. campaigns/<id>/<missions>.ini

nameString(tick)The name of this campaign, which will appear in the GUI menu system when user is selecting which campaign to play.
missionsNumber(tick)

The total number of missions in this campaign, both optional and mandatory. Each mission must have a mission section in the ini file (see later).

Missions are one-indexed. The first mission, which is always the mission run when the campaign starts, is mission 1.

Note: It would be nice not to need this property. I've put it here because it seems to be the way things are done in some other ini files (eg. <mapname>.ini or Challenge Games). If we could remove it, that would make it much easier for modlets to add more missions, although they'd still have the problem as to what section names to use for those missions. Eg. cam with 5 missions, two modlets want to add a mission each, both are assuming it's mission 6 = fail. Not sure how to deal with that yet.

requiresCampaign(error)

Optionally specify one or more Campaigns/Missions that must be won before this campaign can start.

Note: It might be better to just use the "requires" field of [mission_1] section to do this, so requirements of mission 1 are requirements for starting this campaign?

If just a campaign ID is specified, then all non-optional missions (see mission sections below) in that cam must be won before this cam can start.

If specific missions are specified, then those missions must be won before this cam can start.

A mixture of both types can be used as shown in the example further up this page.

Default: No prerequisites.

tipString(error)An optional paragraph of text to describe the campaign, shown as a tool tip when the user hovers over a campaign in the menus.

There will likely be more things needed in this section, either now or in future versions, for example:

  • Research tree to use for the campaign
  • Any custom droid components, etc.
  • Dependencies to load / search paths (Goth and NoQ best suited to provide this info)
  • Image used to show a mission map - eg. a satellite view of USA with blobs on it for each mission (the blobs being placed by code as the missions become available) 

[mission_N] sections

One of these is required for each mission in the campaign.

PropertyTypeMandatoryNotes
nameString(tick)The name of the mission, in human readable form.
requiresCampaign(error)

If specified, the mission will be locked until the specified campaigns or missions are successfully completed.

Note: A campaign is deemed complete when all non-optional missions in it have been won.

baseMission(error)

If specified, indicates that the mission is an "away mission" - in other words, the player has left their base and transported their troops to some other part of the map.

The value of the property specifies the mission that contains the player's base. Functions such as enumStructOffWorld() would refer to the data associated with that mission. Over time, it would be nice if there were more functions related to interaction with "off world", eg. enumDroidOffWorld(), etc.

Ideally, the scripts running in the mission should have access to a global property that confirms to them they are running in an "off world" mission. Also, "off world" seems like bad terminology - the maps are both on planet earth in current campaigns. Maybe "remote" would be a better term, eg. enumRemoteStruct() and enumRemoteDroid().

Default: Not an away mission.

missionFilename(tick)

The path and filename for <mission>.ini relating to this mission. The <mission>.ini would have virtually identical format to <mapname>.ini.

In fact, with the exception of away missions, you could take the <mission>.ini and dump it in to challenges/ folder and play that level as a challenge!

optionalBoolean(error)

Is the mission optional?

  • 0 = no, the mission is mandatory and must be won in order to be able to win the campaign
  • 1 = yes, the mission is optional, it doesn't matter if you win the mission or not

This is for side-missions, secret levels, things like that. For example, if a player achieves some set of objectives in a mission, they might gain access to a secret level where they can pick up some extra tech or oil or whatever.

<mapname>.ini = <challenge>.ini = <mission>.ini = <fastplay>.ini = <tutorial>.ini

I believe that no matter what sort of game you are playing, there should be an ini of the same format used in every case.

The template will be <mapname>.ini as it is the most advanced so far.

  • <mapname>.ini = optionally bundled with maps to set defaults for that map when played as skirmish or multiplayer
  • <challenge>.ini = placed in challenges/ folder, to set custom game settings for a skirmish game
  • <mission>.ini = placed in campaigns/<campaign>/ folder, sets game settings for a campaign mission
  • <fastplay>.ini = (not sure where it's placed), sets the game settings for the "fast play" game
  • <tutorial>.ini = (not sure where it's placed), sets the game settings for the tutorial mission

But they are all the same format.

If new features are required for the ini file, such as being able to specify which research tree to use or whatever, they get added centrally so those features are now available to all game modes.

Campaign-specific features would be put in to <campaign>.ini instead.

Ideally, in the future, it would be nice if we could have multiple tutorials. For example, the current tutorial shows some basics of creating a base and getting power. It would be nice to have some tutorials that show how to use certain weapon classes, or teach macro/micro management, etc. This makes me instantly think that there should be a camT - basically campaign of tutorial missions (the "Training" campaign). The user would have to complete at least camT:1 (basic tutorial that already exists) before they can start campaign, it would also unlock some more advanced tutorials (eg. micro/macro skills). As they progress through campaign, additional tutorials are unlocked so they can train with certain weapons if desired. Modlets could be put in to third-party campaigns so to add new tutorials to the tutorials campaign.

A similar thing could happen with fastplay missions. We could have a campaign of fastplay missions in which most missions are unlocked so you can just dive right in to play them. But there would be a few hidden missions in there that get unlocked by winning the normal missions.

audio.cfg -> audio.ini

The audio.cfg provides some metadata for audio files.

I feel it should be convered to .ini format for the following reasons:

  1. Allow 'modlets' to easily define new audio files without having to completely clone audio.cfg (ie. allow each campaign to have it's own audio, whilst still retaining access to all the audio that comes with the game)
  2. Deprecate audio.wrf– it's data can be merged in to audio.ini
  3. Allow new metadata to be added, such as audio transcriptions to _() localise

The result is that all the /messages/strings/*.txt files, audio.cfg and audio.wrf get merged in to single audio.ini.

Example file format:

Proposed format for audio.ini
[pcv331.ogg]
path     = audio/memressp/desnseq
loop     = 0     // 0 = false (default), 1 = true
volume   = 100   // 1 -> 100, default 100
range    = 1800  // how far (in pixels) can it be heard from its source on the map (default 1800)
subtitle = _("Turret selected")
keywords = female, turret, selected, computer, design, droid

Due to meaningful defaults, most audio files will not require the loop, volume or range properties in their ini section.

The keywords property is optional. As WZ evolves, I plan to implement a GUI mission editor and having keywords for audio would enable rapid location of desired audio files in the editor. In the wiki, I've already labelled every single audio file with keywords (example).

The playSound() function would be updated so that if the audio has subtitle associated with it, that text gets localised and displayed whenever the audio is played. We'd probably want this to be optional though - so a new 'subtitles' param on playSound() which defaults "subtitles" property of config.

sequences.ini (or videos.ini?)

Like audio.ini, we should have an ini for sequences that also associates the relevant subtitle files with the videos centrally, rather than having to define them at point of use.

(lightbulb) New idea: Why not just move supertitles and subtitles in to the ini file? And have one .ini per .ogg? Then no longer need to have external .txt and .txa files, and much easier to add new .oggs.

Proposed format for sequences.ini
[devastation.ogg]
path     = sequences/
// suptitle = sequenceaudio/devastation.txt
// subtitle = sequenceaudio/devestation.txa
keywords = male, nasda, nuke, etc...
 
[supertitles]
<time> = _(<on-screen text>) // would need some way to do multiline -- \n in text?
 
[subtitles]
<time> = _(<on-screen text>) // would need some way to do multiline -- \n in text?

The suptitle (subtitles that appear at top of video, ie. "supertitles"), subtitle (those that appear at bottom) and keywords are all optional.

As with audio.ini, this file would merge all things related to video files in to one place (eg. any .wrfs and also messages/*.txt clutter).

We would need a playVideo(file[, subtitles]) function, which works much the same as playSound() but for video. The 'subtitles' param defaults to whatever is defined in the "subtitles" property of config.