MakingCampaignInWML3

From The Battle for Wesnoth Wiki

Part 3: Improving the first scenario

In the first part of this tutorial we've made the very basic scenario to be able to run the campaign. In the second part we added a little difficulty-based change in enemy leader and put a #textdomain directive at the beginning - both didn't really change much, and our first scenario is still very barebones, so it's high time we do something with it.

There's no TL;DR this time, but you can skip the parts you're not interested in - they are all technically optional and independent.

Time schedule

Our scenario currently has no ongoing day/night schedule and the corresponding part of the game window is black. You can add [time] tags to the [scenario], but almost always you're going to use predefined macros instead, so we're not going to cover that - see TimeWML if you want. All time macros are in one file, but most important ones are listed here. That being said, you're probably going to use {DEFAULT_SCHEDULE} for normal scenarios and {UNDERGROUND} for those in caves, rarely {INDOORS} for those in buildings. Put one of those inside [scenario] and you're done.

Sometimes we want our scenario to start at a specific time of day. You could make your own schedule, switching the order, but it's counterproductive - use current_time= instead. It takes a number corresponding to the order of schedules. For example, when using '{DEFAULT_SCHEDULE}, current_time=1 is dawn, which is default, but current_time=4 will make us start at dusk instead. In the example we're going to use current_time=5, that is, our scenario will start at first watch.

    {DEFAULT_SCHEDULE}
    current_time = 5

Rarely a part of our map is underground while the rest is above the ground. In this case determine which is dominant and put that in the [scenario] directly. For the rest, use [time_area]. In addition to [time] and current_time= like in [scenario], it also takes a list of x and y coordinates affected. Usually you're going to prepare this in scenario editor mode of map editor, not manually, even though it's possible, and then paste it into your scenario.

As a side note, you can point your map_file= key to an editor-generated scenario file instead of just a map, and it'll automatically import everything, including [time_area] and [side] tags. However, it's easy to make a mistake and import too much, so be careful if you try that.

In this scenario we're not going to use any [time_area] tags, but here's an example how it might look like:

    [time_area]
        x = 1-10
        y = 1-5
        {UNDERGROUND}
    [/time_area]

It'll make a 10x5 area in the right corner to always have underground time, instead of usual. You can also add and remove those during the scenario, and make it automatically affect only cave tiles, but those are advanced features.

Time limit and experience modifier

Currently, unlike most campaign scenarios, ours has no time limit. Let's add it:

    turns = 25

That being said, usually the turn limit is progressive with difficulty. We could write it like that:

#ifdef EASY
    turns = 25
#endif
#ifdef NORMAL
    turns = 23
#endif
#ifdef HARD
    turns = 20
#endif

But there's no point. There's a macro for that:

    {TURNS 25 23 20}

And that's it. Just put the macro in your [scenario] tag and maybe adjust the values for each difficulty. Just in case, remember that it's going to make the player lose when turns run out, so it's usually lower as the difficulty progresses.

If you have four difficulties using normal defines, use {TURNS4} instead, e.g. {TURNS4 25 23 20 18}

The other thing you might want to set is experience_modifier= key. It'll cause your unit to advance faster or slower, and might be a part of balance or difficulty. Most campaigns have it at default 100%. If you want to make it, let's say, 200%, add:

    experience_modifier = 200

If you want to make it difficulty-dependent, you can use {QUANTITY} macro (or {QUANTITY4} to cover NIGHTMARE difficulty):

    {QUANTITY experience_modifier 60 100 140}

However, if for some reason the experience modifier changes mid-campaign, either due to scenario change or difficulty change, weird things might happen, so refrain from both. Keep in mind that players can change difficulty at the beginning of a new scenario, unless set otherwise in [campaign] tag (see previous tutorial).

Intro

While playing wesnoth campaigns you probably noticed, that usually before a scenario there are story panels, which introduce the story or narrate the events happening off-screen. Making those is relatively easy. First, put a [story] tag inside your [scenario]. Everything related to the intro will go into [story]. You can have several [story] tags and they will play one by one in order of appearance, but there's usually no point in doing that.

The [story] itself has few configuration options, but you're unlikely to use them normally. What you will add though, are [part] tags. In its simplest form they look like that:

        [part]
            story = _"The Example village lived in peace, protected by its mighty garrison of volunteers. Until one night the ugly necromancer came with its fearsome minions..."
        [/part]

It'll display the text on the bottom of a black screen. Not cool. Let's add the background. Default backgrounds are in the story folder inside images, but of course you can add your own and they will be localized using the content of your [binary_path]. It's pretty much the same as the background= key in [campaign].

            background = "story/landscape-hills-01.jpg"

Among other frequently used keys are music= which will change the music in background after this part is reached, and show_title=, which when set to yes will display the scenario name on the top of the screen. We'll only use the latter in the example, as it's commonly used with last [part] in your [story].

Of course, every [story] can have multiple [part]s inside, but note several things:

  • Many scenarios don't have any [story] at all. The progression is made by dialogues instead
  • You don't have to force yourself to put many [part]s when one is enough.
  • You don't have to tell players every single detail of your story setting, and even if you do, they might just skip it anyway.

Knowing that, have fun in adding as many [part]s as you deem necessary. The example intro we're using looks like that:

    [story]
        [part]
            story = _"The Example village lived in peace, protected by its mighty garrison of volunteers. Until one night the ugly necromancer came with its fearsome minions..."
            background = "story/landscape-hills-01.jpg"
            show_title = yes
        [/part]
    [/story]

It's also possible to add a map showing our path and battle locations, but we'll do that later.

Flavours

Music

There is a defined music playlist that will play by default, but you can override it with your own for added immersion. For that, there's a [music] tag, but you're almost never going to use it directly. Instead, use several macros:

One of {SCENARIO_MUSIC} or {INTRO_AND_SCENARIO_MUSIC}

there could be an optional {LET_INTRO_MUSIC_FINISH} here but it currently (1.16.0) doesn't work as expected

and subsequently any number of {EXTRA_SCENARIO_MUSIC}

You can also replace the music mid-scenario but it's rarely used and we're not going to cover that. All related macros are in sound-utils.cfg file, and most are documented here.

The difference between {SCENARIO_MUSIC} and {INTRO_AND_SCENARIO_MUSIC} is that the latter takes an additional first parameter, a music that will play instead of the default one (Revelation by Joseph G. Toscano) during the intro. Note that you can also use the [part]'s own music= key, and when you're not using any of those the music will just continue from the main menu without any interruptions or changes.

You can, for example, give your scenario a serene feeling by adding something like that inside your [scenario] tag:

    {SCENARIO_MUSIC "elvish-theme.ogg"}
    {EXTRA_SCENARIO_MUSIC "travelling_minstrels.ogg"}

However, we're going to have more impactful music in our scenario, to accentuate the sudden night attack.

    {INTRO_AND_SCENARIO_MUSIC "transience.ogg" "the_city_falls.ogg"}
    {EXTRA_SCENARIO_MUSIC "siege_of_laurelmor.ogg"}
    {EXTRA_SCENARIO_MUSIC "frantic.ogg"}
    {EXTRA_SCENARIO_MUSIC "vengeful.ogg"}
    {EXTRA_SCENARIO_MUSIC "frantic-old.ogg"}

What music to pick when is a delicate topic we're not touching here. You can also put your own music, and the game will try to look for it in the music/ folder in your add-on folder (if you set the [binary_path] properly).

Labels

You can put text describing a certain tile (sometimes a certain area around it as well) using [label] tag. It will show below the unit standing there.

    [label]
        x = 3
        y = 1
        text = _"To the far lands"
    [/label]

This will show the text To the far lands below the hex 3,1.

You can do the same using macro:

    {SET_LABEL 3 1 _"To the far lands"}

You can also do that dynamically later in the game, using the same syntax. There's one more thing, slightly ahead of our current progress:

    [event]
        name = prestart
        [label]
            location_id = 2
            text = _"The ugly necromancer's camp"
        [/label]
    [/event]

This code will add the text at map-defined side 2 starting position, without having to specify the coordinates. You can also use this with other named map locations defined in the map editor. However, this will not work if [label] is directly in [scenario] and there's no predefined macro for it.

Map decorations

In addition to terrain placed in the map editor you can put some additional graphics on your map using [item] tag. It's syntax is similar to the one in [label], except that you replace text= with image= key that should point to an image in your add-on's or game's images folder. In base game images for that are either in scenery or items subfolders. Note, however, that many of those indicate something that player can interact with, and therefore shouldn't be used without reason.

    [item]
        x = 3
        y = 1
        image = "scenery/signpost.png"
    [/item]

And of course there's a macro for that. Note that arguments are in different order than with labels.

    {PLACE_IMAGE "scenery/signpost.png" 3 1}

Keep in mind that despite being named [item], they don't do anything by themselves. For giving items to units, you have to use [object] inside [event], but that's for later.

Configuring player side

In the first part we've set up two [side] tags with bare minimum needed for them to work. Now we're going to expand both of those to match the standards.

First, we're going to move our leader to a separate subtag. This is a new feature that greatly improves readability of the [side] tag. Replace our type= key with:

        [leader]
            type = Longbowman
        [/leader]

Next, we're going to configure the side itself. Our current starting gold is 100 and it's independent from difficulty. To set starting gold we use:

        gold = 200

However, since gold is one of main balance factors, it's better to make it difficulty-dependent. Like with turn limit, there's a macro for that:

        {GOLD 250 200 150}

And of course there's a {GOLD4} version that includes NIGHTMARE. If you skipped the part about time limit, you can go back for details there.

Analogical situation is with income - you have 2 by default and you can set a different value using income= key. However, there are things you need to remember: first, your actual income will be 2 more than what you write. That is, if you write income=2, you'll have 4 actual income per turn. Second, there are macros for income.

        {INCOME 6 4 3}

This will make your base income to be 8 on easy and 5 on hard. Of course there exists an {INCOME4} version. If you want to truly have no base income, use this instead:

        {NO_INCOME}

Keep in mind that it doesn't affect villages. You can set those using village_gold= and village_support= keys, and they work normally, without adding any arbitrary values.

Now we're going to focus on another two keys: fog= and shroud=. You should have learnt about those over the course of several campaigns. Those can be turned on or off separately for all sides, and if they are on, there are several additional keys that can be used describing fog and shroud behaviour with allied sides. They are further documented in SideWML. All you need to know for now is that we're going to turn on fog for our side and leave shroud as its default, which is off:

        fog = yes

Now we're going to set teams. Obviously with only two sides no real alliances are going to happen, but there exists a status table which makes use of this:

        team_name = Humans
        user_team_name = _"Humans"

First one describes actual alliances, and is a list. So if we want to have a side allied with ours, it would need to have Humans in its team name as well. If someone should be allied with both Humans and Undead, we would write:

        team_name = Humans, Undead

The second one is translatable and shows in the status table. It doesn't need to match the team name, though it often does if it only contains one value (no commas). Try to keep it short.

And that's it for now. There are many other keys you can use, but in fact they are rarely used. See the docs if you want to check them for some reason.

Player's side units

We're done with configuring the side itself, but we still haven't really configured the leader. We moved him to the separate subtag, but it didn't change anything - it'll still have a randomly generated changeable name, auto-generated id, and will appear on side 1 starting position. We're going to let him appear there because that's what we usually want, but let's name him, he's our hero after all:

            id = Lezarek
            name = _"Lezarek"

We also set his id, because it simplifies referencing him later, and we usually reference our heroes a lot. Note that id is often, but not necessarily the same, and id must not be translatable, while name should be, because some languages use different alphabets.

If we want to prevent players from changing his name, we can add the following:

            unrenamable = yes

Heroes usually come in pairs or trios, for a simple reason - it simplifies dialogue writing a lot. It would be a pity if our single hero talked to air, or to enemies only, and using a narrator is the last resort. Wars are a group sport, and most commanders have an adiutant. Therefore, we're going to add another unit inside our [side] tag:

        [unit]
            id = Verbott
            name = _"Verbott"
            unrenamable = yes
            type = White Mage
            gender = female
            placement = leader
            [modifications]
                {TRAIT_LOYAL_HERO}
            [/modifications]
        [/unit]

There's a lot going on here, let's skip the first four keys we already know and focus on new ones. We made our White Mage female - she's a hero, so it's recommended to make their gender non-random. If we left it out, it would be male by default. To make gender random we have to explicitly use a different key, random_gender=yes.

Then, placement = leader tells the game to put her near our leader. We could provide a named location or x and y instead or do some other tricky stuff, but if we do nothing she will silently appear on our recall list, which is obviously not recommended under normal circumstances.

Then, we placed a macro {TRAIT_LOYAL_HERO} inside [modifications]. The [modifications] tag is used to place certain things, usually traits, but also custom advancements and objects. {TRAIT_LOYAL_HERO} is a variant of the loyal trait, with different visuals - it gives a silver crown instead of a bronze ring and changes unit ellipse. Those are markings of a story-important hero, but will not cause defeat when they die by themselves - we'll fix that later.

Note that units normally have 2 trait slots, and we just took one. The other one will be randomly filled, but that shouldn't be a case for our hero - let's also give her the intelligent trait using the {TRAIT_INTELLIGENT} macro by placing it inside [modifications] as well.

And that's it. We could obviously add more, but it's usually better to let the player recruit them. Note that we can add units dynamically later but we have to keep some things in mind:

  • There's no [leader] tag outside [side], you must use [unit] with canrecruit=yes and specify placement;
  • Outside [side], the [unit] tag must have a specified side= key;
  • There are macros that might help you with those.

Configuring enemy side

The last thing in this part will be configuring the enemy. First let's fill the same keys as with player side, but with different values:

    [side]
        side = 2
        controller = ai
        recruit = Skeleton Archer, Skeleton

        {GOLD 100 150 200}
        {INCOME 3 6 9}

        team_name = Undead
        user_team_name = _"Undead"

        [leader]
            id = MalShack
            name = _"Mal-Shack"
#ifdef EASY
            type = Dark Sorcerer
#else
            type = Necromancer
#endif
        [/leader]
    [/side]

Note that we didn't set fog for this side because usually there's no point in doing that. However, we could do that if we needed it for some reason - allies with shared vision, sighted events etc. Also, gold and income are growing instead of shrinking with difficulty.

We also didn't set unrenamable=yes because ai won't ever rename its units. We don't need to think about players using console commands because that's not a normal game state.

Another thing to note is that you don't necessarily have to avoid most special characters in ids, but it's better if you do, or at least wrap them in double quotes. There are working examples of id with minus sign though. In many cases we can avoid setting the enemy leader's id, but it's preferable to set it if we're planning to have them say something.

We're not giving the enemy any additional units this time (well, we could but we don't have to), so we're done.

Troubleshooting

Mistakes that are usually done at this stage include:

Typos

If something doesn't work, you might have not typed the key or value properly

invalid, misplaced or not closed tags - this tutorial should make it clear where to put everything, but refer to the full code below just in case. Once more a reminder, that order of tags and macro arguments usually matters, while order of keys usually doesn't. However, different tags placed in [scenario] should usually work the same regardless of which order they are placed in, that is, [item] and [side] can appear in any order, while [side] and [side] must appear in order of their side= keys.

Invalid macros, wrong argument order

Macros are very inflexible and you have to do things exactly as defined, so pay attention to examples

Images don't show

Check filename, path, extension, pay attention to underscores/minuses in names as they sometimes differ, this tutorial assumes you set your [binary_path] properly

Problems with unit types

Consult units.wesnoth.org or source to check if your ids are valid

Full code:

#textdomain wesnoth-WML_Tutorial
[scenario]
    id = 01_Initial_Scenario
    name = _"Initial Scenario"
    map_file = "01_Initial_Scenario.map"

    {DEFAULT_SCHEDULE}
    current_time = 5

    {TURNS 25 23 20}

    [story]
        [part]
            story = _"The Example village lived in peace, protected by its mighty garrison of volunteers. Until one night the ugly necromancer came with its fearsome minions..."
            background = "story/landscape-hills-01.jpg"
            show_title = yes
        [/part]
    [/story]
    {INTRO_AND_SCENARIO_MUSIC "transience.ogg" "the_city_falls.ogg"}
    {EXTRA_SCENARIO_MUSIC "siege_of_laurelmor.ogg"}
    {EXTRA_SCENARIO_MUSIC "frantic.ogg"}
    {EXTRA_SCENARIO_MUSIC "vengeful.ogg"}
    {EXTRA_SCENARIO_MUSIC "frantic-old.ogg"}

    {SET_LABEL 3 1 _"To the far lands"}
    {PLACE_IMAGE "scenery/signpost.png" 3 1}

    [event]
        name = prestart
        [label]
            location_id = 2
            text = _"The ugly necromancer's camp"
        [/label]
    [/event]


    [side]
        side = 1
        controller = human
        recruit = Bowman, Spearman

        {GOLD 250 200 150}
        {INCOME 5 4 3}

        fog = yes

        team_name = Humans
        user_team_name = _"Humans"

        [leader]
            id = Lezarek
            name = _"Lezarek"
            unrenamable = yes
            type = Longbowman
        [/leader]
        [unit]
            id = Verbott
            name = _"Verbott"
            unrenamable = yes
            type = White Mage
            gender = female
            placement = leader
            [modifications]
                {TRAIT_LOYAL_HERO}
                {TRAIT_INTELLIGENT}
            [/modifications]
        [/unit]
    [/side]

    [side]
        side = 2
        controller = ai
        recruit = Skeleton Archer, Skeleton

        {GOLD 100 150 200}
        {INCOME 3 6 9}

        team_name = Undead
        user_team_name = _"Undead"

        [leader]
            id = MalShack
            name = _"Mal-Shack"
#ifdef EASY
            type = Dark Sorcerer
#else
            type = Necromancer
#endif
        [/leader]
    [/side]

[/scenario]

Part 4: Custom macros and events

This page was last edited on 25 November 2021, at 16:05.