[[TOC]] = Triggers = This document will describe how to add triggers to maps, how to program triggers, and what the possibilities of triggers are. We will try to give a lot of examples, that should be easy to copy-paste and modify where needed. But this isn't meant as an introductory course to JavaScript. For a JavaScript reference, it's best to look at the [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference Mozilla Development Network] (MDN). For creating and editing JavaScript files, it's best to use a decent text editor. Syntax highlighting is a must, so [http://www.notepad-plus-plus.org/ Notepad++] on Windows should be sufficient. == Examples A good way to learn how triggers work is to check some examples of existing trigger missions in addition the information provided in this article. || '''Mission name''' || '''Description''' || '''Files''' || || Treasure Islands || Simple multiplayer trigger mission to collect treasures on a group of islands before your opponent does. || [http://trac.wildfiregames.com/browser/ps/trunk/binaries/data/mods/public/maps/scenarios/Treasure%20Islands.pmp Treasure Islands.pmp][[BR]][http://trac.wildfiregames.com/browser/ps/trunk/binaries/data/mods/public/maps/scenarios/Treasure%20Islands.xml Treasure Islands.xml] [[BR]] [http://trac.wildfiregames.com/browser/ps/trunk/binaries/data/mods/public/maps/scenarios/treasure_islands.js treasure_islands.js] || == Modifying your map == For now, Atlas isn't very prepared for triggers. So we have no GUI to edit triggers (obviously), but also no GUI to add trigger scripts to your map. This means you need to edit your map data. For scenarios and skirmish maps, this map data is a JSON part in the map.xml file (see [source:ps/trunk/binaries/data/mods/public/maps/skirmishes/Alpine_Valleys_(2).xml#L42 example]). For random maps it's in the map.json file directly (see [source:ps/trunk/binaries/data/mods/public/maps/random/aegean_sea.json example]). In this JSON part of the data, you define an array of loaded trigger scripts. You can define multiple more general helper scripts, and your custom script(s). {{{ #!js { "CircularMap": true, "Description": "The first steps of playing 0 A.D.", "...": "...", "TriggerScripts": [ "scripts/TriggerHelper.js", "skirmishes/basic_tutorial.js" ] } }}} The root of these paths is in `modName/maps/`, and it's custom to put general scripts in the `scripts` directory, and map-specific scripts next to the existing map files. Of course, you need to make sure these files exist by creating them with your favourite text editor. You can write a small warning in the script, so you know your script is executed {{{ #!js warn("My first trigger script"); }}} This should show a yellow warning in the top left of your screen when the map loading is finished. Several difficulty levels are possible for the scripts (see simulation/data/settings/trigger_difficulties.json for a complete list). For that purpose, you should add in the JSON part: {{{ #!js { "SupportedTriggerDifficulties": { "Values": ["Easy", "Hard"], "Default": "Easy" } } }}} if your script supports these two levels. Then, in your JS script, you can retrieve the difficulty level chosen by the player using {{{ #!js { let difficulty = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger).GetDifficulty(); } }}} '''Tip:''' See [wiki:Logging Logging] for more logging options, and use `uneval(data)` to transform any JS data into human-readable text. == Writing the trigger scripts == A trigger script typically consists of '''actions''' which are fired when certain '''events''' happen, and it keeps a bit of '''data''' to know the state of the game. These three parts are explained below. === Action functions === Actions are regular JavaScript functions, registered under the `Trigger` prototype. These functions can call each other, or be called from a trigger. When actions are called from a trigger, they always receive a data object with more information about the event that just happened. How the data looks depends on the actual event (see the table at the bottom). {{{ #!js Trigger.prototype.MyAction = function(data) { // The action was called on a certain event // Do Something }; }}} Calling one function from another is possible with the following type of calls: {{{ #!js Trigger.prototype.MyAction = function(data) { // Ask a different function to calculate something, or execute some sub-action var entities = this.GetRelevantEntities(data.player); // Do something with those entities }; Trigger.prototype.GetRelevantEntities = function(player) { // calculate something return relevantEntities; }; }}} The `this` keyword refers to the `Trigger` object anything defined under the prototype + additional data, which makes it easy to use. === Registering triggers === When you have your actions made, you need to bind them to a certain event. This is done via the predefined RegisterTrigger function. Triggers can be enabled in any method, but you usually need to enable some at the start of the game. You can use the following schema: {{{ #!js { // Get the cmpTrigger object let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger); // Register the trigger directly cmpTrigger.RegisterTrigger("OnPlayerCommand", "MyAction", { "enabled": true }); } }}} The first parameter of the trigger is the event on which it will be executed (see the table at the bottom for supported events). In the example, the trigger will be executed for every player command (move, gather, attack, donate, ...). The second parameter is the name of the action, which has to be defined under the Trigger prototype. And the third part is a data object. For most triggers, this data will be just enabled = true or false. But for more complicated triggers (range triggers, time triggers, ...) this data can contain other elements, such as the the distance of the range trigger, or the interval of the timer. Again, see the table below. The combination of event and action name must be unique. This combination can be used to enable and disable triggers. Registering a trigger twice isn't possible, and you will be warned when you do that. When you have your first triggers registered, they can fire actions, but actions can again register new triggers. As this time, it happens within the `Trigger` prototype, you have to access `cmpTrigger` with `this`. {{{ #!js Trigger.prototype.MyTriggerRegisteringAction = function(data) { var myData = {"enabled": true}; this.RegisterTrigger("OnPlayerCommand", "MySecondAction", myData); }; }}} Enabling and disabling triggers in action functions happens as shown: {{{ #!js Trigger.prototype.MySecondAction = function(data) { this.EnableTrigger("OnPlayerCommand", "MyAction"); // OR this.DisableTrigger("OnPlayerCommand", "MyAction"); }; }}} You can enable and disable triggers as often as you want. === Keeping track of data === Quite often, you'll want to keep track of data that changes throughout the game (number of units killed, number of times a certain trigger is executed, ...). Keeping track of data also happens under the `Trigger` object, so with the `this` keyword. ==== Data initialisation ==== Storing data you want to keep track of should happen at the end of you script, and be done in the `Trigger` component. By doing so, you can use the data later on (in the `Trigger` prototype functions) with the `this` keyword. {{{ #!js // get the cmpTrigger object (the trigger component) var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger); // Add data to it cmpTrigger.executedTriggers = 0; cmpTrigger.killedUnits = 0; cmpTrigger.state = "initialising"; }}} ==== Data usage ==== After data initialisation, you can just use it in other trigger functions. {{{ #!js Trigger.prototype.MyAction = function(data) { this.executedTriggers++; // will enlarge the counter with one this.killedUnits += TriggerHelper.GetKilledEntities(data); // execute a function with separate logic, and add it to a counter } }}} == Loading technicalities (how to support saved games) == When a saved game is loaded, or someone joins an existing multiplayer game, it's important that the same state is kept. This requires you to know how and when trigger scripts are loaded. The trigger script is loaded as the last step when loading a game. In JavaScript, loading a script means executing it. So this creates all functions (without executing them), and adds the data to objects like you define them. When loading a saved game, the same happens, but after loading the script, it also restores data saved under the trigger object (but not under the prototype) to the old variables. Which means that it will overwrite registered triggers (as that's data) and your data, to give an equal state. Thus both the `Trigger` prototype (e.g. `Trigger.prototype.myData`) and the data you add directly to `this` (`this.myData`) can be accessed via `this`. But only the data added directly to `this` will be saved in a saved game (e.g. `this.myVar` will be saved but `Trigger.protoype.myVar` not!). So although it's possible to add other stuff to the prototype than just functions, only do so if it doesn't change throughout the game (i.e. constants like `Trigger.prototype.MIN_UNITS_REQUIRED_FOR_ATTACK = 1;` are fine). Also note that functions can't be saved in saved games. So functions should always be defined under the prototype, and never change. == Reference Table == {{{#!th Event name }}} {{{#!th Accepted Data format }}} {{{#!th Returned data format }}} {{{#!th Notes }}} |---------------- {{{#!td `OnCinemaPathEnded` }}} {{{#!td {{{#!js { } }}} }}} {{{#!td {{{#!js { "name": CinemaPathName } }}} }}} {{{#!td }}} |---------------- {{{#!td `OnCinemaQueueEnded` }}} {{{#!td {{{#!js { } }}} }}} {{{#!td {{{#!js { } }}} }}} {{{#!td Happens when all cinema paths are ended }}} |---------------- {{{#!td `OnConstructionStarted` }}} {{{#!td {{{#!js { "enabled": bool } }}} }}} {{{#!td {{{#!js { "foundation": entityId, "template": templateName } }}} }}} {{{#!td Happens when the building of the foundation starts }}} |---------------- {{{#!td `OnDiplomacyChanged` }}} {{{#!td {{{#!js { "enabled": bool } }}} }}} {{{#!td {{{#!js { "player": playerId, "otherPlayer": playerId } }}} }}} {{{#!td Happens when a player changes their diplomacy stance towards another player. Note that two events will be sent if the diplomacy worsens between the two players. }}} |---------------- {{{#!td `OnInterval` }}} {{{#!td {{{#!js { "delay": number, "interval": number, "enabled": bool } }}} }}} {{{#!td {{{#!js { } }}} }}} {{{#!td }}} |---------------- {{{#!td `OnOwnershipChanged` }}} {{{#!td {{{#!js { "enabled": bool } }}} }}} {{{#!td {{{#!js { "entity": entityId, "from": playerId, "to": playerId } }}} }}} {{{#!td This event is also fired when a unit/building is created or destroyed. In that case, the ownership goes from (resp. to) the invalid player entity `-1`. }}} |---------------- {{{#!td `OnPlayerCommand` }}} {{{#!td {{{#!js { "enabled": bool } }}} }}} {{{#!td {{{#!js { "player": playerId, "cmd": unitCommand } }}} }}} {{{#!td Any command a player sends. The "cmd" part of a command always have a type, and per type can have different other atributes. Check the documentation in the source file ([source:ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js Commands.js]) for documentation }}} |---------------- {{{#!td `OnPlayerDefeated` }}} {{{#!td {{{#!js { "enabled": bool } }}} }}} {{{#!td {{{#!js { "playerId": playerId } }}} }}} {{{#!td }}} |---------------- {{{#!td `OnPlayerWon` }}} {{{#!td {{{#!js { "enabled": bool } }}} }}} {{{#!td {{{#!js { "playerId": playerId } }}} }}} {{{#!td }}} |---------------- {{{#!td `OnRange` }}} {{{#!td {{{#!js { "entities": [entityId], "players": [playerId], "minRange": number, "maxRange": number, "requiredComponent": componentId, "enabled": bool } }}} }}} {{{#!td {{{#!js { "added": [entityId], "removed": [entityId], "currenctCollection": [entityId] } }}} }}} {{{#!td The range entities data should always be ids of trigger points. }}} |---------------- {{{#!td `OnResearchFinished` }}} {{{#!td {{{#!js { "enabled": bool } }}} }}} {{{#!td {{{#!js { "player": playerId, "tech": templateName } }}} }}} {{{#!td }}} |---------------- {{{#!td `OnResearchQueued` }}} {{{#!td {{{#!js { "enabled": bool } }}} }}} {{{#!td {{{#!js { "playerid": playerId, "technologyTemplate": templateName, "researcherEntity": entityId } }}} }}} {{{#!td }}} |---------------- {{{#!td `OnStructureBuilt` }}} {{{#!td {{{#!js { "enabled": bool } }}} }}} {{{#!td {{{#!js { "building": constructedBuilding } }}} }}} {{{#!td Happens when a foundation gets finished or a building gets completely repaired }}} |---------------- {{{#!td `OnTrainingFinished` }}} {{{#!td {{{#!js { "enabled": bool } }}} }}} {{{#!td {{{#!js { "entities": [entityId], "owner": playerId, "metadata": metadata } }}} }}} {{{#!td Happens when training of some units is finished }}} |---------------- {{{#!td `OnTrainingQueued` }}} {{{#!td {{{#!js { "enabled": bool } }}} }}} {{{#!td {{{#!js { "playerid": playerId, "unitTemplate": templateName, "count": number, "metadata": metadata, "trainerEntity": entityId } }}} }}} {{{#!td }}} |---------------- {{{#!td `OnTreasureCollected` }}} {{{#!td {{{#!js { } }}} }}} {{{#!td {{{#!js { "player": playerId, "type": type.specific, "amount": status.amount } }}} }}} {{{#!td Happens in ResourceGatherer.js when a treasure is collected. }}}