Events Tutorial

So you want to add some events, but you have no idea how? Well you've come to the right place, because so do I. This tutorial documents my journey into the world of events (it may later be refined to be a more straightforward tutorial). 
Please read the Lua reference manual for a reference to all available events, actions and datatypes with their methods that are available in the Event Scripting API. The following examples work with Freeciv 2.1 (Example 1) and Freeciv 2.2 (All examples)
=== The basic premise ===
An event includes a trigger (signal) and an action. In the script code you will write, you create a callback function using the Lua scripting language. This callback function provides the actions and is linked up to a trigger that is invoked from the freeciv code itself.
An example is signal.connect('unit_moved', 'unit_moved_callback') which registers the script to listen for unit_moved events. unit_moved_callback should be a function in the Lua script.
=== Getting started ===
You may add events either to a ruleset or to a scenario. Changing the ruleset will affect anyone who plays with that ruleset; if you add an event to the default ruleset it will affect almost everyone. Adding an event to the scenario means only someone who plays with your scenario will get this event. As an example, the civ2 ruleset is a ruleset, while a Europe or World-War-Two scenario would probably be scenarios.
To add events to the ruleset you must edit the script.lua file in the ruleset directory. This is pretty straightforward.
To add events to the scenario you must add some script code directly inside the .sav file itself. This means you add a section
 [script]
 code=$
 …
 $
to the .sav file. The code you will write goes in place of the ... bits between the $ signs.
If we insert the shortest possible event handler in such a code block, it looks like this:
 [script]
 code=$
-- This callback reacts when a unit is moved
function unit_moved_callback(unit, src_tile, dst_tile)
 notify.all('A/An %s moved!', unit.utype:name_translation())
end 
signal.connect('unit_moved', 'unit_moved_callback')
 $
To see how this works in practice, look in the tutorial at .
One important thing to note is the use of "." (as in "unit.utype" above) vs ":" (as in "utype:name_translation()" above). The "." is required before a variable name, and the ":" is required before a function name. Otherwise, the code will fail to load, and the position of the syntax error is not reported.
=== Example 1: Scenario narrative ===
An example that could be used in the tutorial: Tell the user that a found spot is good for a city. We make a trigger for the unit_moved event and look if a Setter moves to a Plains or Grassland tile:
 [script]
 code=$
-- This line is a comment since it is marked with two dashes
function has_unit_type_name(unit, utype_name)
 return (unit.utype.id == find.unit_type(utype_name).id)
end
function has_tile_terrain_name(tile, terrain_name)
 return (tile.terrain.id == find.terrain(terrain_name).id)
end
-- This callback reacts when a unit is moved
function unit_moved_callback(unit, src_tile, dst_tile)
 if unit.owner:is_human() then
 if has_unit_type_name(unit, 'Settlers')
 and (has_tile_terrain_name(dst_tile, 'Grassland')
 or has_tile_terrain_name(dst_tile, 'Plains')) then
 notify.event(unit.owner, nil, E.TUTORIAL or E.SCRIPT, [[
This looks like a good place to build a city. The next time this
unit gets a chance to move, press (b) to found a city.
In general you want to build cities on open ground near water. Food
is the most important resource for any city. Grassland and plains
provide plenty of food.]])
 end
 end
end
signal.connect('unit_moved', 'unit_moved_callback')
 $
In this example, the special [[ … ]] syntax denotes a multiline string. This is so we can show a long message to the user. A short string may also be written with quotes in only one line: notify.player(unit.owner, "This is a good spot for a city!")
notify.event takes four parameters here: Player to receive the message, Tile where the event happened, message category and the message itself. As you can see, we can say nil (which means "empty value") to say that there is no specific tile to this event. If you want though, you extend this example by using unit.tile as the event location.
=== Example 2: Creating Units ===
In this example, we change the way we create mercenaries from huts. Instead of
defining a new event handler, we override an event handler in the default
ruleset (Freeciv 2.2).
In our version, we always create Archers as mercenaries, and we tell the user
"Archers join your side!".
 [script]
 code=$
-- Define a function by the same name as in the
-- default rulset. Our Scenario version supercedes
-- the ruleset version.
function default_hut_get_mercenaries(unit)
 local owner = unit.owner
 local utype = find.unit_type("Archers")
 if utype then
 notify.event(owner, unit.tile, E.HUT_MERC,
 "%s join your side!", utype:name_translation())
 -- create_unit takes arguments:
 -- Player, Tile, Unit Type, Veteran level, Homecity, Moves Left
 create_unit(owner, unit.tile, utype, 0, unit:get_homecity(), -1)
 return true
 else
 return false
 end
end
-- In early versions, the function used this name instead
hut_get_mercenaries = default_hut_get_mercenaries
-- NOTE: We don't use signal.connect when overriding
 $
=== Example 3: Scenario Objective ===
We may use events to give scenario victory to a player according to any custom
objectives. Let's create a mini-scenario with two opposing sides with each
side with its own capital (here, Berlin and Paris). The scenario shall
be won whenever one side conquers the capital of the other.
 [script]
 code=$
-- I used the Map editor to look up the identifiers of these cities,
-- they stay constant through the game even if the cities change name
berlin_city_id = 105
paris_city_id = 106
-- React to the city_lost event
function city_was_lost(city, loser, winner)
 local napoleon = find.player(0)
 local friedrich = find.player(1)
 if city.id == paris_city_id then
 friedrich:victory()
 notify.all("%s is lost! %s wins the war.",
	 city.name, friedrich.name)
 elseif city.id == berlin_city_id then
 napoleon:victory()
 notify.all("%s is lost! %s wins the war.",
	 city.name, napoleon.name)
 end
end
function city_was_destroyed(city, loser, destroyer)
 -- in this case, we can handle it the same way
 city_was_lost(city, loser, destroyer)
end
signal.connect('city_lost', 'city_was_lost')
signal.connect('city_destroyed', 'city_was_destroyed')
 $
Category:Event scripting