Freeciv supports Event Scripting in rulesets and scenarios since version 2.1, using the Lua language. The tutorial is implemented as a scripted scenario.
Hardcoded game events which are candidates for scripting Edit
- Entering village huts. (implemented)
- Partisan generation on conquering city. (implemented)
- Darwin free techs on build, and Philosophy free tech to first player who discovers it.
- Marco Polo gives embassies.
- Building a spaceship "building" gives no building but rather a spaceship part.
- The following text is transcluded from an older 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 Edit
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 Edit
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:
-- 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 …/data/scenarios/tutorial.sav.
Example 1: Scenario narrative Edit
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:
-- 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 Edit
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!".
-- 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 Edit
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.
-- 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')