Difference between revisions of "WML for Complete Beginners"
Artisticdude (talk | contribs) m (→So What Exactly is WML?) |
Artisticdude (talk | contribs) (Started on the section about loops) |
||
Line 879: | Line 879: | ||
===Loops=== | ===Loops=== | ||
+ | |||
+ | I was a very messy child, much to the distress of my mother, who was the epitome of cleanliness and couldn't for the life of her understand how she could have raised such an untidy child as me. Occasionally she'd work up enough courage to venture into the land of chaos that was my room, and then she'd tell me not leave my room until it was picked up. | ||
+ | |||
+ | Okay, intimate family analogies aside, pay attention to how my mother told me to clean up my room: I was not to leave my room until it was clean. Whether or not the room was clean was the condition on which my permission to leave my room depended. So each time I put something away (which, more often then not, I did by stuffing the item under the bed), I would have to check to see that condition was met. If the room wasn't clean, then my mother's condition wasn't met and I had to keep cleaning. If the room was clean, then my mother's condition was met and I was free to leave my room. | ||
+ | |||
+ | Talk about basic parts of loops, variations of loops (do/while, etc.) | ||
==Conclusion: Where to Go From Here== | ==Conclusion: Where to Go From Here== |
Revision as of 14:59, 30 November 2012
Template for future "WML for Complete Beginners" tutorial
Note: this is a work in progress.
TODO:
1. Move the "objectives" section and make it a subsection of the "events" section, since objectives are defined within a prestart event. 2. Add to the numbers definition that numbers can include decimal point values (and reference the fact that WML will remove any unnecessary 0's when it performs the calculations or accesses the numerical value in question)
Contents
- 1 Introduction
- 2 Chapter 1: Syntax
- 3 Chapter 2: The Userdata Directory and the Campaign Folder
- 4 Chapter 3: The _main.cfg
- 5 Chapter 4: Creating Your First Scenario
- 6 Chapter 5: Events
- 7 Chapter 6: Building and Including a Custom Unit
- 8 Chapter 7: Introduction to Variables
- 9 Chapter 8: Array, and Container Variables
- 10 Chapter 9: Macros
- 11 Chapter 10: Introduction to Logic
- 12 Chapter 11: More Logic: Cases and Loops
- 13 Conclusion: Where to Go From Here
Introduction
Hey there!
Now I'm guessing that, if you're reading this, you already know what The Battle for Wesnoth is. If you don't, I suggest finding before you read this tutorial.
Some people may wonder about the purpose of this tutorial. "We already have almost every aspect of WML covered in the ReferenceWML section," these people might say. But the information contained in the WML reference section is just that: a reference. Not a tutorial. This page aims to introduce complete beginners to WML without their having to sift for days through "references" until WML finally begins to vuagely make some sort of sense.
Who This Tutorial is For
The specific demographic for which this tutorial is intended is users with no previous programming knowledge. This tutorial does assume that you have a certain level of competence in basic computer skills and concepts, such as opening and editing text files, and being able to follow folder paths and locate specific directories. In this tutorial you will learn the fundamentals of WML by building a short single-player campaign from the ground up.
What Tools You Will Need
Programming in WML requires only one tool: a basic text editor. You don't need a fancy word processor to program WML (in fact, it is recommended that you avoid using those fancy word processors); a simple program like Windows Notepad or Mac OS X TextEdit will work just fine. For Linux, there is a very nice text editing application called Kate that actually comes with syntax highlighting for WML.
Obviously, you will also need The Battle for Wesnoth installed on your computer.
So What Exactly is WML?
WML is an acronym for the "Wesnoth Markup Language", a custom scripting language that The Battle For Wesnoth uses to allow players to create and modify content without being required to learn a much more complex language like Lua or C++. Now I'm going to say this here and now: don't think you can learn WML overnight. Although WML is relatively easy to learn, it will take a certain amount of effort, time and dedication on your part to fully understand the ins and outs of the language.
Chapter 1: Syntax
First things first; let's go over the syntax of WML.
For those of you who might not know what the "syntax" of a language is, think of it as a set of rules for how WML needs to be written in order for the game to understand it. This concept may sound a bit confusing, but whether you realize it or not you have been using the concept of syntax your entire life! Think of the structure of a sentence in English: "I like jelly and cheese sandwiches." That sentence uses a certain set of rules, or syntax in order to make sense to people who hear or read it. If you said instead, "Like cheese I and sandwiches jelly", that would make no sense, and no one would understand what you were saying. Likewise, if you said "I like cheese sandwiches and jelly", that would change the entire meaning of the sentence!
Just like the syntax of the English language, WML syntax also requires proper capitalization, spelling, grammar and punctuation in order for others to understand what you're saying or writing. For example, if you decided to ignore the syntax of the English language and wrote something like this: "won day mary and i had went to seen the elefant at the zoo it's trunc was reely long", chances are people would not understand much of what you wrote. On the other hand, if you used the correct syntax and wrote: "One day Mary and I went to see the elephant at the zoo. Its trunk was really long!", people can easily understand what you wrote because it is written in the syntax they recognize and understand. Just as English-reading people would be unable to understand something were it not written in the correct English syntax, the Battle for Wesnoth game is unable to read any WML that is not written in the correct WML syntax.
So now let's go over the basics of WML syntax. Later on you will be gradually introduced to more complex WML syntax, but for now we'll just go over the basic stuff, enough to get you started.
Basic Components: Tags and Attributes
WML, as one can infer from the meaning of the acronym, is a markup language. This means that the syntax of the entire language consists of two fundamental elements: tags and attributes.
Tags
- A tag is a string of lowercase text encapsulated by two square brackets, one at either end. This is an example of a "campaign" tag:
[campaign]
- Tags can only contain alphanumeric characters (letters and numbers) and underscores "_". Notice I said earlier that "a tag is a string of lowercase text". This is a fundamental aspect of the WML syntax: all tags are always written in lowercase letters. If you try and use capital letters (even just one), the WML engine won't be able to understand what tag you're trying to use and will give you an error when it tries to read the incorrect tag.
- As with most markup languages, in WML tags are always used in pairs: one opening tag and one closing tag. Think of the opening tag and the closing tag like the covers of a book: when you open the front cover, you know you're at the beginning. When you reach the back cover, you know you're done reading the book. Likewise, when the WML engine finds an opening tag, it realizes it's at the beginning of a task. When it reaches the closing tag, it realizes it has finished the task.
- Closing tags are exactly the same as opening tags except for one key component: closing tags always have a forward slash "/" immediately after the first square bracket. This forward slash tells the WML engine that this tag is a closing tag and not another opening tag. Here is an example of the closing "campaign" tag:
[/campaign]
- The opening and closing tag together are referred to as a "tagset". A tagset always contains exactly two tags: the opening tag and the closing tag. So the "campaign" tagset would look like this:
[campaign] [/campaign]
- So now we know how the WML engine knows where the beginning and end of a task are, and what syntax to use when writing them. These are the basics of tags. Later on we'll go over the more complicated aspects of tags; for now though, make sure you understand the concepts introduced here. Before we move on to the next section, let's review the points we've learned in this section about tags:
- A tag is a string of lowercase text encapsulated by two square brackets, one at either end.
- A closing tag is exactly the same as an opening tag except for the forward slash immediately following the first square bracket.
- The opening tag and the closing tag together are called the tagset.
- A tagset consists of exactly two tags: the opening tag and the closing tag.
- So now we know how to tell the WML engine where the beginning and the end of a task are, but what about actually making it do the task? This is where attributes come in.
Attributes
- Attributes consist of two principal elements: keys and values, and are written like so:
key=value
- The basic function of attributes is to store information that is needed by the WML engine. The key of the attribute specifies what kind of information is stored, and the value of the attribute is the actual data that is stored. All text to the left of the equals sign "=" is considered to be the key, and all text to the right of the equals sign is considered to be the value of the key.
- This may sound rather confusing, so let's illustrate by a practical example:
- Let's say you wanted your friend to go to the grocery store and pick you up a loaf of bread. Would you say to him, "Go"? Of course not! He wouldn't understand what you wanted him to do, which means you'd have no bread for dinner. Likewise, if you only write a tagset, that's equivalent to telling the WML engine to "do", but that's not enough. You will need to be more specific about exactly what the WML engine should do. If your friend were a human WML engine and you wanted him to go to the grocery store to get some bread, you might give him this code to read (note: this code isn't actually real WML, it is "pseudocode", i.e. made up code for the purpose of illustration. If I ever use pseudocode during this tutorial I will tell you that it is pseudocode before you read the example, like I am doing now.):
[go] where=grocery_store get=bread [/go]
- Tags tell the WML engine what kind of task to do, but without attributes to specify exactly what to do, the WML engine won't be able to do anything because you haven't given it enough specific information. This is just like if you told your friend to "Go": he'd understand that you want him to go somewhere, but he'd be unable to perform the task because he doesn't know where to go or what to do when he gets there.
- Keys
- Keys are sequences of lowercase alphabetic characters that tell the game what kind of information it is dealing with when it reads the attribute. Keys are case-sensetive (i.e., you can't use capital letters) and must be spelled correctly.
- Values
- WML keys deal with one and only one type of data, called strings. A string is simply a sequence of ASCII characters that can include pretty much any character on your keyboard. Strings may be divided into two categories: Standard and Numerical.
- 1. Standard Strings
- A standard string is simply a sequence of ASCII characters that is neither a numerical nor a translatable string. For example, this is a standard string:
x
- as is this:
blue
- If a standard string includes whitespaces, it must be enclosed within double quotes:
"Everything in these double quotes is a single string. This is the number one: 1. Hooray! :) We are now coming to the end of this string."
- Everything within the double quotes (including the colon and parenthesis emoticon) is considered to one long string by the game.
- Sometimes you will want to mark a standard string as translatable so that it can be translated into other languages. The only difference between a translatable sting and a non-transatable is that translatable strings are marked so that translators know that they need to translate that string, and non-translatable strings are not marked, so the translators know that they don't translate those strings.
- To mark a string as translatable, you simply put an underscore in front of the string, before the first double quote that marks the beginning of the string. Example of a translatable string:
_ "This is a translatable string."
- If the WML engine does not find an underscore in front of the string, it will assume the string is non-translatable.
- Although not strictly necessary, it is considered good syntax to include a space before and after the underscore that marks a string as translatable. For instance, if we were to assign the translatable string "Hello World!" to this key, it would be considered good syntax to write
key= _ "Hello World!"
- rather than
key=_"Hello World!"
- although the game considers both to be equivalent, and will therefore recognize both as translatable strings.
- 2. Numerical Strings
- Numerical strings, obviously, are strings that contain only numbers, decimal points, or minus signs "-". If a string contains anything other than numbers, decimal points and/or a minus sign, the string becomes a standard string instead of a numerical one. Numerical strings can be either a single numeric character like this:
2
- a sequence of numeric characters, like this:
230001
- or floating-point values (that's just a fancy way of saying that they can contain decimal points), like these two examples:
2.6 395667.49382345
- or negative numbers, like these examples:
-49 -594.932
- For all intents and purposes, you can treat numerical strings just as you would numbers in real life. You can add, divide, and otherwise mathematically employ them in mathematical computations (we'll go over this in-depth in chapter X). Just remember that if you include any characters other than numbers, decimal points, or minus signs, the string will cease to be a numeric string and will become a standard string, which means you won't be able to use it in mathematical calculations.
- Directory paths are simply special strings that tell the game where to find a specific file or folder. Here is an example of a directory path:
{data/add-ons/my_first_campaign}
This directory path tells the game where the folder "my_first_campaign" is located.
More About Tags
So now you should understand the basics about tags and attributes. As I promised earlier, we will now discuss some of the more involved aspects of tags.
Nested Tags: Parents and Children
- A fundamental aspect of markup languages is that you can use tagsets inside other tagsets. Tagsets located inside other tagsets are called nested tagsets. In the example below, the "side" tagset is nested inside the "scenario" tagset:
[scenario] [side] [/side] [/scenario]
- When referring to nested tagsets, the tagset located inside the other is called the child tagset, and the tagset that encloses the child tagset is known ast he parent tagset. To illustrate with pseudocode:
[parent] [child] [/child] [/parent]
- Tagsets that are not child tagsets of any other tagsets are called toplevel tagsets. In this next example, the [scenario] tagset is a toplevel tagset, because it is not the child tagset of any other tagset. The [event] tagset is the child tagset of [scenario], because it is located inside the [scenario] tagset. The tagset [event] is also the parent tagset of the [message] tagset, because the [message] tagset is located inside the [event] tagset. That means that since [event] is the parent of [message], [message] is the child tagset of [event].
[scenario] [event] [message] [/message] [/event] [/scenario]
Indentation and Levels
- You may have noticed that in the examples above, the child tagsets are indented four spaces further to the right than are their parent tagsets. Why is this? It's because proper indentation (although not technically required by the WML engine) makes you code a lot easier to read and maintain. It's like writing an outline for a school paper, where you would write something like this:
I. A. B. C. II. A. 1. 2. B. C.
- Indentation makes it much easier to see whether tagset is the child or parent of other tagsets. The amount of indentation before a tagset determines in what level that tagset is located. If the tagset is a toplevel tagset (i.e. has no spaces in front of it because it is not the child of any other tagset), that tagset is located at level one. Tagsets with an indentation of four spaces in front of them are located in level 2, because they are the children of the toplevel (level 1) tagset. Tagsets that are children of level 2 tagsets are called level 3 tagsets (and are indented 12 spaces), tagsets inside level 3 tagsets are called level 4 tagsets (and are indented 16 spaces), etc. As a general rule, all the attributes of a tagset, along with any child tagsets of that tagset, are indented one level deeper than that tagset. To illustrate in pseudocode:
[toplevel_tagset] level_2_attribute=value [level_2_tagset] level_3_attribute=value [level 3 tagset] level_4_attribute=value [/level 3 tagset] [/level_2_tagset] [/toplevel_tagset]
- Indenting tagsets and attributes into levels like this makes it much easier for you (and others) to read, fix and maintain your code. It is strongly recommended that you indent exactly four spaces for each new level, although you can also use tabs instead of hitting the space key 4 times, if you'd prefer.
Chapter 2: The Userdata Directory and the Campaign Folder
So now that you understand the basic syntax of WML, it's time to learn where The Battle for Wesnoth stores files so that we can begin creating our campaign.
The Userdata Directory
There are two main directories that Wesnoth creates on your computer. The installation directory contains all the files for the core game. The userdata directory stores all your personal Wesnoth data, which includes your installed add-ons, your settings and preferences, and your saved games. The userdata directory is where we will store your work throughout this tutorial.
The location of your userdata directory differs depending on your operating system, and in the case of Microsoft Windows, the version of your operating system. Refer to the following table to determine where your userdata directory is located:
Windows XP | ~/My Documents/My Games/Wesnoth 1.10/ |
---|---|
Windows Vista and above | ~/Documents/My Games/Wesnoth 1.10/ |
Mac OS X | ~/Library/Application Support/Wesnoth 1.10/ |
Linux | ~/.local/share/wesnoth/1.10/ |
Now navigate to your userdata folder. Provided that you have already run the Wesnoth application at least once before, you should see a total of five folders ("cache", "data", "editor", "persist", and "saves") in this directory, along with two files ("preferences" and "save_index.gz"). If you see all seven of these, everything is good. If you do not see all seven, try running the Wesnoth application. If that does not work, try restarting your computer. If neither of these steps work, reinstall Wesnoth.
Of the seven objects contained in the userdata folder, you will only ever need to directly interact with two: the "data" folder and the "editor" folder.
The data folder
- The data folder is where all installed add-ons are kept. This is where we will be building our campaign, when we get to that. If you have previously installed any Wesnoth add-ons, they should show up in this folder.
The editor folder
- The editor folder is where all maps created with the in-game map editor are stored. If you have ever created and saved a map in-game, you should see the map file in this directory.
The Campaign Folder
Like I told you earlier, all add-ons are installed in the data folder inside the userdata directory. That means that your campaign will also be located inside the data folder. If you're not already there, enter the data folder now.
Next, create a new folder inside the data folder. Name this new folder "my_first_campaign" exactly as I wrote it here (but don't include the quotation marks). Make sure you spelled it right, that you didn't accidentally capitalize any letters and that you included the underscores between the words in the folder name, because if you don't your campaign won't work when you try to play it.
Now enter the "my_first_campaign" folder and create seven new folders. Name these folders "images", "macros", "maps", "scenarios", "translations", "units" and "utils" (again, make sure you spelled everything right, that you didn't accidentally capitalize anything, and that you didn't include the quotation marks).
All campaigns share this common folder structure.
Chapter 3: The _main.cfg
Note: this page borrows heavily from the BuildingCampaignsTheCampaignFile page.
So we've created a campaign folder, but as of yet the game doesn't even know this new folder exists. In order for the game to find the folder, we have to create a special file called "_main.cfg" that tells the game where to find all of your campaign's data. Without this file, the game won't be able to find your campaign when it starts up, and consequently you won't be able to play your campaign.
So let's get started creating a "_main.cfg" file so that the game can find your campaign.
Navigate to your campaign folder, if you aren't already there. Now create a new text file and name it "_main.cfg" (just like that, including the underscore but without the quotes). Now you have created a file called "_main.cfg", but we're not done yet. Right now the file is empty, so the game still won't be able to locate your campaign yet. But fear not, all you have to do is write some specific WML inside the "_main.cfg" file and the game will be able to find your campaign just fine.
The Text Domain
Open the "_main.cfg" file in your text editor if you haven't already. and add the following tagset:
[textdomain] [/textdomain]
This tagset which specifies where the game should look for translations to the strings in the campaign (at this stage you probably won't have any translations, but it's a common practice to add a text domain just in case you get translations later on). The textdomain tag specifies a name for the textdomain, which is what is used in the [campaign] tag, and is used in campaign scenarios to connect the strings with translations.
Inside the [textdomain] tag, include the attributes name and path (don't assign values to them just yet, we'll do that in a moment):
[textdomain] name= path= [/textdomain]
- The "name" Attribute
The attribute name specifies the name of the text domain you are creating. The textdomain name should be unique, and start with 'wesnoth-', to ensure that it does not conflict with other textdomains that might be specified on a given system. Let's name our text domain "my_first_campaign". Don't forget to start it with "wesnoth-". Now the contents of your _main.cfg file should look exactly like this:
[textdomain] name="wesnoth-my_first_campaign" path= [/textdomain]
- The "path" Attribute
The attribute path specifies a path to the directory where the compiled translation files will be stored. This should be a file inside the campaign directory. Right now our translations folder is empty, but if you ever get translations for your campaign, this is the folder in which they would go. Let's assign the "translations" folder directory path, which should be "data/add-ons/my_first_campaign/translations", to this attribute. Your _main.cfg should now look like this:
[textdomain] name="wesnoth-my_first_campaign" path="data/add-ons/my_first_campaign/translations" [/textdomain]
Defining the Campaign
And now we're going to add the [campaign] tagset. Yes, it's our old friend, the [campaign] tagset, from Chapter 1.
[campaign] [/campaign]
Next, inside the [campaign] tagset, include the following line:
#textdomain wesnoth-my_first_campaign
This tells the game that the text strings in this file belong to the text domain you just defined above
Next we need to give the attributes id,name,abbrev,icon,image,first_scenario,description,difficulties, and difficulty_descriptions. Don't assign any values to these attributes yet, we'll do that in a moment. For now, just make sure that you have all of these attributes in between the campaign tags, like so (the exact order of the attributes doesn't matter, but it is recommended that you follow the order given below to make things easier for yourself when following this tutorial):
[campaign] #textdomain wesnoth-my_first_campaign id= name= abbrev= define= icon= image= first_scenario= description= difficulties= difficulty_descriptions= [/campaign]
Now let's go over the attributes one by one and give them their values. I'll give you a specific value to assign to each attribute, and then I'll explain why we use that particular value.
- The "id" Attribute
- The unique identifier of your campaign. The value of an "id" attribute can only contain lowercase alphanumeric characters and underscores. We are going to give this attribute the value "my_first_campaign", like so:
id=my_first_campaign
- The "name" Attribute
- The name of your campaign. This translatable string is the name that will show up in the in-game campaign menu, where you can select which campaign you want to play. Let's give it the value "My First Campaign". Since we want this string to be translatable, don't forget to include the translation mark (the underscore) in front of the first double quote.
name= _ "My First Campaign"
- The "abbrev" Attribute
- This attribute defines a campaign abbreviation that will be used as a prefix for the names of save files from that campaign. It should generally consist of the acronym of the campaign's name (i.e., the first letter of each of the words in the campaign's name, capitalized).
- The "define" Attribute
This attribute creates a key that lets the game know when a user has selected to play a scenario from your campaign.
- The "icon" Attribute
The image that will be displayed next to the name of your campaign in the in-game campaign selection menu. Since we need the game to locate a specific file, we [...]
- The "image" Attribute
This defines the image that will appear under your campaign's description when your campaign is selected in the in-game campaign selection menu.
- The "first_scenario" Attribute
- The "description" Attribute
- The "difficulties" Attribute
- The "difficulty_descriptions" Attribute
[campaign] #textdomain wesnoth-my_first_campaign id=my_first_campaign name= _ "My First Campaign" abbrev= _ "MFC" define=CAMPAIGN_MY_FIRST_CAMPAIGN icon= image= first_scenario=my_first_scenario description= _ "This is my first campaign." difficulties=EASY difficulty_descriptions= [/campaign]
The Preprocessor
(discuss preprocessor directives, binary path, directory inclusion, etc.)
Preprocessor Directives
The Binary Path
The binary path is used to tell the game to include a certain directory in the userdata folder whenever it searches for a file. For instance, if you had a custom image in your campaign called "my_face.png", whenever the game finds a reference to that file in a scenario (e.g., you want to display that image at one of the story screens in a scenario), it will first search the core gamedata directories, then it will search any folders included in the binary path. If it cannot find the file in either the gamedata directory or in any of the directories specified in the binary path, then it will give you an error.
You will only need to include a binary path if your campaign contains custom images, sounds or music that is not included in mainline Wesnoth. If you do not have any custom images, sounds or music, then you should not include a binary path. Since we will be including some custom images in our campaign, we are going to need to include a binary path.
To create a binary path, use the following syntax:
[binary_path] path=data/add-ons/my_first_campaign [/binary_path]
This tells the game to search the specified userdata directory whenever it cannot locate a certain file in the gamedata directory. Note that the value of the "path" key must always begin with "data/add-ons/" followed by the name of your campaign folder.
Directory Inclusion
(The final _main.cfg should end up looking something like this:)
[textdomain] name="wesnoth-my_first_campaign" path="data/add-ons/my_first_campaign/translations" [/textdomain] #textdomain wesnoth-my_first_campaign [campaign] #wesnoth-My_First_Campaign id=my_first_campaign name= _ "My First Campaign" abbrev= _ "MFC" define=CAMPAIGN_MY_FIRST_CAMPAIGN #need icon and image (take from core files, don't include external files for sake of simplicity) icon= image= first_scenario=my_first_scenario description= _ "This is my first campaign." difficulties=EASY difficulty_descriptions={MENU_IMG_TXT2 units/undead/shadow-s-attack-4.png _"Easy" _""} [/campaign] #ifdef CAMPAIGN_MY_FIRST_CAMPAIGN [binary_path] path=data/add-ons/my_first_campaign [/binary_path] {~add-ons/my_first_campaign/macros} {~add-ons/my_first_campaign/utils} {~add-ons/my_first_campaign/scenarios} #endif
(note: no units yet, save that for the adding custom units section)
Chapter 4: Creating Your First Scenario
Now that you have your _main.cfg file, it's time to create your first scenario.
Creating the Scenario Map
All scenarios require a map in order to work. Open the Battle for Wesnoth application. On the mainmenu, you should see an option labeled "Map Editor". Click this option and wait for the editor to load.
By default, Wesnoth creates a blank map with the dimensions 44 x 33 (43 wide and 33 tall). These dimensions will work fine for our purposes, so we won't change them. Hover your cursor over the map and look at the center of the upper edge of the Battle for Wesnoth application window. You should see two numbers separated by a comma.
(screenshot)
These numbers are the X and Y coordinates of the hex over which your cursor is currently hovering. If you move your cursor to another hex, the coordinates will change to show the new location of your cursor. This is a fast and convenient way to locate coordinates on the map.
So we have a map, but let's face it: it's rather dull and uninteresting. Time to add some new terrain. Locate the terrain palette (the area at the far right of your Battle for Wesnoth window that displays the terrains you can use in the editor).
(screenshot)
Towards the top of this window, you should see the terrain category selection buttons. From here you can change what category of terrain is displayed in the terrain palette.
(screenshot)
First, let's add a castle for the leader of the player's side to recruit from in the first scenario. Click on the "castle" terrain category button.
(screenshot of the castle terrain category button)
Creating the Scenario .cfg File
Create a new text file in the "scenarios" folder inside your campaign folder. Name this new text file "my_first_scenario.cfg". Open the "my_first_scenario.cfg" file in your text editor, if you haven't already. Since it is a new file, it should be completely empty right now. It won't be when we're done with it, however!
The Toplevel Tagset and Attributes
First, on the very first line of the file, tell the game what text domain this file belongs to. In case you forgot, the text domain for this campaign is " wesnoth-my_first_campaign". So you would write:
#textdomain wesnoth-my_first_campaign
Now let's add the [scenario] tagset:
[scenario] [/scenario]
The [scenario] tagset is a toplevel tagset (i.e., it is not the child of any other tagset) and tells the game where the scenario begins and where it ends. All the information for your scenario goes within this tagset.
Next, inside the [scenario] tagset, include the following keys (don't forget to indent 4 spaces before each key):
id= next_scenario= name= map_data= turns=
Now the contents of the "my_first_scenario.cfg" file should look like this:
#textdomain wesnoth-my_first_campaign [scenario] id= next_scenario= name= map_data= turns= [/scenario]
We have provided keys for these attributes, so now it's time to assign them their values.
- The "id" Key
- Assign the value "my_first_scenario" to the "id" key, like so:
id=my_first_scenario
- Do you remember the value we assigned to the "first_scenario" key in the "_main.cfg" file? If you followed the instructions given in this tutorial, the value of that key should be "my_first_scenario". It is absolutely imperative that the value of the "first_scenario" key and the "id" key of the first scenario are exactly the same. If you misspelled either of them, introduced an incorrect capitalization into either of them, or made a typo of any kind in either of them, the game will return an error message when it tries to load the scenario. This is because the value of the "first_scenario" key refers to the id of your first scenario. If there is no scenario with an "id" key whose value exactly matches the value of the "first_scenario" key in the "_main.cfg", the game won't be able to find the first scenario and will tell you so by giving you an error message when you try to play your campaign.
- The "next_scenario" Key
- We are going to assign the value "null" to the "next_scenario" key. Example:
next_scenario=null
- The "next_scenario" key is a close cousin of the "first_scenario" key found in the "_main.cfg" file. Just as the "first_scenario" tells the game to look for a scenario with an id key whose value matches the value of the the "first_scenario" key, the "next_scenario" key tells the game which scenario to load after the player completes the current scenario. Just like with the "first_scenario" key, make sure the value of the "next_scenario" key exactly matches the id of the next scenario (including underscores, capitalization, spelling, etc.), otherwise you'll get an error message. If there is no next scenario, you would assign the value "null" to the "next_scenario" key, as we did here. This means that when the player completes the current scenario, the game knows that there is no next scenario to go to, and the campaign will end.
- The "name" Key
- For this attribute, we are going to give it this translatable string:
_ "My First Scenario."
As you should recall from our previous discussion about translatable strings, the value we give should be enclosed in double quotes and should have a whitespace, an underscore, and then another whitespace immediately before the first double quote. So now your "name" attribute should look like this:
name= _ "My First Scenario"
- Technically, the name of the scenario doesn't have to resemble the scenario's id at all. But it's a good idea to have the scenario id and the scenario name be as similar as possible to avoid any confusion later on.
- The "map_data" Key
- For this key, we are going to assign the value "{~add-ons/my_first_campaign/maps/my_first_map.map}". Map filepaths must always be within surrounded by double quotes, otherwise error messages will occur. Now it should look like this:
map_data="{~add-ons/my_first_campaign/maps/my_first_map.map}"
- The "turns" Key
- We are going to assign the number "30" to this key:
turns=30
- This attribute specifies how many turns the player will have to complete the scenario. If the player does not complete the scenario objective before the turns run out, then the player will lose the scenario. Since we have assigned the value "30" to this key, that means that if the player hasn't won the scenario by the end of thirty turns, he or she will lose.
Now that we have assigned values to all our keys, the entire contents of the "my_first_scenario.cfg" file should now look like this:
#textdomain wesnoth-my_first_campaign [scenario] id=my_first_scenario next_scenario=null name=_"My First Scenario." map_data="{~add-ons/my_first_campaign/maps/my_first_map.map}" turns=30 [/scenario]
Defining Sides
In any scenario, you will always need at least one side (the player's), and usually at least one other side as well. For this scenario, we need to define two sides: the player's side and the enemy's side.
Let's start with the player's side. In order to define a side, we need to include the [side] tagset as a child of the [scenario] tagset.
(...)
Chapter 5: Events
Let's walk through an average morning. When your alarm clock goes off, you wake up. You go downstairs and put some bread in the toaster for breakfast. When the toaster pops, you butter the toast and eat it. When you have eaten breakfast, you go outside and wait for the schoolbus to arrive. When the bus arrives, you get on it. When it stops at your destination, you get off of the bus.
Notice that you do everything “when” something else happens. These are all “events”. The alarm going off caused you to wake up. The toaster popping causes you to butter the toast and eat it. Finishing breakfast go outside. The bus stopping causes you to get on or off. These are all like events in WML.
The syntax for writing an event goes like this:
[event] name=name_of_the_event #do something [event]
There are quite a few events available to you in WML. Of these, the most commonly used are the prestart event, the start event, and the moveto event.
Common Events
Now, I'm not going to supply you with an exhaustive list of every kind of predefined event and what each does, that's what the EventWML page is for. I will, however, provide you with a list of the most frequently used predefined events and what they do, since we will be using these events in our campaign scenarios later on.
(list these events: prestart, start, moveto, time over, die, last breath)
Objectives
Chapter 6: Building and Including a Custom Unit
Sometimes campaign authors only use mainline units in their campaigns. Other times, however, they may want to include a custom unit that isn't found in default Wesnoth. Thankfully, including a custom unit is quite easy.
Creating Your Custom Unit's .cfg File
Including the Custom Unit In Your Campaign
Chapter 7: Introduction to Variables
Now it's time to learn about “variables”. Variables contain things. They're a lot like boxes that you'd use when moving to a new home. You put things in the boxes, and then you label them so that you can find the right things when unpacking later.
Like attributes, every variable has a name and a value. The name of the variable is like the label on the box, and the variable's value is the thing that the variable (or box) contains.
For instance, you might put all of your dishes in a box and label it “dishes”. Similarly you might create a variable named “my_name” and put your name inside of it. Then if you wanted to find your name later, you could look in the variable called “my_name”.
Creating Variables
To create a variable, the following syntax is used:
[set_variable] name=variable_name value=variable_value [/set_variable]
This creates a variable and assigns a name and value to it.
Referencing Variables
If you have ever taken basic algebra, you should know what substitution is. If you haven't, don't worry, I'll explain it.
Substitution basically allows you to substitute one thing for another thing, as long as both of those things are declared equal. For instance, let's say that x=7. Since they have been declared equal, if you were told to solve this problem:
x-3=
what would you do? Well, since x is equal to seven, you can just replace x with 7, which gives you:
7-3=
From there, it's easy to solve this problem.
What you just did was called substitution. You substituted 7 for x in the problem.
Returning the idea of the dishes stored in the box labeled "dishes". If your mother pointed at the box containing the dishes and said "get out the contents of the box labeled dishes", you understand that she wants you to get out the dishes from the box labeled "dishes", so you'd get out the dishes and give them to her. Now suppose you had a WML variable named "dishes_box" and the value of that variable was "dishes". If you tell the game that you want it to get the value of the variable named "dishes_box", it would give you the value "dishes". So what exactly do we need to do in order to tell the game to get the value of the "dishes_box" variable? This is where substitution comes in.
Suppose you wanted to have the narrator give a message telling the player the value of the variable "dishes_box". Here's how you would tell the game to do that in WML:
[message] speaker=narrator message= _ "The value of the dishes_box variable is: $dishes_box" [/message]
By preceding the name of a variable with a dollar sign "$" you are telling the game that you want to substitute the value of that variable. So in-game the narrator would say, "The value of the dishes_box variable is: dishes"
Suppose you decided change the value of the "dishes_box" variable to "empty". Now the narrator will say in-game, "The value of the dishes_box variable is: empty"
Manipulating Variables
Clearing Variables
Whenever you know for sure that you won't need a variable any more, it's considered good programming practice to delete that variable, or clear it. This effectively tells the game that the variable in question isn't needed any more, so the game deletes that variable. If you don't clear variables once you no longer need them, they can accumulate over time and slow down the performance of both the game and the player's computer.
In order to clear a variable, use the following syntax:
#whatever the syntax is goes here, NOT THE MACRO, we want the user to be able to clear variables without relying on the macro (maybe mention the macro as a side note anyway?)
Chapter 8: Array, and Container Variables
So far we have only discussed scalar variables, i.e. variables that have only one value at any given time. Believe it or not, there are types of variables than can store more than one value simultaneously, or even other variables.
Array Variables
Container Variables
Container variables are variables that contain other variables within themselves. Returning to the metaphor of boxes, let's say you had three small boxes, labeled "Apples", "Oranges", and "Pears", respectively. Instead of having to carry around three smaller boxes, wouldn't it be much easier if we could just put them all in one large box labeled "fruit"? Well, with container variables, you can!
Container variables are not restricted to containing scalar variables, however. They can also store array variables.
Chapter 9: Macros
Have you ever needed to perform a task where you did the exact same thing several times? For instance, maybe you work at a cafe and you have three customers who want the exact same item: a (...)
There are also instances in programming where you need the computer to perform the exact same task multiple times. But each time you need the computer to perform that same task again, you would have to write the same code again. There would be a lot of repetitious code, and it would take forever to write. Wouldn't it be great if we could do that task multiple times without having to result to writing the code out multiple times? Well, with macros, you can.
You can think of macros as a special kind of variable. Just like you can store a long sentence in a variable and substitute it in several messages later on so that you don't have to write out the sentence several times, you can store WML code in a macro and simply call the macro wherever you would have to write all the code contained in the macro. When the scenario is loaded, the preprocessor will automatically substitute the code contained in the macro wherever the macro is called, for the duration of that scenario.
This can be a confusing topic, so let's illustrate with some actual WML examples. Here we have the noble leader and the hard-of-hearing stooge trying to formulate their battle plan at the start of the scenario:
[event] name=start [message] speaker=Leader message= _ "What do you think our plan should be, Stooge?" [/message] [message] speaker=Stooge message= _ "WHAT?" [/message] [message] speaker=Leader message= _ "I said, what do you think our battle plan should be?" [/message] [message] speaker=Stooge message= _ "WHAT?" [/message] [message] speaker=Leader message= _ "WHAT DO YOU THINK OUR BATTLE PLAN SHOULD BE?!" [/message] [message] speaker=Stooge message= _ "WHAT?" [/message] [message] speaker=Leader message= _ "Grrr..." [/message] [/event]
Notice how each time the leader says something, Stooge says the exact same thing: "WHAT?" Rather than having to write
[message] speaker=Stooge message= _ "WHAT?" [/message]
three times, let's stick the code in a macro named "STOOGE_REPLY". Create a new text document in the "macros" folder inside your campaign folder. Name it "my_macros.cfg". Now open the file in a text editor (if you haven't already) and enter the following code:
#define STOOGE_REPLY [message] speaker=Stooge message= _ "WHAT?" [/message] #enddef
Now save the file. Hooray, you have just created your first macro! Now go to the scenarios folder and open the "my_first_campaign"
The Parts of a Macro
Macros consist of three elements: the macro preprocessor directives, the macro symbol, and the macro body.
The Macro Preprocessor Directives
The macro preprocessor directives consist of the strings "#define" and "enddef". They tell the game where the macro begins and ends. Just like the preprocessor directives in the "_main.cfg" file, the macro preprocessor directives must be entirely lowercase and be spelled correctly.
The Macro Symbol
The macro's symbol is the string of uppercase characters following the opening preprocessor directive that identifies the macro. Think of it as the macro's id. Symbols can only include alphanumeric characters and underscores, and should be capitalized throughout. In the case of the example above, the macro symbol would be:
STOOGE_REPLY
The Macro Body
The macro body is the code that is contained in the macro. Everything between the preprocessor directives (with the exception of the macro symbol) is considered by the game to be the macro body. In this case, the macro body is:
[message] speaker=Stooge message= _ "WHAT?" [/message]
Calling A Macro
Now that we have created our macro and understand what it does and what its respective parts are, let's return to our scenario and scroll to the start event that contains the dialogue between the leader and the stooge. Replace each instance of this code:
[message] speaker=Stooge message= _ "WHAT?" [/message]
with the following line:
{STOOGE_REPLY}
Basically, this tells the game that whenever it comes across an instance of this line:
{STOOGE_REPLY}
it should substitute the following code in place of that line:
[message] speaker=Stooge message= _ "WHAT?" [/message]
Macro Arguments
Chapter 10: Introduction to Logic
If This, Then That
Suppose you went to the grocery store to get some food. You park you car in the parking lot and begin to walk towards the store, when suddenly you realize that you can't remember if you shut the car door or not after you got out. You turn around to see if the car door is open...
...and then what?
Well, if the car door is open, you know that you did forget to close it, so you go over and close it before you go into the grocery store. If the car door isn't open, there's no need for you to do anything, so you keep walking towards the store.
This kind of logic (called conditional logic) evaluates a condition and determines a reaction based on the state of that condition. In the example above, the condition is whether or not the car door is open. If it is open, then you go over and close it. If it isn't open, then you continue walking towards the store.
In WML you have a number of logical operations available to assess and react to conditions. The first one we will go over is the if/then operation. The syntax of this operations is:
[if] (condition) [then] (do something) [/then] [/if]
Else
With the if clause, if the condition is true then we do something. But what if the condition isn't true? Well, in the examples above the WML engine doesn't have anything to do if the condition isn't true, so it just keeps running and doesn't do anything. However, it is possible to make the game perform a task if the condition isn't true, which is done by using the tagset [else]. The syntax is:
[if] (condition] [then] (do something) [/then] [else] (do something else) [/else] [/if]
Returning to the example of the car door, suppose that, before you turn around to check to see whether the door is open or not, you decide to go over and close the door if it is open, and if it isn't then you'll do a backflip to celebrate your genius before continuing your journey across the parking lot to the grocery store. In pseudocode:
[if] the car door is open [then] go over and shut it [/then] [else] do a backflip [/else] [/if]
Chapter 11: More Logic: Cases and Loops
Cases
Suppose you had a scenario in which the player must pick a flower by moving a unit to the coordinates 14,30 in order to win. But that's not all: the flower can only be picked after turn 10 and before turn 20 (so the flower can only be picked between turns 11 and 20). If the player tries to pick the flower before turn 11, the game tells him that the flower hasn't bloomed yet and that he or she needs to wait a while longer. If the player tries to pick the flower after turn 20, the game will display a message telling him or her that he or she waited too long, and now the flower is dead. How would you go about implementing this in WML?
Well, you could write something like this:
[event] name=moveto first_time_only=no [filter] side=1 [/filter] [if] [variable] name=turn_number less_than_equal_to=10 [/variable] [then] [message] speaker=narrator message= _ "The flower hasn't bloomed yet. Try coming back later." [/message] [/then] [/if] [if] [variable] name=turn_number greater_than=20 [/variable] [then] [message] speaker=narrator message= _ "You waited too long: the flower is dead now." [/message] [/then] [/if] [if] [variable] name=turn_number greater_than=10 [/variable] [and] [variable] name=turn_count less_than_equal_to=20 [/variable] [/and] [then] [message] speaker=narrator message= _ "You pick the flower." [/message] [/then] [/if] [/event]
but this is extremely lengthy and tedious to write and maintain. Wouldn't it be awesome if we had some simpler way to test multiple conditions without having to create an entire [if] codeblock for each condition?
Well, actually, there is an easier way to test for multiple conditions without having to wade through a quagmire of if codeblocks. This is done via the switch/case codeblock, which tests the value of a variable for multiple conditions, and determines the action performed as a result of that evaluation. The syntax of the switch/case codeblock goes thusly:
[switch] name=variable_name [case] value=first_value #do something [/case] [case] value=second_value #do something [/case] [case] value=third_value #do something [/case] [else] #do something [/else] [/switch]
Loops
I was a very messy child, much to the distress of my mother, who was the epitome of cleanliness and couldn't for the life of her understand how she could have raised such an untidy child as me. Occasionally she'd work up enough courage to venture into the land of chaos that was my room, and then she'd tell me not leave my room until it was picked up.
Okay, intimate family analogies aside, pay attention to how my mother told me to clean up my room: I was not to leave my room until it was clean. Whether or not the room was clean was the condition on which my permission to leave my room depended. So each time I put something away (which, more often then not, I did by stuffing the item under the bed), I would have to check to see that condition was met. If the room wasn't clean, then my mother's condition wasn't met and I had to keep cleaning. If the room was clean, then my mother's condition was met and I was free to leave my room.
Talk about basic parts of loops, variations of loops (do/while, etc.)
Conclusion: Where to Go From Here
As long and involved as this tutorial was, it barely scratches the surface of WML. You can't call yourself a WML expert just yet, but the hardest part for you — finding a starting point — is now over. You now know how to create a simple campaign, and you know many of the features WML has to offer.
One of the best ways to learn more WML (and one of the only ways before this tutorial ;) ) is to study the WML code of other add-ons. You can also research any tags you're not familiar with on the WML Reference, which is a complete reference to pretty much every element of WML.
If you run into any problems that you can't solve by looking at other people's code or by studying the wiki, post a message in the WML Workshop forum. The resident WML gurus will be happy to help you out if you ask politely.
You've been given the tools and the materials. Now go out and build something amazing!