Latest Posts

Changes in Scenario Tutorial

Editor Comment

Reviewed until objectives


Revision Differences of Revision 3

[TOC] ¶

This tutorial has the goal to give some initial help for creating a scenario. While we can't provide a full explanation of the Lua language, nor all the possibilities of the Lua API that widelands provides, you should be able to understand the code of the implemented scenarios and can adapt it to your own scenario. ¶

If you have reached the end of this tutorial, you have learned how the implemented scenarios are structured, how to create message boxes, make a smooth scrolling of the map, create a logic when the player reaches a specific point on the map, give the player an objective and create the logic for the objective. ¶

What you should know before reading on: ¶

* [Scenario Tutorial](https://wl.widelands.org/docs/wl/tutorial/), at least the chapters 'Designing a map' and 'Hello World'. The chapter [A LUA Primer](/docs/wl/tutorial/#a-lua-primer) mustn't be read, because this tutorial points to some external pages describing some parts of the LUA programming language. ¶
* Make sure to use a plain text editor when writing scripts, e.g. [notepad++](https://notepad-plus-plus.org), [Geany](https://www.geany.org) or [dig through wikipedia articles about text editors](https://en.wikipedia.org/wiki/Text_editor) ¶

To follow this example, create an empty map with only one player set at position 0,0 . To find this position watch the values at the bottom right in the editor. Those values represents: x position, y position, height. Make sure you have set the tribe 'Empire' in the menu 'Players Option'. ¶

## init.lua ¶

You have read that the file _'your_map/scripting/init.lua'_ is the main entry for your scenario. So this file is the starting point and should contain necessary global settings, which are usually separated in different parts: ¶

### Including some helperscripts ¶

~~~~ ¶
include "scripting/coroutine.lua" ¶
include "scripting/messages.lua" ¶
include "scripting/ui.lua" ¶
include "scripting/richtext.lua" ¶
~~~~ ¶

The files which get included here contain functions which you may need in your project. E.g. the file _'coroutine.lua'_ contains a function _sleep(time)_. Those files (and their functions) are described in [Auxiliary Scripts](https://wl.widelands.org/docs/wl/autogen_toc_auxiliary/). ¶

### Setting Global Variables ¶

In this part of the file _'init.lua'_ are variables defined which can be used in each other of the following files. That's why they are called 'global' variables. This global variables are shortcuts and prevent writing each time the same: ¶

~~~~ ¶
-- Defining global variables ¶
plr = wl.Game().players[1] ¶
map = wl.Game().map ¶
plr_sf = map.player_slots[1].starting_field ¶
~~~~ ¶

What this code does: ¶

* `-- Defining global variables`: All text beginning with two hyphens are treated as a comment, so this does nothing. You can also comment parts of lua code to deactivate them ¶
* `plr = wl.Game().players[1]`: This reads the first defined player of the map (the one you have set) and assigns it to the variable 'plr'. If you have set more players to the map, they can be accessed by increasing the value in the brackets, so `wl.Game().players[2]` is the second player. ¶
* `map = wl.Game().map`: The variable `map` gives you now access to many properties of the map itself, e.g. to the function _get_field()_. This will be the most used function in your scenario. ¶
* `plr_sf = map.player_slots[1].starting_field`: You have set a player in the map editor on a field, so this assigns this field to the global variable `plr_sf`. Note that here the variable `map` is already used, which was defined one line above. Also note that there is a point between 'map', 'player_slots' and 'starting_field'. I tell you later why this is important. ¶

### Including the script to load starting conditions ¶

Now the main initialization is done and you can work on to get the story alive. To split of initialization and other stuff, include another file in the last line of your _'init.lua'_
: ¶

~~~~ ¶
-- Load own lua scripts ¶
include "map:scripting/starting_conditions.lua" ¶
~~~~ ¶

TODO: Explain where the `map:` comes from ¶

The file _'starting_conditions.lua'_ will contain initial settings for the players. The include at the end of the file _'init.lua'_ makes sure all other initialization is done and the global variables are available in each further included file. ¶

## _'starting_conditions.lua'_ ¶

The map you have created is empty by default. You have set a player, but no buildings at all. It is your part to place buildings and fill them with wares and workers. ¶

If not already done, create a new file and save it as _'starting_conditions.lua'_ in the folder _'scripting'_ of your map. ¶

### Place buildings ¶

First you place the headquarters for the player. Do you remember you have set the global variables `plr` and `plr_sf`? Here we use them: ¶

~~~~ ¶
-- Place the headquarter for player1 ¶
local hq = plr:place_building("empire_headquarters", plr_sf, false, true) ¶
~~~~ ¶

Compare the given arguments with the description of [_place_building()_](/docs/wl/autogen_wl_bases/#wl.bases.PlayerBase.place_building). The first argument for the function is a string (enclosed in double quotation marks). You could find the correct string in the corresponding init.lua of the building in the subfolders of _'data/tribes/buildings/'_. You need the string of the _'name'_ attribute here. ¶

The above function returns the building of the created buildings. In this example the building is stored in the __local__ variable `hq`. ¶

__Importand remark about local:__ Whenever you assign variables with values, you should do it this way: `local name_of_variable = value/function`. The keyword _local_ makes sure that this variable is only valid in this scope (file or function). The only exception: If you really know that you will use this variable elsewhere, you can omit the keyword _local_. But in this case you should give the variable a name which is absolutely clear! ¶

Note the colon between _plr_ and the functions name. Do you remember that there where already a point as a separator? Time for a small explanation about the difference between point and colon: ¶

Consider to have an object like a car. This car has a property: color. But this car has also some functions: drive_straight(), drive_left(), drive_right(). If you want to get the color, you want to access the property: `clr = car.color.` If you want to drive the car you need to call the functions: `car:drive_straight(100 meter)`. Do you see the difference? Accessing a property needs a point, calling a function needs a colon! If you go through the documentation you may notice that some entries have brackets, and others not. The entries with brackets are functions, other are properties. Examples: ¶

* Properties (can be accessed through a point): [_Player.name_](/docs/wl/autogen_wl_game/#wl.game.Player.name), [_Map.width_](/docs/wl/autogen_wl_map/#wl.map.Map.width) ¶
* Functions (must be called with a colon): [_Player:sees_field(f)_](/docs/wl/autogen_wl_game/#wl.game.Player.sees_field), [_Map:get_Field(x,y)_](/docs/wl/autogen_wl_map/#wl.map.Map.get_field) ¶

Now its time for a first test. Run: ¶

~~~~ ¶
widelands --scenario=/full/path/to/the/map.wmf ¶
TODO: example for windows OS ¶
~~~~ ¶

You should see the placed headquarters after loading is complete. Look at the stock... the headquarters is empty, no wares and workers are in it. You should change that. ¶

### Fill buildings ¶

The function [place_building()](/docs/wl/autogen_wl_bases/#wl.bases.PlayerBase.place_building) returns the building of the created building. The building is stored in the local variable `hq` and this variable gives access to the building itself. So you could use the function [set_wares()](https://wl.widelands.org/docs/wl/autogen_wl_map/#wl.map.HasWares.set_wares) to fill the building: ¶

~~~~ ¶
hq:set_wares("log", 5) ¶
hq:set_wares("planks",10) ¶
hq:set_wares("granite",10) ¶
~~~~ ¶

Because setting wares is a common task, there is a shortcut by passing a table to _set_wares()_: ¶
~~~~ ¶
hq:set_wares({ ¶
log = 5, ¶
planks = 20, ¶
granite = 20, ¶
marble = 5, ¶
}) ¶
~~~~ ¶

Same goes for setting workers
and soldiers: ¶
~~~~ ¶
hq:set_workers({ ¶
empire_builder = 5, ¶
empire_carpenter = 2, ¶
empire_lumberjack = 5, ¶
empire_forester=2, ¶
}) ¶

hq:set_soldiers({0,0,0,0}, 45) ¶
~~~~ ¶

Like with the buildings name you will find the right names for wares and workers in the corresponding _'init.lua'_ in the subfolders of _'data/tribes/'_. Add as many wares and workers as you like.
Af

Set
ting soldiers is some what complicated, because you have to define also the soldier's training levels. See [set_soldiers()](/docs/wl/autogen_wl_map/#wl.map.HasSoldiers.set_soldiers) yfor an explanation of the values. ¶
~~~~ ¶
hq:set_soldiers({0,0,0,0}, 45) ¶
~~~~ ¶

Y
ou should make a test run again, after finishing these settings. ¶

For an introduction
tof tables, see the [Tables tutorial](http://lua-users.org/wiki/TablesTutorial) in the lua users wiki. ¶

Summary: ¶

* A campaign starts always with an empty map, but needs setting the starting position of players including their tribe ¶
* The file _'init.lua'_ contain only basic initial settings ¶
* Do the players initialization in the file _'starting_conditions.lua'_ ¶
* Its up to the scenarios author to place at least the basic buildings (headquarters, warehouses). AI players needs at least a headquarters or a warehouse. ¶
* Also the initial set of wares, workers and soldiers is up to the scenarios author ¶
* Include _'starting_conditions.lua'_ at the end of _'init.lua'_ so the contents will be available ¶

Now its time to create the main control script. It is usually called _'main_thread.lua'_ ¶

## _'main_thread.lua'_ and _'texts.lua'_ ¶

The file _'main_thread.lua'_ contains most of the logic for the scenario. The file _'texts.lua'_ should contain texts for message boxes that your scenario needs. ¶

Create at least the file _'main_thread.lua'_. ¶

### Message boxes ¶

You may have seen that our scenarios have some message boxes to inform the player about the story. In _'init.lua'_ the functions of _'messages.lua'_ are included. This file contains convenient functions for messages. Use [_campaign_message_box()_](/docs/wl/autogen_auxiliary_messages/#campaign_message_box) and add the following to the new file _'main_thread.lua'_: ¶

~~~~ ¶
campaign_message_box({title = "This is the title", body = p("Hi, this is my first scenario")}) ¶
~~~~ ¶

The function is called with the needed arguments, in this case a table (enclosed with curly braces {}). The 'body' must be formatted with the richtext system, the function _p(text)_ is used here. See [richtext.lua](https://wl.widelands.org/docs/wl/autogen_auxiliary_richtext/) for an explanation and other formatting functions. ¶

The file _'main_thread.lua'_ must be loaded, so include it as last line in _'init.lua'_: ¶

~~~~ ¶
-- Load own lua scripts ¶
include "map:scripting/starting_conditions.lua" ¶
include "map:scripting/main_thread.lua" ¶
~~~~ ¶

Run a test. The message box should be shown after loading has finished. ¶

### texts.lua ¶

You may have more than one message box in your campaign. For better readability of your main script _'main_thread.lua'_ it is better to put the table(s) of text in an extra file. The built in campaigns have them usually in a file called _'texts.lua'_ which get included in _'init.lua'_ [after including _'starting_conditions.lua'_](/#including-the-script-to-load-starting-conditions) or at the beginning of _'main_thread.lua'_. This will look then like: ¶

File _'texts.lua_': ¶
~~~~ ¶
-- Store the table in the global variable 'first_message' ¶
first_message = { ¶
title = "This is the title", ¶
body = p("Hi, this is my first scenario"), ¶
} ¶
~~~~ ¶

File _'main_thread.lua'_: ¶
~~~~ ¶
-- Including tables of text so the variables defined over there can be used here ¶
include "map:scripting/texts.lua" ¶

-- Use the variable (which represents the table in texts.lua) ¶
campaign_message_box(first_message) ¶
~~~~ ¶

You could also modify the message box, by applying additional values to the table in _'texts.lua'_: ¶

~~~~ ¶
-- Store the table in the global variable 'first_message' ¶
first_message = { ¶
title = "This is the title", ¶
body = p("Hi, this is my first scenario"), ¶
posx = 1, ¶
posy = 1, ¶
} ¶
~~~~ ¶

See [message_box()](/docs/wl/autogen_wl_game/#wl.game.Player.message_box) for the explanation of _posx_ and _posy_. ¶

Exercise: ¶

- Play around with the values of _posx_ and _posy_. Remember that you can also comment lines of code with two hyphens at the beginning of a line ¶
- Change the width and height of the message box ¶

[Solution for playing with message box attributes](#play-with-message-box-attributes) ¶

### Triggering events - adding logic ¶

You know now how to create message boxes, but you will also need an event, a trigger, to show it to the player. Such events are the most important things in campaigns. They offer functionality like: ¶

* Show message boxes when things happens ¶
* Running out of building material ¶
* The player has reached a specific point on the map ¶
* A specific building has been build ¶
* Add objectives a player has to solve ¶
* and so on ¶

Usually an event is combined with an objective, but because triggering an event is an important task, objectives are handled in the next chapter. In this example an event is created if the player reaches a specific point on the map. There are two possibilities to get this work: ¶

* Checking the return value of the function [_Player:sees_field(field)_](https://wl.widelands.org/docs/wl/autogen_wl_game/#wl.game.Player.sees_field)
. A player sees a field, if it is revealed. It is not necessary if this field is in the displayed area of the map (a player currently sees on his display). This field could be in the players territory, or not.
* Checking the property [_Field.owner_](https://wl.widelands.org/docs/wl/autogen_wl_map/#wl.map.Field.owner)
. A owned field is inside a players territory.

In this example the property _field.owner_ is used, but both possibilities needs a field to work with. Fields are part of the map and can be accessed through `wl.Game().map`. In the [global variables section of init.lua](#global-variables) this was assigned to the variable `map` . Now this variable is used to call the function [_get_field(x,y)_](/docs/wl/autogen_wl_map/#wl.map.Map.get_field). This function needs the x and y coordinates of the map, so add the following to _'main_thread.lua'_ below the call to campaign_message_box: ¶

~~~~ ¶
-- Get the field at position x=10, y=0 and store it in the variable 'field' ¶
local field = map:get_field(10,0) ¶
print("Field: ", field , "Owner: ", field.owner) ¶
~~~~ ¶

If you run the scenario the field coordinates and the owner of that field will be printed in the console. The value of _field.owner_ is currently 'nil', because this field has no owner. Note that the print statement is only executed after clicking "OK" in the message box. ¶

What is needed further is a mechanism to check if the owner of that field has changed. A loop is needed that runs the same code over and over until the owner has changed. The problem with such loops is that they can't be run as a standalone loop in LUA. This is where coroutines comes in. A coroutine is started by calling the function [_run()_](/docs/wl/autogen_auxiliary_coroutine/). So you create a loop inside a function and call `run()` with the functions name as argument at the end of _'main_thread.lua'_. Replace the previous added code with this one, note the moved message box: ¶

~~~~ ¶
-- Defining a function called 'check_field_owner
()' ¶
function check_field_owner() ¶
local field = map:get_field(10,0) ¶

-- Start defining a loop ¶
while true do ¶
print("Field: ", field , "Owner: ", field.owner) ¶
sleep(1000) -- An important row, see below ¶
-- End of defining the loop ¶
end ¶

-- End of defining the function ¶
end ¶

-- Call the function 'run()' with name of function as argument ¶
run(check_field_owner) ¶

campaign_message_box(first_message) ¶
~~~~ ¶

The row __`sleep(1000)`__ is really important. If you forget that, this loop prevents every interaction between the player and the program itself: ¶

* A player can't do anything else while the coroutine is executed ¶
* The only way to stop it is to kill the whole program either by clicking on the 'X' of the window, or with your taskmanager ¶

Run the scenario and look into the console output. After loading is complete, the field coordinates and the field owner is printed out exactly one time. Then the message box appears. If you click "OK" in the message box the print statement is further called every second. The important thing
s with this observation is: ¶

*1. __Starting a coroutine returns immediately__ : After starting the coroutine by calling _run(name_of_funtion)_ all code after this call will be executed also. In this case: The message box is shown. So it is possible to start more coroutines by calling _run(name_of_funtion)_ more than once.
2. The loop is running further


Ok, now it's time for some magic: Run your scenario and explore into the direction of the field (to the right). As soon the field is owned by you, the output will change. Why is this magic? The variable `field` is defined outside of the loop so it is initialized only once. One could think that the fields state (the owner in this case) is also only initialized once, but this is not the case. That is because the variable `field` is a __reference__ to the field. Whenever this field changes the changes are also reflected in the variable `field`! ¶

The loop created above will never end. This has to be changed by adding a condition to the loop. You may have seen in the consoles output, that the _field.owner_ has changed to `Player(1)`. The first player was assigned to the global variable `plr` in [_'init.lua'_](#global-variables), so comparing`field.owner` with our global variable `plr` should do the trick. To check that the loop is really endet,
we add also another message box is added: ¶
~~~~ ¶
function check_field_owner() ¶
local field = map:get_field(10,0) ¶
while field.owner ~= plr do
-- Run loop as long 'field.owner NOT EQUAL plr' ¶
while field.owner ~= plr do ¶
sleep(1000) ¶
end ¶
campaign_message_box( {field = field, title = "Field owned", body = p("This field is now owned by meyou!")} ) ¶
end ¶

run(check_field_owner) ¶
~~~~ ¶

The loop runs now until 'field.owner __not equal__ plr'. Such a comparison returns either 'true' or 'false'. See also [Rational Operators](https://www.lua.org/pil/3.2.html) for other types. For other types of loops see [Control structures](https://www.lua.org/pil/4.3.html). ¶

As soon the message box appears, you can be sure that the loop has been ended. ¶

Exercise: ¶

* Move the table of the message box into the file _'texts.lua'_ and make the corresponding changes ¶
* Add a new function _plr_sees_field()_ and use [_sees_field()_](/docs/wl/autogen_wl_game/#wl.game.Player.sees_field) for the condition. You have to choose another field though, one which is outside the area the player sees, for testing purposes use field 15,0 ¶
* Create another message box which shows that the player can now see the field ¶
* Run the new function by adding an additional `run()` command ¶

When running the scenario and you explore the territory to right, you should see at least two message boxes popping up: One after the first coroutine has ended, and the new one after the second coroutine has ended. ¶

If you can't get it to work, [here is the Solution](#add-another-function). ¶

There is one drawback with coroutines: If you save the game, the coroutines and the state of their variables are saved also. This means you can't run a game, save it, change the logic of a coroutine, and reload the saved game to check the new logic of the coroutine. The new logic will not be part of the loaded save game, because it uses the old logic. The only way to test the new logic is to start a new game. This makes working on scenarios a bit hard, especially for beginners. At the end of this article are [some tips for testing coroutines](#tips-for-testing-coroutines) ¶

### Summary ¶

If you have reached this point of this tutorial you have learned a lot. Here are the main things: ¶

* Split logic and texts und leave logic in _'main_thread.lua'_ whereas put texts in other files ¶
* Include each other file either from _'init.lua'_ or at the beginning of _'main_thread.lua'_ ¶
* Use always the keyword `local` when defining variables ¶
* Add events as functions and execute them through _run(function)_ ¶
* Don't forget to use _sleep()_ in loops ¶
* To verify variables you can always use a _print()_ statement ¶
* Changing logic of coroutines have only affect if you start a new game ¶

### Smooth scrolling of the map ¶

The [included file _'ui.lua'_](#including-some-helperscripts) contain the needed functions for smooth scrolling. The first function is [_wait_for_roadbuilding_and_scroll(field)_](/docs/wl/autogen_auxiliary_ui/#wait_for_roadbuilding_and_scroll), which returns the center x/y position in pixels of the map. You store the return value in a local variable, here `cur_view`, and use this variable later on for the function [_scroll_to_map_pixel(pixel_coordinates)_](/docs/wl/autogen_auxiliary_ui/#scroll_to_map_pixel). Apply this to the function check_field_owner(): ¶
~~~~ ¶
function check_field_owner() ¶
local field = map:get_field(10,0) ¶
while field.owner ~= plr do ¶
sleep(1000) ¶
end ¶

-- Call the function and store the return value ¶
local cur_view = wait_for_roadbuilding_and_scroll(field) -- Call the function and store the return value
campaign_message_box(field_reached) ¶
scroll_to_map_pixel(cur_view)
-- Use the return value to scroll back to 'cur_view'
scroll_to_map_pixel(cur_view)

end ¶
~~~~ ¶

Run a test and watch the result. ¶

## Objectives ¶

If you have played some of our scenarios you have seen that there are objectives a player has to fulfill. The steps for objectives are similar to what you have learned so far when triggering events: ¶

1. Show a message box with the objectives text and add it to the game ¶
2. Run a loop until the objectives conditions are fulfilled ¶
3. Remove the objective from the list of objectives ¶

At the end of this chapter, the objective should be started close after the player has owned the field and should advise him to build a forester, a lumberjack and a sawmill. ¶

### Add an objective ¶

The function [_add_campaign_objective()_](/docs/wl/autogen_auxiliary_messages/#add_campaign_objective) is responsible to add an objective to the game, so it is shown when hitting the _Objectives_ button in the bottom menu. Try it out by adding this code at the end of _'main_thread.lua'_ (so it is executed directly after the coroutines has been started): ¶

~~~~ ¶
-- Testing adding an objective ¶
local o = add_campaign_objective({ ¶
name = "build_forester_lumberjack", ¶
title = "Build a forester and a Lumberjack", ¶
body = li("Build a forester") .. ¶
li("Build a lumberjack")) ¶
}) ¶
~~~~ ¶

Run the game and hit the button to show the objective. ¶

The code is similar to showing a message, but there is one additional entry in the table: _name_. The value for _name_ is important and is used internally to distinguish between the added objectives. When ever adding an objective, make sure the value assigned to _name_ is unique for the whole scenario. The _body_ is formatted with function _li("text")_, which shows the texts as list items. The doubled points (`..`) is the Lua string concatenation operator and must be used to concatenate all strings. ¶

Exercise: ¶

* As with message boxes it is better to separate texts and logic, so pull out the table and store it in a new file _'objectives.lua'_ ¶
* Dont forget to include the new file ¶
* Add another list item to the objective saying to build a sawmill ¶

[Solution](#separate-objective-texts-and-logic) ¶

### The logic of the objective ¶

The needed function to get the build buildings of a player is [_check_for_buildings()_](/docs/wl/autogen_auxiliary_objective_utils/#check_for_buildings) and lives in the file [_'objective_utils.lua'_](/docs/wl/autogen_auxiliary_objective_utils/#objective-utils-lua). This file isn't [included](#including-some-helperscripts) yet, so add it. Then create a function for the objective and use the return value of _check_for_buildings()_ in the loop for the comparison: ¶

~~~~ ¶
-- The coroutine to check if the condition of the objective is fulfilled ¶
function check_forester_lumberjack() ¶
campaign_message_box(ask_for_forester_lumberjack) ¶
local obj = add_campaign_objective(obj_build_lumberjack_forester) ¶
while not check_for_buildings(plr, { ¶
empire_lumberjacks_house=1, ¶
empire_foresters_house=1, ¶
empire_sawmill=1, ¶
}) do ¶
sleep(1000) ¶
end ¶
campaign_message_box(done_forester_lumberjack) ¶
obj.done = true -- Mark the objective as solved ¶
end ¶
~~~~ ¶

Add an additional `run()` statement at the end of _'main_thread.lua_' to run the new coroutine and run the scenario. First check the the objective by clicking on the button 'Objectives' in the bottom menu, then build a forester, a lumberjack and a sawmill, then check the 'Objectives' again. ¶

But this new coroutine is running right after start, but it should show up after we have owned the field. All what is needed to archive this, is to move the row `run()` from the end of _'main_thread.lua'_ to the end of the function `check_field_owner()`: ¶

~~~~ ¶
function check_field_owner() ¶
-- existing code ... ¶

run(check_forester_lumberjack) -- Start the objective now ¶
end ¶
~~~~ ¶

### Combining message box and objective ¶

The function to check the objective contains two message boxes: One before the objective is added to the game, and one at the end. It is possible to combine the the first message box with the call to add the objective, so the code is one line smaller. The related function for this is [_message_box_objective()_](/docs/wl/autogen_auxiliary_messages/#message_box_objective) and is provided through [_'messages.lua'_](/docs/wl/autogen_auxiliary_messages/). This function also has additional functionality, like showing the message box in the top right corner of the screen (which is not possible with normal message boxes). ¶

Change the table of the objective (in _'objectives.lua'_) to this one: ¶

~~~~ ¶
obj_build_lumberjack_forester = { ¶
-- Values for the message box ¶
title = "New objective", ¶
body = p("Build a forrester and a lumberjack"), ¶
w = 120, ¶
h = 120, ¶

-- Values for the objective ¶
obj_name = "build_lumberjack_forester", ¶
obj_title = "Build a forester and a Lumberjack", ¶
obj_body = li("Build a forrester") .. ¶
li("Build a lumberjack"), ¶

-- Additional arguments ¶
position = "topright", ¶
} ¶
~~~~ ¶

This is similar to what we had already. The values for the message box are the same, but the attributes for the objective changed to have the prefix 'obj_'. ¶

Exercise: ¶

* Remove the first message box in function _check_for_buildings()_ and also the related code for it in _'texts.lua'_ ¶
* Use the new function _message_box_objective()_ instead of _add_campaign_objective()_. Don't forget to add the first argument 'plr' ¶

[Solution](#usage-of-_message_box_objective) ¶

## Solutions ¶

### Separate objective texts and logic ¶

It is usual to start the name of the table of an objective with the prefix 'obj_': ¶

File _'objectives.lua'_: ¶

~~~~ ¶
obj_build_lumberjack_forester = { ¶
name = "build_lumberjack_forester", ¶
title = "Build a forester and a Lumberjack", ¶
body = li("Build a forrester") .. ¶
li("Build a lumberjack") .. ¶
li("Build a sawmill") ¶
} ¶
~~~~ ¶

Including _'objectives.lua'_ at the top of file _'main_thread.lua'_: ¶

~~~~ ¶
-- Including tables of objectives ¶
include "map:scripting/objectives.lua" ¶

-- functions and run commands already made ¶

-- Testing adding an objective ¶
local o = add_campaign_objective(obj_build_lumberjack_forester) -- Use the table of objectives.lua ¶
~~~~ ¶


### Play with message box attributes ¶

File _'texts.lua'_: ¶
~~~~ ¶
-- Store the table in the variable 'first_message' ¶
first_message = { ¶
title = "This is the title", ¶
body = p("Hi, this is my first scenario"), ¶
posx = 1, ¶

-- commented code ¶
-- posy = 1, ¶

-- Applying width and height to the message box ¶
w = 200, ¶
h = 200, ¶
} ¶
~~~~ ¶

### Add another function ¶

Does your solution look like this one? ¶
~~~~ ¶
-- Added function to check sees_field() ¶
function check_sees_field() ¶
local field = map:get_field(15,0) ¶
while plr:sees_field(field) ~= true do -- See below ¶
sleep(1000) ¶
end ¶
campaign_message_box(plr_sees_field) ¶
end ¶

run(check_field) ¶
-- Additional run() command ¶
run(check_sees_field) ¶
~~~~ ¶

The condition for the loop could also be written this way to improve readability: ¶

~~~~ ¶
while not plr:sees_field(field) do ¶
~~~~ ¶

### Usage of _message_box_objective() ¶

~~~~ ¶
function check_forester_lumberjack() ¶
local obj = message_box_objective(plr, obj_build_lumberjack_forester) ¶
-- old code goes here ¶
~~~~ ¶

## Tips for testing coroutines ¶

* scriptconsole F6 ¶
* dofile("filename") ¶
* commenting coroutines ¶