function Trigger() {} Trigger.prototype.Schema = ""; /** * Events we're able to receive and call handlers for. */ Trigger.prototype.eventNames = [ "OnAttackDetected", "OnCinemaPathEnded", "OnCinemaQueueEnded", "OnConstructionStarted", "OnDiplomacyChanged", "OnDeserialized", "OnInitGame", "OnInterval", "OnEntityRenamed", "OnOwnershipChanged", "OnPlayerCommand", "OnPlayerDefeated", "OnPlayerWon", "OnRange", "OnResearchFinished", "OnResearchQueued", "OnStructureBuilt", "OnTrainingFinished", "OnTrainingQueued", "OnTreasureCollected" ]; Trigger.prototype.Init = function() { // Difficulty used by trigger scripts (as defined in data/settings/trigger_difficulties.json). this.difficulty = undefined; this.triggerPoints = {}; // Each event has its own set of actions determined by the map maker. this.triggers = {}; for (const eventName of this.eventNames) this.triggers[eventName] = {}; }; Trigger.prototype.RegisterTriggerPoint = function(ref, ent) { if (!this.triggerPoints[ref]) this.triggerPoints[ref] = []; this.triggerPoints[ref].push(ent); }; Trigger.prototype.RemoveRegisteredTriggerPoint = function(ref, ent) { if (!this.triggerPoints[ref]) { warn("no trigger points found with ref "+ref); return; } const i = this.triggerPoints[ref].indexOf(ent); if (i == -1) { warn("entity " + ent + " wasn't found under the trigger points with ref "+ref); return; } this.triggerPoints[ref].splice(i, 1); }; Trigger.prototype.GetTriggerPoints = function(ref) { return this.triggerPoints[ref] || []; }; /** * Create a trigger listening on a specific event. * * @param {string} event - One of eventNames * @param {string} name - Name of the trigger. * If no action is specified in triggerData, the action will be the trigger name. * @param {Object} triggerData - f.e. enabled or not, delay for timers, range for range triggers. * @param {Object} customData - User-defined data that will be forwarded to the action. * * @example * triggerData = { enabled: true, interval: 1000, delay: 500 } * * General settings: * enabled = false * If the trigger is enabled by default. * action = name * The function (on Trigger) to call. Defaults to the trigger name. * * Range trigger: * entities = [id1, id2] * Ids of the source * players = [1,2,3,...] * list of player ids * minRange = 0 * Minimum range for the query * maxRange = -1 * Maximum range for the query (-1 = no maximum) * requiredComponent = 0 * Required component id the entities will have */ Trigger.prototype.RegisterTrigger = function(event, name, triggerData, customData = undefined) { if (!this.triggers[event]) { warn("Trigger.js: Invalid trigger event \"" + event + "\"."); return; } if (this.triggers[event][name]) { warn("Trigger.js: Trigger \"" + name + "\" has been registered before. Aborting..."); return; } // clone the data to be sure it's only modified locally // We could run into triggers overwriting each other's data otherwise. // F.e. getting the wrong timer tag triggerData = clone(triggerData) || { "enabled": false }; if (!triggerData.action) triggerData.action = name; this.triggers[event][name] = { "triggerData": triggerData, "customData": customData }; // setup range query if (event == "OnRange") { if (!triggerData.entities) { warn("Trigger.js: Range triggers should carry extra data"); return; } triggerData.queries = []; for (const ent of triggerData.entities) { const cmpTriggerPoint = Engine.QueryInterface(ent, IID_TriggerPoint); if (!cmpTriggerPoint) { warn("Trigger.js: Range triggers must be defined on trigger points"); continue; } triggerData.queries.push(cmpTriggerPoint.RegisterRangeTrigger(name, triggerData)); } } if (triggerData.enabled) this.EnableTrigger(event, name); }; Trigger.prototype.DisableTrigger = function(event, name) { if (!this.triggers[event][name]) { warn("Trigger.js: Disabling unknown trigger " + name); return; } const triggerData = this.triggers[event][name].triggerData; // special casing interval and range triggers for performance if (event == "OnInterval") { if (!triggerData.timer) // don't disable it a second time return; const cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); cmpTimer.CancelTimer(triggerData.timer); triggerData.timer = null; } else if (event == "OnRange") { if (!triggerData.queries) { warn("Trigger.js: Range query wasn't set up before trying to disable it."); return; } const cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); for (const query of triggerData.queries) cmpRangeManager.DisableActiveQuery(query); } triggerData.enabled = false; }; Trigger.prototype.DisableAllTriggers = function() { for (const event of this.eventNames) for (const name in this.triggers[event]) this.DisableTrigger(event, name); }; Trigger.prototype.EnableTrigger = function(event, name) { if (!this.triggers[event][name]) { warn("Trigger.js: Enabling unknown trigger " + name); return; } const triggerData = this.triggers[event][name].triggerData; // special casing interval and range triggers for performance if (event == "OnInterval") { if (triggerData.timer) // don't enable it a second time return; if (!triggerData.interval) { warn("Trigger.js: An interval trigger should have an intervel in its data"); return; } const cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); triggerData.timer = cmpTimer.SetInterval(this.entity, IID_Trigger, "DoAction", triggerData.delay || 0, triggerData.interval, { "action": name }); } else if (event == "OnRange") { if (!triggerData.queries) { warn("Trigger.js: Range query wasn't set up before"); return; } const cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); for (const query of triggerData.queries) cmpRangeManager.EnableActiveQuery(query); } triggerData.enabled = true; }; Trigger.prototype.OnGlobalInitGame = function(msg) { this.CallEvent("OnInitGame", {}); }; Trigger.prototype.OnGlobalConstructionFinished = function(msg) { this.CallEvent("OnStructureBuilt", { "building": msg.newentity, "foundation": msg.entity }); }; Trigger.prototype.OnGlobalTrainingFinished = function(msg) { this.CallEvent("OnTrainingFinished", msg); // The data for this one is {"entities": createdEnts, // "owner": cmpOwnership.GetOwner(), // "metadata": metadata} // See function "SpawnUnits" in ProductionQueue for more details }; Trigger.prototype.OnGlobalResearchFinished = function(msg) { this.CallEvent("OnResearchFinished", msg); // The data for this one is { "player": playerID, "tech": tech } }; Trigger.prototype.OnGlobalCinemaPathEnded = function(msg) { this.CallEvent("OnCinemaPathEnded", msg); }; Trigger.prototype.OnGlobalCinemaQueueEnded = function(msg) { this.CallEvent("OnCinemaQueueEnded", msg); }; Trigger.prototype.OnGlobalDeserialized = function(msg) { this.CallEvent("OnDeserialized", msg); }; Trigger.prototype.OnGlobalEntityRenamed = function(msg) { this.CallEvent("OnEntityRenamed", msg); }; Trigger.prototype.OnGlobalOwnershipChanged = function(msg) { this.CallEvent("OnOwnershipChanged", msg); // data is {"entity": ent, "from": playerId, "to": playerId} }; Trigger.prototype.OnGlobalPlayerDefeated = function(msg) { this.CallEvent("OnPlayerDefeated", msg); }; Trigger.prototype.OnGlobalPlayerWon = function(msg) { this.CallEvent("OnPlayerWon", msg); }; Trigger.prototype.OnGlobalDiplomacyChanged = function(msg) { this.CallEvent("OnDiplomacyChanged", msg); }; /** * Execute a function after a certain delay. * * @param {number} time - Delay in milliseconds. * @param {string} action - Name of the action function. * @param {Object} eventData - Arbitrary object that will be passed to the action function. * @return {number} The ID of the timer, so it can be stopped later. */ Trigger.prototype.DoAfterDelay = function(time, action, eventData) { const cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); return cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Trigger, "DoAction", time, { "action": action, "eventData": eventData }); }; /** * Execute a function each time a certain delay has passed. * * @param {number} interval - Interval in milleseconds between consecutive calls. * @param {string} action - Name of the action function. * @param {Object} eventData - Arbitrary object that will be passed to the action function. * @param {number} [start] - Optional initial delay in milleseconds before starting the calls. * If not given, interval will be used. * @return {number} the ID of the timer, so it can be stopped later. */ Trigger.prototype.DoRepeatedly = function(time, action, eventData, start) { const cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); return cmpTimer.SetInterval(SYSTEM_ENTITY, IID_Trigger, "DoAction", start !== undefined ? start : time, time, { "action": action, "eventData": eventData }); }; /** * This function executes the actions bound to the events. * It's either called directlty from other simulation scripts, * or from message listeners in this file * * @param {string} event - One of eventNames * @param {Object} data - will be passed to the actions */ Trigger.prototype.CallEvent = function(event, eventData) { if (!this.triggers[event]) { warn("Trigger.js: Unknown trigger event called:\"" + event + "\"."); return; } for (const name in this.triggers[event]) if (this.triggers[event][name].triggerData.enabled) this.DoAction({ "action": this.triggers[event][name].triggerData.action, "eventData": eventData, "customData": this.triggers[event][name].customData, "triggerData": this.triggers[event][name].triggerData }); }; /** * Call the action method of a trigger with the given event Data. * By default, call the trigger even if it is currently disabled. */ Trigger.prototype.CallTrigger = function(event, name, eventData, evenIfDisabled = true) { if (!this.triggers[event]?.[name]) { warn(`Trigger.js: called a trigger '${name}' for event '${event}' that wasn't found`); return; } if (!evenIfDisabled && !this.triggers[event][name].triggerData.enabled) return; this.DoAction({ "action": this.triggers[event][name].triggerData.action, "eventData": eventData, "customData": this.triggers[event][name].customData, "triggerData": this.triggers[event][name].triggerData }); }; /** * Called by the trigger listeners to execute the actual action. Including sanity checks. * Intended for internal use, prefer CallEvent or CallTrigger. */ Trigger.prototype.DoAction = function(msg) { if (this[msg.action]) this[msg.action](msg?.eventData, msg?.customData, msg?.triggerData); else warn("Trigger.js: called a trigger action '" + msg.action + "' that wasn't found"); }; /** * Level of difficulty used by trigger scripts. */ Trigger.prototype.GetDifficulty = function() { return this.difficulty; }; Trigger.prototype.SetDifficulty = function(diff) { this.difficulty = diff; }; Engine.RegisterSystemComponentType(IID_Trigger, "Trigger", Trigger);