MakingCampaignInWML2
Expanding the campaign definition (and macros)
In the previous part of this tutorial we made a minimal working example that could be run and played, but it's far from the usual content of a campaign. If you have ADHD or for some other reason don't want to read all the descriptions, jump to the TL;DR.
Contents
Textdomain
Let's start by introducing the very common content of add-on's _main.cfg file, that is the textdomain. It allows your campaign to be translated and it's better to do that at the very start than try to add it later, especially since it's mostly unobtrusive and doesn't take much time. First, let's add a [textdomain] tag, it's typically located before [campaign] and looks like:
[textdomain]
name = wesnoth-WML_Tutorial
path = data/add-ons/WML_Tutorial/translations
[/textdomain]
The name= key is most important. Your textdomain name should start with wesnoth-, and after that it typically uses your add-on's folder name for a good reason - this name will be used as folder name again.
The path= key contains the path to your translation root relative to the userdata folder, typically it's the same as in [binary_path] but with /translations at the end.
- You don't have to add /translations at the end but you should. Although having your main folder as translation root will flatten your folder structure for translation, which can possibly make access to it faster, it'll be confusing for others, so it's discouraged.
The reference advises to create translations folder now, but it doesn't need to have content for now and actually everything will work even if you don't.
Note that mainline campaigns have different content in this tag because their translations are handled by game core, so don't copy their way.
Now, we're going to add a #textdomain directive. It's typically the first line of any .cfg file containing translatable strings. For our example it'll be:
#textdomain wesnoth-WML_Tutorial
#texdomain is a fixed part, and after a space there should be your textdomain name, exactly the same as in name= key in [textdomain] tag. You should place the exact same line in your scenario file, but not in your map file. If you have a .cfg file that you're sure that it won't have any translatable strings, you can use #wmllint: no translatables instead, but if you're not sure, just add the full #textdomain directive or leave nothing. Don't put any of these in files other than those with .cfg extension.
Visuals
Your campaign entry in the campaigns menu looks very barebones for now. Let's add a little variety. First, an icon. It will show on campaigns list and is considered pretty mandatory. It's customary to use an image that's usually displayed on the map, like an item or a unit. In this example we're going to use a book from mainline as an icon:
icon = "items/book3.png"
Place this inside your [campaign] tag. If you're planning to use custom images, see the note about paths below. Note that it's customary to wrap file paths and certain other strings in double quotes, but usually they are not necessary. Don't add the _ (underscore) before though, because this is NOT translatable.
Other images also go inside [campaign] tag but are much more optional, even mainline campaigns not always use them:
image = "portraits/humans/longbowman.png"
This will show below the description (we'll add one soon) and it's usually a unit/character portrait. It's optional, but quite common. We're using the portrait of Wesnoth's longbowman as an example.
background = "story/grim-altar.jpg"
This is a new feature, introduced in 1.15 versions, so it's not commonly used. It replaces the default background of the campaign menu when our campaign is selected. Typically uses story/cutscene background images. Note that Wesnoth backgrounds in the story folder have atypical .jpg extension, but it's not necessarily the case for other background images.
Note about custom images
- Usually you could access an image, e.g. "snow-golem.png" in your add-on's images folder by just writing "snow-golem.png". But that's because you use [binary_path] to access it. However, our [binary_path] is inside #ifdef, which only loads AFTER we start the campaign, so the game doesn't know about it when we're in campaign selection menu.
- To use custom images in your [campaign] tag you have to use fully qualified names, relative to userdata folder, that is, the same as you used in [binary_path] and [textdomain]. For the afforementioned snow golem image you'd have to write data/add-ons/WML_Tutorial/images/snow-golem.png.
- There's a trick that can simplify it, but you'll learn it later.
Now, let's move to the description. In mainline campaigns it's usually splitted into two parts, the actual description and difficulty/length addition, for translation purposes. Don't forget to mark each part as translatable whether you split or not.
description = _"Basic, but slowly expanding example for tutorial.
Great, emotional story not yet guaranteed.
" + _"(Novice? level, 1 scenario.)"
Note that you mustn't tabulate the description inside quotes in multiline description, as it'll be added to the string and shown in the game. You can use fancy formatting, as described here.
- Centering the description
- There exists a key description_alignment= if you want to center your description like the one in The Sceptre of Fire. It doesn't add the italics though.
description_alignment = center
- It won't appear in code examples later.
There's one more purely visual thing, but it only shows in save filenames - the abbrev. It's recommended to set that. First derive an abbreviation of your campaign from its name. For example, The Rise of Wesnoth is abbreviated TRoW and Under the Burning Suns is UtBS. For this example add-on, we'll use WMLT. Note that abbreviations vary between languages, so this is translatable.
abbrev = _"WMLT"
- You should, but don't have to avoid clashes in the abbrev= key.
- It's preferable, but not necessary that it doesn't clash with other add-ons. However, as it only affects save filenames, it's unlikely to cause problems for the game itself - it could at most overwrite saves if for some reason scenario names are the same. It might affect the community though.
- Example: the abbreviation conflict between Era of Magic and Era of Myths, causing the former to use the abbreviation EoMa. But you don't have to bother about it at this point that much.
Sorting
There's a default order of campaigns appearing in the menu and ours will probably be last for now. Set your campaign's rank to something bigger than 300, the lower it is, the bigger priority on default sorting it's going to have, if not set it will appear somewhere near the end. Check in CampaignWML for a detailed explanation.
rank = 970
Note that you can also sort by name (alphabetically) and by time. For the former, set start_year= and end_year= keys, for example:
start_year = "10 BW"
end_year = "10 YW"
In this campaign we're going to use a simpler form that replaces both of them to set our campaign to happen a year after "The Story of Two Brothers":
year = "364 YW"
As it was said, it only affects sorting by timeline and nothing else, so if your campaign for some reason isn't placed in the Wesnoth universe or the date is not determined, just omit this key.
Difficulty
Difficulty in campaigns is currently added using [difficulty] tags. It was different before, so not all add-ons follow this convention. The tag looks as follows:
[difficulty]
define = EASY
image = "units/human-loyalists/bowman.png"
label = _"Recruit"
description = _"Beginner"
[/difficulty]
It should be placed inside the [campaign] tag, like [side] in [scenario]. Don't forget about correct indentation.
First key, define= works similarly to the one in [campaign] tag, and is also checked with #ifdef. But instead of only loading resources when campaign starts, it will only put certain parts of code in the campaign when it's started with the right difficulty, so it doesn't have effect on the [campaign] tag itself and will be used in scenarios (or units) instead. Example:
#ifdef EASY
type = Dark Sorcerer
#else
type = Necromancer
#endif
This code if put inside the enemy's [side] tag will cause their leader to be a Dark Sorcerer if the difficulty is set to EASY, and a Necromancer otherwise. Note that it'll show in examples later.
It's strongly recommended to use a set of: EASY, NORMAL, HARD if you have three difficulties and add NIGHTMARE if you have four, because there are some improvements (namely macros) that make use of those.
Second key, image= should point to an image illustrating the difficulty. It usually follows level progression for a unit or several similar units of different levels. For example, Liberty uses a Peasant (lvl 0) for EASY, Outlaw (lvl 2) for NORMAL and Fugitive (lvl 3) for HARD. It's purely visual.
Third key, label= is a translatable string with a flavour text representing the difficulty. It's often the name of the unit on image, but not necessarily.
Fourth key, description= is also translatable, and it serves as the actual user-visible difficulty name for the ones confused by flavour texts. Note, that you might want to choose from the ones used in mainline and save time on translating and devising a clever descriptive name. Just start the game and look at what was used so far.
Having a single difficulty doesn't really change anything so we'll add two more:
[difficulty]
define = NORMAL
image = "units/human-loyalists/longbowman.png"
label = _"Veteran"
description = _"Intermediate"
[/difficulty]
[difficulty]
define = HARD
image = "units/human-loyalists/masterbowman.png"
label = _"Commander"
description = _"Hard"
[/difficulty]
We'll now use a little trick and append ~RC(magenta>blue) to the images, so their paths will look like:
image = "units/human-loyalists/longbowman.png~RC(magenta>blue)"
It'll cause their images to be coloured like units of default side 2. Again, it's purely visual. See documentation for more.
Note that players can change difficulty mid-campaign by default. To prevent that, you can add allow_difficulty_change = no to your [campaign] tag.
MACROS
Now we're going to use another trick. We'll replace those tags with macros. Macro is literally telling the game to put a certain code in a certain place when loading the files. So in our code we're going to replace this:
[difficulty]
define = EASY
image = "units/human-loyalists/bowman.png"
label = _"Recruit"
description = _"Beginner"
[/difficulty]
With this:
{CAMPAIGN_DIFFICULTY EASY "units/human-loyalists/bowman.png~RC(magenta>blue)" _"Recruit" _"Beginner"}
And the game will swap it back for us. Note, that the order of values in macros (they are called arguments in this context), unlike keys, does matter and has to be strictly followed. You also have to watch out for spaces, as anything that's not within () parentheses or "" double quotes and contains a space will count as two separate arguments. If we used the Wesnoth core's syntax for translatables the third argument would have to look like that: ( _ "Recruit")
Macros are great for saving time on typing and space on the screen when developing. You can also make your own macros but we'll do that later. There's a big collection of macros already made in Wesnoth and most are documented in the reference, along with the one we just used.
We'll also replace the other two with macros to save space. Usually you'd use a macro from the beginning and never write the tag in full. Note that we treat macros like keys for the purpose of indentation.
{CAMPAIGN_DIFFICULTY NORMAL "units/human-loyalists/longbowman.png~RC(magenta>blue)" _"Veteran" _"Intermediate"}
{CAMPAIGN_DIFFICULTY HARD "units/human-loyalists/masterbowman.png~RC(magenta>blue)" _"Commander" _"Hard"}
Credits
Those will appear when the player finishes the campaign, just before mainline credits. They look like that:
[about]
title = _"Designers"
[entry]
name = "John Doe (hisnickname)"
comment = "Served as excellent example on how an entry usually looks like"
[/entry]
[/about]
[about]
title = _"Programmers"
[entry]
name = "You"
comment = "Assuming you actually did something"
[/entry]
[entry]
name = "John Smith (jhnsmth)"
[/entry]
[/about]
The [about] tag describes a section in credits, its title= key is usually translatable and describes what the people inside did (usually role name), [entry] describes a single person, its name= key shouldn't be translatable and does what says on the tin, comment= is optional, provides an extra insight in their achievements and shouldn't be bothered to be translated.
That's it. You don't even need those at all, but you probably will add them anyway, for fame and future generations, or something.
Alternatively you can use end_credits = no instead and this will cause the game to display no credits at the end of your campaign, obviously, including mainline credits.
Consult CreditsWML for more. Or just check any other campaign's credits. It's actually the least important thing for your campaign.
TL;DR
Skip In this part we added a [textdomain] tag to _main.cfg file and #textdomain definition at the beginning.
#textdomain wesnoth-WML_Tutorial
[textdomain]
name = wesnoth-WML_Tutorial
path = data/add-ons/WML_Tutorial/translations
[/textdomain]
In the [campaign] tag: We added an icon to display next to the campaign's name, and abbrev for prefixing save filenames.
icon = "items/book3.png"
abbrev = _"WMLT"
We also added the description, along with its optional visuals - image and background.
description = _"Basic, but slowly expanding example for tutorial.
Great, emotional story not yet guaranteed.
" + _"(Novice? level, 1 scenario)"
image = "portraits/humans/longbowman.png"
background = "story/grim-altar.jpg"
We also added rank and year for sorting purposes. Both are optional and almost unnoticeable for the average user. You can replace the latter with start_year and end_year.
rank = 970
year = "364 YW"
We added three difficulties using macros in place of tags and keys:
{CAMPAIGN_DIFFICULTY EASY "units/human-loyalists/bowman.png~RC(magenta>blue)" _"Recruit" _"Beginner"}
{CAMPAIGN_DIFFICULTY NORMAL "units/human-loyalists/longbowman.png~RC(magenta>blue)" _"Veteran" _"Intermediate"}
{CAMPAIGN_DIFFICULTY HARD "units/human-loyalists/masterbowman.png~RC(magenta>blue)" _"Commander" _"Hard"}
Macros are good and here's the list.
We also added credits but if you're reading the TL;DR you're not worthy to put those yet. Go back after actually making your own campaign.
Troubleshooting
Campaign doesn't appear on the list anymore
- Probably macros are badly written or your tags are not opened/closed properly. Error message shown when running the game might say more.
Images don't show
- Assuming your [binary_path] is correct (this was done in the previous part), check your paths and. They should be relative to the game's 'images' folder, or have full path relative to userdata folder. Watch out for typos and extensions.
Code so far:
- _main.cfg
#textdomain wesnoth-WML_Tutorial
[textdomain]
name = wesnoth-WML_Tutorial
path = data/add-ons/WML_Tutorial/translations
[/textdomain]
[campaign]
id = WML_Tutorial
name = _"WML Tutorial"
icon = "items/book3.png"
abbrev = _"WMLT"
description = _"Basic, but slowly expanding example for tutorial.
Great, emotional story not yet guaranteed.
" + _"(Novice? level, 1 scenario)"
image = "portraits/humans/longbowman.png"
background = "story/grim-altar.jpg"
rank = 970
year = "364 YW"
first_scenario = 01_Initial_Scenario
define = WML_TUTORIAL
{CAMPAIGN_DIFFICULTY EASY "units/human-loyalists/bowman.png~RC(magenta>blue)" _"Recruit" _"Beginner"}
{CAMPAIGN_DIFFICULTY NORMAL "units/human-loyalists/longbowman.png~RC(magenta>blue)" _"Veteran" _"Intermediate"}
{CAMPAIGN_DIFFICULTY HARD "units/human-loyalists/masterbowman.png~RC(magenta>blue)" _"Commander" _"Hard"}
[about]
title = _"Designers"
[entry]
name = "John Doe (hisnickname)"
comment = "Served as excellent example on how an entry usually looks like"
[/entry]
[/about]
[about]
title = _"Programmers"
[entry]
name = "You"
comment = "Assuming you actually did something"
[/entry]
[entry]
name = "John Smith (jhnsmth)"
[/entry]
[/about]
[/campaign]
#ifdef WML_TUTORIAL
[binary_path]
path = data/add-ons/WML_Tutorial
[/binary_path]
{~add-ons/WML_Tutorial/scenarios}
#endif
- scenarios/01_Initial_Scenario.cfg
#textdomain wesnoth-WML_Tutorial
[scenario]
id = 01_Initial_Scenario
name = _"Initial Scenario"
map_file = "01_Initial_Scenario.map"
[side]
side = 1
controller = human
type = Longbowman
recruit = Bowman, Spearman
[/side]
[side]
side = 2
controller = ai
#ifdef EASY
type = Dark Sorcerer
#else
type = Necromancer
#endif
recruit = Skeleton Archer, Skeleton
[/side]
[/scenario]