Ticket #52: Triggers.6.patch

File Triggers.6.patch, 42.0 KB (added by sanderd17, 10 years ago)

New proposal. Extending the Triggers prototype to give the trigger makers less hassle calling components

  • binaries/data/mods/public/simulation/components/BuildingAI.js

     
    146146 */
    147147BuildingAI.prototype.OnRangeUpdate = function(msg)
    148148{
    149 
    150149    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
    151150    if (!cmpAttack)
    152151        return;
  • binaries/data/mods/public/simulation/components/Foundation.js

     
    202202            // (via CCmpTemplateManager). Now we need to remove that temporary
    203203            // blocker-disabling, so that we'll perform standard unit blocking instead.
    204204            cmpObstruction.SetDisableBlockMovementPathfinding(false, false, -1);
     205           
     206            // Call the related trigger event
     207            var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     208            cmpTrigger.CallEvent("OnConstructionStarted", {"foundation": this.entity, "template": this.finalTemplateName});
    205209        }
    206210
    207211        // Switch foundation to scaffold variant
  • binaries/data/mods/public/simulation/components/ProductionQueue.js

     
    288288                "timeTotal": time*1000,
    289289                "timeRemaining": time*1000,
    290290            });
     291           
     292            // Call the related trigger event
     293            var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     294            cmpTrigger.CallEvent("OnTrainingQueued", {"playerid": cmpPlayer.GetPlayerID(), "unitTemplate": templateName, "count": count, "metadata": metadata, "trainerEntity": this.entity});
    291295        }
    292296        else if (type == "technology")
    293297        {
     
    324328                "timeTotal": time*1000,
    325329                "timeRemaining": time*1000,
    326330            });
     331           
     332            // Call the related trigger event
     333            var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     334            cmpTrigger.CallEvent("OnResearchQueued", {"playerid": cmpPlayer.GetPlayerID(), "technologyTemplate": templateName, "researcherEntity": this.entity});
    327335        }
    328336        else
    329337        {
  • binaries/data/mods/public/simulation/components/ResourceTrickle.js

     
    5757    if (cmpPlayer)
    5858        for (var resource in rates)
    5959            cmpPlayer.AddResource(resource, rates[resource]);
    60    
    6160};
    6261
    6362Engine.RegisterComponentType(IID_ResourceTrickle, "ResourceTrickle", ResourceTrickle);
  • binaries/data/mods/public/simulation/components/TechnologyManager.js

     
    286286        return;
    287287    }
    288288   
     289    // Call the related trigger event
     290    var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     291    cmpTrigger.CallEvent("OnResearchFinished", {"researcher": this.entity, "tech": tech});
     292   
    289293    var modifiedComponents = {};
    290294    this.researchedTechs[tech] = template;
    291295    // store the modifications in an easy to access structure
  • binaries/data/mods/public/simulation/components/Trigger.js

     
     1function Trigger() {};
     2
     3Trigger.prototype.Schema =
     4    "<a:component type='system'/><empty/>";
     5
     6Trigger.prototype.Init = function()
     7{
     8    this.triggerEntities = {};
     9
     10    var eventNames = [
     11        "OnEntityTookDamage", 
     12        "OnEntityKilled", 
     13        "OnStructureBuilt", 
     14        "OnConstructionStarted", 
     15        "OnTrainingFinished", 
     16        "OnTrainingQueued", 
     17        "OnResearchFinished", 
     18        "OnResearchQueued", 
     19        "OnUnitIssuedOrder",
     20        "OnUnitsIssuedOrder"
     21    ];
     22       
     23    // Each event has its own set of actions determined by the map maker.
     24    for each (var eventName in eventNames)
     25        this["event" + eventName + "Actions"] = [];
     26     
     27    // Special events
     28    this.eventOnIntervalData = {};
     29    this.rangeTriggers = {};
     30   
     31    // We are going to have all of the triggers here to be able to enable/disbale them in runtime.
     32    this.triggerEnabled = {};
     33   
     34    // To prevent the lose of trigger variables after a save, they "should" be defined in "InitTriggers" function, which is the starting point of a trigger script
     35    this.DoAfterDelay(0, "InitGame", {});
     36};
     37
     38Trigger.prototype.InitGame = function()
     39{
     40};
     41
     42Trigger.prototype.RegisterTriggerEntity = function(ref, ent)
     43{
     44    if (!this.triggerEntities[ref])
     45        this.triggerEntities[ref] = [];
     46    this.triggerEntities[ref].push(ent);
     47};
     48
     49Trigger.prototype.RemoveRegisteredTriggerEntity = function(ref, ent)
     50{
     51    if (!this.triggerEntities[ref])
     52        return;
     53    var i = this.triggerEntities[ref].indexOf(ent);
     54    if (i != -1)
     55         this.triggerEntities[ref].splice(i, 1);
     56};
     57
     58Trigger.prototype.GetRegisteredTriggerEntities = function(ref)
     59{
     60    return this.triggerEntities[ref] || [];
     61};
     62
     63/**  Binds the "action" function to one of the implemented events.
     64 *
     65 * @param eventName Name of the event (see the list in init) 
     66 * @param action Functionname of a function available under this object
     67 */
     68Trigger.prototype.RegisterTrigger = function(event, action, enabled)
     69{
     70    var eventString = "event" + event + "Actions";
     71    if (this[eventString])
     72    {
     73        this[eventString].push(action);
     74        this.triggerEnabled[[event, action]] = enabled
     75    }
     76    else
     77        error("Invalid trigger event \"" + event + "\".")
     78};
     79
     80// Disable trigger
     81Trigger.prototype.DisableTrigger = function(event, action)
     82{
     83    if (this.triggerEnabled[[event, action]] !== undefined)
     84        this.triggerEnabled[[event, action]] = false;
     85    else
     86        warn("Trigger key doesn't exist:" + event + " & " + action);
     87}
     88
     89// Enable trigger
     90Trigger.prototype.EnableTrigger = function(event, action)
     91{
     92    if (this.triggerEnabled[[event, action]] !== undefined)
     93        this.triggerEnabled[[event, action]] = true;
     94    else
     95        warn("Trigger key doesn't exist:" + event + " & " + action);
     96}
     97
     98/** 
     99 * This is an event with more than just the key argument, 
     100 * so we should have a special register function for it.
     101 *
     102 * @param entity Entity id around which the range is checked
     103 * @param data The data is an object containing information for the range query
     104 * Some of the data has sendible defaults (mentionned next to the object)
     105 * data.players = [1,2,3,...] * list of player ids
     106 * data.minRange = 0          * Minimum range for the query
     107 * data.maxRange = -1         * Maximum range for the query (-1 = no maximum)
     108 * data.requiredComponent = 0 * Required component id the entities will have
     109 * data.enabled = false       * If the query is enabled by default
     110 * data.action                * The function to execute on a range update
     111 * data.key                   * The key of this trigger
     112 */
     113Trigger.prototype.RegisterRangeTrigger = function(entity, data)
     114{
     115    var cmpTriggerEntity = Engine.QueryInterface(entity, IID_TriggerEntity);
     116    if (cmpTriggerEntity)
     117    {
     118        var tag = cmpTriggerEntity.RegisterRangeTrigger(data)
     119        if (data.key)
     120            this.rangeTriggers[data.key] = tag;
     121        return tag;
     122    }
     123    warn("Tried to register a range trigger on an entity (" + entity + ") that has no TriggerEntity component.");
     124    return null;
     125}
     126
     127Trigger.prototype.EnableRangeTrigger = function(key)
     128{
     129    if (!this.rangeTriggers[key])
     130        return;
     131    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     132    cmpRangeManager.EnableActiveQuery(this.rangeTriggers[key]);
     133};
     134
     135Trigger.prototype.DisableRangeTrigger = function(key)
     136{
     137    if (!this.rangeTriggers[key])
     138        return;
     139    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     140    cmpRangeManager.DisableActiveQuery(this.rangeTriggers[key]);
     141};
     142
     143/** 
     144 * This is an event with more than just the key argument, 
     145 * so we should have a special register function for it.
     146 *
     147 * @param action Functionname to execute on interval
     148 * @param delay How many miliseconds before the first call?
     149 * @param interval How many miliseconds between each call?
     150 * @param enabled Determines whether if the trigger is active by default
     151 */
     152Trigger.prototype.RegisterOnIntervalTrigger = function(action, delay, interval, enabled)
     153{
     154    // If the trigger was active by default, start the update timer
     155    if (enabled)
     156    {
     157        var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     158        this.eventOnIntervalData[action] = cmpTimer.SetInterval(this.entity, IID_Trigger, "TimerUpdate", delay, interval, {"action" : action});
     159    }
     160    else // If not, save it for future when we activate the trigger
     161        this.eventOnIntervalData[action] = [delay, interval, {"action" : action}];
     162}
     163
     164// Interval triggers are different in nature, and should have their own Enable and disable functions
     165Trigger.prototype.DisableIntervalTrigger = function(action)
     166{
     167    var timer = this.eventOnIntervalData[action];
     168    // Check if it is active
     169    if (typeof (timer) === 'number')
     170    {
     171        var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     172        cmpTimer.CancelTimer(timer);
     173    }
     174}
     175
     176Trigger.prototype.EnableIntervalTrigger = function(action)
     177{
     178    var timer = this.eventOnIntervalData[action];
     179    // Check if it is inactive
     180    if (typeof (timer) !== 'number')
     181    {
     182        var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     183        this.eventOnIntervalData[action] = cmpTimer.SetInterval(this.entity, IID_Trigger, "TimerUpdate", timer[0], timer[1], timer[2]);
     184    }
     185}
     186
     187/**
     188 * This function executes the actions bound to the events
     189 * It's either called directlty from other simulation scripts, 
     190 * or from message listeners in this file
     191 *
     192 * @param event Name of the event (see the list in init)
     193 * @param data Data object that will be passed to the actions
     194 */
     195Trigger.prototype.CallEvent = function(event, data)
     196{
     197    // A special case, when a unit dies, the range event associated with it should also be removed
     198    if (event == "OnEntityKilled")
     199    {
     200        for (var i in this.eventOnEntityKilledActions)
     201        {
     202            if (this.triggerEnabled[[event, i]])    // Check if it is enabled
     203                this[this.eventOnEntityKilledActions[i]](data); // The data for this one is {"killerEntity": killerEntity, "killedEntity": targetEntity}
     204       
     205            // Remove the "OnUnitFromRangeEntity" triggers which have this entity as their argument
     206            for (var j in this.eventOnUnitRangeFromEntityData)
     207                if (this.eventOnUnitRangeFromEntityData[j].entity == data.killedEntity)
     208                    {
     209                        DisableRangeTrigger(j);
     210                        delete this.eventOnUnitRangeFromEntityData[j];
     211                    }
     212        }
     213        return;
     214    }
     215   
     216    var actions = this["event" + event + "Actions"];
     217   
     218    if (actions === undefined)
     219    {
     220        warn("Unknown trigger event:\"" + event + "\".");
     221        return;
     222    }
     223   
     224    for (var i in actions)
     225    {
     226        if (this.triggerEnabled[[event, actions[i]]])
     227            this[this["event" + event + "Actions"][i]](data);
     228    }
     229}
     230
     231// Handles "OnStructureBuilt" event.
     232Trigger.prototype.OnGlobalConstructionFinished = function(msg)
     233{
     234    this.CallEvent("OnStructureBuilt", {"building": msg.newentity}); // The data for this one is {"building": constructedBuilding}
     235}
     236
     237// Handles "OnTrainingFinished" event.
     238Trigger.prototype.OnGlobalTrainingFinished = function(msg)
     239{
     240    this.CallEvent("OnTrainingFinished", msg);
     241    // The data for this one is {"entities": createdEnts,
     242    //                           "owner": cmpOwnership.GetOwner(),
     243    //                           "metadata": metadata}
     244    // See function "SpawnUnits" in ProductionQueue for more details
     245}
     246
     247// Handle "OnInterval" event.
     248Trigger.prototype.TimerUpdate = function(data, lateness)
     249{
     250    this[data.action](lateness);
     251}
     252
     253/**
     254 * Execute a function after a certain delay
     255 * @param time The delay expressed in milleseconds
     256 * @param action Name of the action function
     257 * @param data Data object that will be passed to the action function
     258 */
     259Trigger.prototype.DoAfterDelay = function(miliseconds, action, data)
     260{
     261    var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     262    return cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Trigger, "DoAction", miliseconds, {"action": action, "data": data});
     263}
     264
     265/**
     266 * Called by the trigger listeners to exucute the actual action. Including sanity checks.
     267 */
     268Trigger.prototype.DoAction = function(msg)
     269{
     270    if (this[msg.action])
     271        this[msg.action](msg.data);
     272    else
     273        error("called a trigger action '" + msg.action + "' that wasn't added to the g_TriggersData global");
     274}
     275
     276Engine.RegisterSystemComponentType(IID_Trigger, "Trigger", Trigger);
  • binaries/data/mods/public/simulation/components/TriggerEntity.js

     
     1function TriggerEntity() {};
     2
     3TriggerEntity.prototype.Schema =
     4    "<optional>" +
     5        "<element name='EntityReference'>" +
     6            "<text/>" +
     7        "</element>" +
     8    "</optional>";
     9
     10TriggerEntity.prototype.Init = function()
     11{
     12    if (this.template && this.template.EntityReference)
     13    {
     14        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     15        cmpTrigger.RegisterTriggerEntity(this.template.EntityReference, this.entity);
     16    }
     17    this.rangeTriggers = {};
     18};
     19
     20TriggerEntity.prototype.OnDestroy = function()
     21{
     22    if (this.template && this.template.EntityReference)
     23    {
     24        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     25        cmpTrigger.RemoveRegisteredTriggerEntity(this.template.EntityReference, this.entity);
     26    }
     27};
     28
     29/**
     30 * The data is an object containing information for the range query
     31 * Some of the data has sendible defaults (mentionned next to the object)
     32 * data.players = [1,2,3,...]  * list of player ids
     33 * data.minRange = 0           * Minimum range for the query
     34 * data.maxRange = -1          * Maximum range for the query (-1 = no maximum)
     35 * data.requiredComponent = -1 * Required component id the entities will have
     36 * data.enabled = false        * If the query is enabled by default
     37 * data.action                 * The function to execute on a range update
     38 */
     39TriggerEntity.prototype.RegisterRangeTrigger = function(data)
     40{
     41    if (!data.players)
     42    {
     43        var numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
     44        data.players = [];
     45        for (var i = 0; i < numPlayers; i++)
     46            data.players.push(i);
     47    }
     48    data.minRange = data.minRange || 0;
     49    data.maxRange = data.maxRange || -1;
     50    data.requiredComponent = data.requiredComponent || -1;
     51    data.currentCollection = [];
     52   
     53    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     54    data.tag = cmpRangeManager.CreateActiveQuery(this.entity, data.minRange, data.maxRange, data.players, data.requiredComponent, cmpRangeManager.GetEntityFlagMask("normal"));
     55
     56    this.rangeTriggers[data.tag] = data;
     57    if (data.enabled)
     58        cmpRangeManager.EnableActiveQuery(data.tag);
     59    return data.tag;
     60};
     61
     62TriggerEntity.prototype.OnRangeUpdate = function(msg)
     63{
     64    var trigger = this.rangeTriggers[msg.tag];
     65    if (!trigger)
     66        return;
     67
     68    for (var ent of msg.removed)
     69    {
     70        var index = trigger.currentCollection.indexOf(ent);
     71        if (index > -1)
     72            trigger.currentCollection.splice(index, 1);
     73    }
     74       
     75    for each (var entity in msg.added)
     76        trigger.currentCollection.push(entity);
     77
     78    trigger.added = msg.added;
     79    trigger.removed = msg.removed;
     80    var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     81    if (cmpTrigger[trigger.action])
     82        cmpTrigger[trigger.action](trigger);
     83};
     84
     85
     86Engine.RegisterComponentType(IID_TriggerEntity, "TriggerEntity", TriggerEntity);
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    199199        cmpUnitMotion.MoveToFormationOffset(msg.data.target, msg.data.x, msg.data.z);
    200200
    201201        this.SetNextStateAlwaysEntering("FORMATIONMEMBER.WALKING");
     202       
     203        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     204        cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.FormationWalk", "metadata": msg});
    202205    },
    203206
    204207    // Special orders:
     
    225228            // We are already at the target, or can't move at all
    226229            this.FinishOrder();
    227230        }
     231       
     232        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     233        cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.LeaveFoundation", "metadata": msg});
    228234    },
    229235
    230236    // Individual orders:
     
    247253            this.SetNextState("ANIMAL.IDLE");
    248254        else
    249255            this.SetNextState("INDIVIDUAL.IDLE");
    250 
     256       
     257        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     258        cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Stop", "metadata": msg});
    251259    },
    252260
    253261    "Order.Walk": function(msg) {
     
    258266            return;
    259267        }
    260268
     269        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     270        cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Walk", "metadata": msg});
     271       
    261272        // For packable units:
    262273        // 1. If packed, we can move.
    263274        // 2. If unpacked, we first need to pack, then follow case 1.
     
    284295            return;
    285296        }
    286297
     298        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     299        cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.WalkAndFight", "metadata": msg});
     300       
    287301        // For packable units:
    288302        // 1. If packed, we can move.
    289303        // 2. If unpacked, we first need to pack, then follow case 1.
     
    310324            this.FinishOrder();
    311325            return;
    312326        }
    313 
     327       
     328        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     329        cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.WalkToTarget", "metadata": msg, "target": this.order.data.target});
     330       
    314331        // For packable units:
    315332        // 1. If packed, we can move.
    316333        // 2. If unpacked, we first need to pack, then follow case 1.
     
    369386            this.StopMoving();
    370387            this.SetNextState("INDIVIDUAL.PICKUP.LOADING");
    371388        }
     389       
     390        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     391        cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.PickupUnit", "metadata": msg, "target": this.order.data.target});
    372392    },
    373393
    374394    "Order.Guard": function(msg) {
     
    382402            this.SetNextState("INDIVIDUAL.GUARD.ESCORTING");
    383403        else
    384404            this.SetNextState("INDIVIDUAL.GUARD.GUARDING");
     405       
     406        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     407        cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Guard", "metadata": msg, "target": this.order.data.target});
    385408    },
    386409
    387410    "Order.Flee": function(msg) {
     
    390413        var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    391414        if (cmpUnitMotion.MoveToTargetRange(this.order.data.target, distance, -1))
    392415        {
     416            var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     417            cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Flee", "metadata": msg, "target": this.order.data.target});
     418       
    393419            // We've started fleeing from the given target
    394420            if (this.IsAnimal())
    395421                this.SetNextState("ANIMAL.FLEEING");
     
    402428            this.StopMoving();
    403429            this.FinishOrder();
    404430        }
     431       
     432
    405433    },
    406434
    407435    "Order.Attack": function(msg) {
     
    422450        }
    423451        this.order.data.attackType = type;
    424452
     453        // Call "OnUnitIssuedOrder" event of the triggers.
     454        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     455        cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Attack", "metadata": msg, "target": this.order.data.target});
     456       
    425457        // If we are already at the target, try attacking it from here
    426458        if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType))
    427459        {
     
    506538        // We can't reach the target, and can't move towards it,
    507539        // so abandon this attack order
    508540        this.FinishOrder();
     541       
    509542    },
    510543
    511544    "Order.Heal": function(msg) {
     
    526559        // Check if the target is in range
    527560        if (this.CheckTargetRange(this.order.data.target, IID_Heal))
    528561        {
     562            // Call "OnUnitIssuedOrder" event of the triggers.
     563            var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     564            cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Heal", "metadata": msg, "target": this.order.data.target});
     565       
    529566            this.StopMoving();
    530567            this.SetNextState("INDIVIDUAL.HEAL.HEALING");
    531568            return;
     
    542579        // Try to move within heal range
    543580        if (this.MoveToTargetRange(this.order.data.target, IID_Heal))
    544581        {
     582            // Call "OnUnitIssuedOrder" event of the triggers.
     583            var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     584            cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Heal", "metadata": msg, "target": this.order.data.target});
     585       
    545586            // We've started walking to the given point
    546587            this.SetNextState("INDIVIDUAL.HEAL.APPROACHING");
    547588            return;
     
    599640            this.StopMoving();
    600641            this.SetNextStateAlwaysEntering("INDIVIDUAL.GATHER.GATHERING");
    601642        }
     643       
     644        // Call "OnUnitIssuedOrder" event of the triggers.
     645        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     646        cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Gather", "metadata": msg, "target": this.order.data.target});
    602647    },
    603648
    604649    "Order.GatherNearPosition": function(msg) {
     
    605650        // Move the unit to the position to gather from.
    606651        this.MoveToPoint(this.order.data.x, this.order.data.z);
    607652        this.SetNextState("INDIVIDUAL.GATHER.WALKING");
     653       
     654        // Call "OnUnitIssuedOrder" event of the triggers.
     655        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     656        cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.GatherNearPosition", "metadata": msg});
    608657    },
    609658
    610659    "Order.ReturnResource": function(msg) {
     
    628677        // Try to move to the dropsite
    629678        if (this.MoveToTargetRange(this.order.data.target, IID_ResourceGatherer))
    630679        {
     680            // Call "OnUnitIssuedOrder" event of the triggers.
     681            var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     682            cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.ReturnResource", "metadata": msg, "target": this.order.data.target});
     683       
    631684            // We've started walking to the target
    632685            this.SetNextState("INDIVIDUAL.RETURNRESOURCE.APPROACHING");
    633686            return;
     
    660713        this.waypoints = undefined;
    661714        if (this.MoveToMarket(nextMarket))
    662715        {
     716            // Call "OnUnitIssuedOrder" event of the triggers.
     717            var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     718            cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Trade", "metadata": msg, "target": this.order.data.target});
     719       
    663720            // We've started walking to the next market
    664721            this.SetNextState(state);
    665722        }
     
    682739            this.StopMoving();
    683740            this.SetNextState("INDIVIDUAL.REPAIR.REPAIRING");
    684741        }
     742       
     743        // Call "OnUnitIssuedOrder" event of the triggers.
     744        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     745        cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Repair", "metadata": msg, "target": this.order.data.target});
    685746    },
    686747
    687748    "Order.Garrison": function(msg) {
     749        // Call "OnUnitIssuedOrder" event of the triggers.
     750        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     751        cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Garrison", "metadata": msg, "target": this.order.data.target});
     752   
    688753        // For packable units:
    689754        // 1. If packed, we can move to the garrison target.
    690755        // 2. If unpacked, we first need to pack, then follow case 1.
     
    709774
    710775    "Order.Autogarrison": function(msg) {
    711776        this.SetNextState("INDIVIDUAL.AUTOGARRISON");
     777       
     778        // Call "OnUnitIssuedOrder" event of the triggers.
     779        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     780        cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Autogarrison", "metadata": msg});
    712781    },
    713782
    714783    "Order.Alert": function(msg) {
     
    722791            this.ReplaceOrder("Garrison", {"target": this.alertGarrisoningTarget});
    723792        else
    724793            this.FinishOrder();
     794       
     795        // Call "OnUnitIssuedOrder" event of the triggers.
     796        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     797        cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Alert", "metadata": msg});
    725798    }, 
    726799
    727800    "Order.Cheering": function(msg) {
    728801        this.SetNextState("INDIVIDUAL.CHEERING");
     802       
     803        // Call "OnUnitIssuedOrder" event of the triggers.
     804        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     805        cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Cheering", "metadata": msg});
    729806    },
    730807
    731808    "Order.Pack": function(msg) {
     
    733810        {
    734811            this.StopMoving();
    735812            this.SetNextState("INDIVIDUAL.PACKING");
     813           
     814            // Call "OnUnitIssuedOrder" event of the triggers.
     815            var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     816            cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Pack", "metadata": msg});
    736817        }
    737818    },
    738819
     
    741822        {
    742823            this.StopMoving();
    743824            this.SetNextState("INDIVIDUAL.UNPACKING");
     825           
     826            // Call "OnUnitIssuedOrder" event of the triggers.
     827            var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     828            cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Unpack", "metadata": msg});
    744829        }
    745830    },
    746831
     
    749834        if (cmpPack && cmpPack.IsPacking() && !cmpPack.IsPacked())
    750835            cmpPack.CancelPack();
    751836        this.FinishOrder();
     837       
     838        // Call "OnUnitIssuedOrder" event of the triggers.
     839        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     840        cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.CancelPack", "metadata": msg});
    752841    },
    753842
    754843    "Order.CancelUnpack": function(msg) {
     
    756845        if (cmpPack && cmpPack.IsPacking() && cmpPack.IsPacked())
    757846            cmpPack.CancelPack();
    758847        this.FinishOrder();
     848       
     849        // Call "OnUnitIssuedOrder" event of the triggers.
     850        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     851        cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.CancelUnpack", "metadata": msg});
    759852    },
    760853
    761854    // States for the special entity representing a group of units moving in formation:
     
    820913            var cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI);
    821914            if (cmpTargetUnitAI && cmpTargetUnitAI.IsFormationMember())
    822915                target = cmpTargetUnitAI.GetFormationController();
    823 
     916           
    824917            var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
    825918            // Check if we are already in range, otherwise walk there
    826919            if (!this.CheckTargetAttackRange(target, target))
     
    849942                this.FinishOrder();
    850943                return;
    851944            }
     945           
    852946            // Check if we are already in range, otherwise walk there
    853947            if (!this.CheckGarrisonRange(msg.data.target))
    854948            {
     
    9261020            }
    9271021
    9281022            this.CallMemberFunction("GatherNearPosition", [msg.data.x, msg.data.z, msg.data.type, msg.data.template, false]);
    929 
     1023           
    9301024            this.SetNextStateAlwaysEntering("MEMBER");
    9311025        },
    9321026
     
    9451039            }
    9461040
    9471041            this.CallMemberFunction("Heal", [msg.data.target, false]);
    948 
     1042           
    9491043            this.SetNextStateAlwaysEntering("MEMBER");
    9501044        },
    9511045
     
    12191313
    12201314            // Stop moving as soon as the formation disbands
    12211315            this.StopMoving();
    1222 
     1316           
    12231317            // If the controller handled an order but some members rejected it,
    12241318            // they will have no orders and be in the FORMATIONMEMBER.IDLE state.
    12251319            if (this.orderQueue.length)
  • binaries/data/mods/public/simulation/components/interfaces/Trigger.js

     
     1Engine.RegisterInterface("Trigger");
  • binaries/data/mods/public/simulation/components/interfaces/TriggerEntity.js

     
     1Engine.RegisterInterface("TriggerEntity");
     2
  • binaries/data/mods/public/simulation/helpers/Commands.js

     
    8888        break;
    8989
    9090    case "walk":
     91        // Handle OnUnitsIssuedOrder event of the triggers
     92        if (entities.length > 1)
     93        {
     94            var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     95            cmpTrigger.CallEvent("OnUnitsIssuedOrder", {"entities": entities, "order": "Order.Walk", "metadata": {"x": cmd.x, "z": cmd.z, "queued": cmd.queued}});
     96        }
     97       
    9198        GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
    9299            cmpUnitAI.Walk(cmd.x, cmd.z, cmd.queued);
    93100        });
     
    94101        break;
    95102
    96103    case "attack-walk":
     104        // Handle OnUnitsIssuedOrder event of the triggers
     105        if (entities.length > 1)
     106        {
     107            var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     108            cmpTrigger.CallEvent("OnUnitsIssuedOrder", {"entities": entities, "order": "Order.WalkAndFight", "metadata": {"x": cmd.x, "z": cmd.z, "queued": cmd.queued}});
     109        }
     110       
    97111        GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
    98112            cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.queued);
    99113        });
     
    105119            // This check is for debugging only!
    106120            warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd));
    107121        }
    108 
     122       
     123        // Handle OnUnitsIssuedOrder event of the triggers
     124        if (entities.length > 1)
     125        {
     126            var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     127            cmpTrigger.CallEvent("OnUnitsIssuedOrder", {"entities": entities, "order": "Order.Attack", "metadata": {"target": cmd.target, "queued": cmd.queued}});
     128        }
     129       
    109130        // See UnitAI.CanAttack for target checks
    110131        GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
    111132            cmpUnitAI.Attack(cmd.target, cmd.queued);
     
    119140            warn("Invalid command: heal target is not owned by player "+player+" or their ally: "+uneval(cmd));
    120141        }
    121142
     143        // Handle OnUnitsIssuedOrder event of the triggers
     144        if (entities.length > 1)
     145        {
     146            var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     147            cmpTrigger.CallEvent("OnUnitsIssuedOrder", {"entities": entities, "order": "Order.Heal", "metadata": {"target": cmd.target, "queued": cmd.queued}});
     148        }
     149       
    122150        // See UnitAI.CanHeal for target checks
    123151        GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
    124152            cmpUnitAI.Heal(cmd.target, cmd.queued);
     
    133161            warn("Invalid command: repair target is not owned by ally of player "+player+": "+uneval(cmd));
    134162        }
    135163
     164        // Handle OnUnitsIssuedOrder event of the triggers
     165        if (entities.length > 1)
     166        {
     167            var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     168            cmpTrigger.CallEvent("OnUnitsIssuedOrder", {"entities": entities, "order": "Order.Repair", "metadata": {"target": cmd.target, "autocontinue": cmd.autocontinue, "queued": cmd.queued}});
     169        }
     170       
    136171        // See UnitAI.CanRepair for target checks
    137172        GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
    138173            cmpUnitAI.Repair(cmd.target, cmd.autocontinue, cmd.queued);
     
    146181            warn("Invalid command: resource is not owned by gaia or player "+player+": "+uneval(cmd));
    147182        }
    148183
     184        // Handle OnUnitsIssuedOrder event of the triggers
     185        if (entities.length > 1)
     186        {
     187            var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     188            cmpTrigger.CallEvent("OnUnitsIssuedOrder", {"entities": entities, "order": "Order.Gather", "metadata": {"target": cmd.target, "queued": cmd.queued}});
     189        }
     190       
    149191        // See UnitAI.CanGather for target checks
    150192        GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
    151193            cmpUnitAI.Gather(cmd.target, cmd.queued);
     
    153195        break;
    154196       
    155197    case "gather-near-position":
     198        // Handle OnUnitsIssuedOrder event of the triggers
     199        if (entities.length > 1)
     200        {
     201            var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     202            cmpTrigger.CallEvent("OnUnitsIssuedOrder", {"entities": entities, "order": "Order.GatherNearPosition", "metadata": {"x": cmd.x, "z": cmd.z, "type": cmd.resourceType, "template": cmd.resourceTemplate, "queued": cmd.queued}});
     203        }
     204   
    156205        GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
    157206            cmpUnitAI.GatherNearPosition(cmd.x, cmd.z, cmd.resourceType, cmd.resourceTemplate, cmd.queued);
    158207        });
     
    166215            warn("Invalid command: dropsite is not owned by player "+player+": "+uneval(cmd));
    167216        }
    168217
     218        // Handle OnUnitsIssuedOrder event of the triggers
     219        if (entities.length > 1)
     220        {
     221            var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     222            cmpTrigger.CallEvent("OnUnitsIssuedOrder", {"entities": entities, "order": "Order.ReturnResource", "metadata": {"target": cmd.target, "queued": cmd.queued}});
     223        }
     224       
    169225        // See UnitAI.CanReturnResource for target checks
    170226        GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
    171227            cmpUnitAI.ReturnResource(cmd.target, cmd.queued);
     
    329385        // Verify that the building can be controlled by the player or is mutualAlly
    330386        if (CanControlUnitOrIsAlly(cmd.target, player, controlAllUnits))
    331387        {
     388           
     389            // Handle OnUnitsIssuedOrder event of the triggers
     390            if (entities.length > 1)
     391            {
     392                var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     393                cmpTrigger.CallEvent("OnUnitsIssuedOrder", {"entities": entities, "order": "Order.Garrison", "metadata": {"target": cmd.target, "queued": cmd.queued}});
     394            }
     395       
    332396            GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
    333397                cmpUnitAI.Garrison(cmd.target, cmd.queued);
    334398            });
  • binaries/data/mods/public/simulation/helpers/Damage.js

     
    7474    // Damage the target
    7575    var targetState = cmpDamageReceiver.TakeDamage(data.strengths.hack * data.multiplier, data.strengths.pierce * data.multiplier, data.strengths.crush * data.multiplier, data.attacker);
    7676
     77    // Call the related trigger event
     78    // We have to call the event instead of listening to it by messages because the message is sent
     79    // after the target gets killed, which is not our intended case
     80    var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     81    cmpTrigger.CallEvent("OnEntityTookDamage", {"attackerEntity": data.attacker, "attackedEntity": data.target, "type":data.type, "ammount":-targetState.change});
     82   
    7783    // If the target was killed run some cleanup
    7884    if (targetState.killed)
    7985        Damage.TargetKilled(data.attacker, data.target);
     
    110116    // Call RangeManager with dummy entity and return the result.
    111117    var rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    112118    var rangeQuery = rangeManager.ExecuteQueryAroundPos(origin, 0, radius, players, IID_DamageReceiver);
     119   
    113120    return rangeQuery;
    114121};
    115122
     
    133140    var cmpLooter = Engine.QueryInterface(killerEntity, IID_Looter);
    134141    if (cmpLooter)
    135142        cmpLooter.Collect(targetEntity);
     143       
     144    // Call the related trigger event
     145    var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     146    cmpTrigger.CallEvent("OnEntityKilled", {"killerEntity": killerEntity, "killedEntity": targetEntity});
    136147};
    137148
    138149Engine.RegisterGlobal("Damage", Damage);
  • binaries/data/mods/public/simulation/helpers/TriggerHelpers.js

     
     1// Contains standardized functions suitable for using in trigger scripts.
     2// Do not use them in any other simulation script.
     3
     4/**
     5 * A function to get the owner of an entity.
     6 * Returns the ID of a player. Returns 0 if the owner is Gaia.
     7 */
     8function GetEntityOwner(entity)
     9{
     10    return Engine.QueryInterface(entity, IID_Ownership).GetOwner()
     11}
     12
     13/**
     14 * A function to get the generic name of an entity
     15 */
     16function GetEntityGenericName(entity)
     17{
     18    var cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
     19    if (!cmpIdentity)
     20        return undefined;
     21    return cmpIdentity.GetGenericName();
     22}
     23
     24/**
     25 * A function to get a list of the classes of an entity
     26 */
     27function GetEntityClassesList(entity)
     28{
     29    var cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
     30    if (!cmpIdentity)
     31        return undefined;
     32    return cmpIdentity.GetClassesList();
     33}
     34
     35/**
     36 * A function to determine if an entity has a specific class
     37 */
     38function EntityHasClass(entity, classname)
     39{
     40    var classes = GetEntityClassesList(entity);
     41    return (classes && classes.indexOf(classname) != -1);
     42}
     43
     44/**
     45 * Returns the entity id of a player based on the id of the player
     46 * Useful when one wants to change a player's properties.
     47 */
     48function GetPlayerEntityByID(id)
     49{
     50    var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     51    return cmpPlayerMan.GetPlayerByID(id);
     52}
     53
     54/**
     55 * Can be used to "force" a building to spawn a group of entities.
     56 * Only works for buildings that can already train units.
     57 */
     58function BuildingSpawnUnits(entity, template, count)
     59{
     60    var cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue);
     61    if (!cmpProductionQueue)
     62        return;
     63    cmpProductionQueue.SpawnUnits(template, count, null);
     64}
     65
     66/**
     67 * Shows a message in the top center of the screen
     68 */
     69function PushGUINotificationInTopCenter(player, message)
     70{
     71    var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     72    cmpGUIInterface.PushNotification({"player": player, "message": message});
     73}
     74
     75/**
     76 * Returns the resource type that can be gathered from an entity
     77 */
     78function GetEntityResourceType(entity)
     79{
     80    var cmpResourceSupply = Engine.QueryInterface(entity, IID_ResourceSupply);
     81    if (!cmpResourceSupply)
     82        return undefined;
     83    return cmpResourceSupply.GetType();
     84}
     85
     86/**
     87 * Returns a list of entities owned by the player
     88 */
     89function GetEntitiesByPlayer(playerID)
     90{
     91    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     92    return cmpRangeManager.GetEntitiesByPlayer(playerID);
     93}
     94
     95/**
     96 * Wins the game for a player
     97 */
     98function SetPlayerWon(playerID)
     99{
     100    var playerEnt = GetPlayerEntityByID(playerID);
     101    var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
     102    cmpPlayer.SetState("won")
     103}
     104
     105/**
     106 * Defeats a player
     107 */
     108function DefeatPlayer(playerID)
     109{
     110    var playerEnt = GetPlayerEntityByID(playerID);
     111    Engine.PostMessage(playerEnt, MT_PlayerDefeated, { "playerId": playerID } );
     112}
     113
     114/**
     115 * Returns the system trigger component.
     116 */
     117function GetTriggerComponent()
     118{
     119    return Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     120}
     121
     122Engine.RegisterGlobal("GetEntityOwner", GetEntityOwner);
     123Engine.RegisterGlobal("GetEntityGenericName", GetEntityGenericName);
     124Engine.RegisterGlobal("GetEntityClassesList", GetEntityClassesList);
     125Engine.RegisterGlobal("EntityHasClass", EntityHasClass);
     126Engine.RegisterGlobal("GetPlayerEntityByID", GetPlayerEntityByID);
     127Engine.RegisterGlobal("BuildingSpawnUnits", BuildingSpawnUnits);
     128Engine.RegisterGlobal("PushGUINotificationInTopCenter", PushGUINotificationInTopCenter);
     129Engine.RegisterGlobal("GetEntityResourceType", GetEntityResourceType);
     130Engine.RegisterGlobal("GetEntitiesByPlayer", GetEntitiesByPlayer);
     131Engine.RegisterGlobal("SetPlayerWon", SetPlayerWon);
     132Engine.RegisterGlobal("DefeatPlayer", DefeatPlayer);
     133Engine.RegisterGlobal("GetTriggerComponent", GetTriggerComponent);
     134 No newline at end of file
  • binaries/data/mods/public/simulation/templates/template_structure.xml

     
    9797    <BarHeight>0.6</BarHeight>
    9898    <HeightOffset>12.0</HeightOffset>
    9999  </StatusBars>
     100  <TriggerEntity/>
    100101  <TerritoryDecay>
    101102    <HealthDecayRate>5</HealthDecayRate>
    102103  </TerritoryDecay>
  • binaries/data/mods/public/simulation/templates/template_unit.xml

     
    9393    <BarHeight>0.333</BarHeight>
    9494    <HeightOffset>5.0</HeightOffset>
    9595  </StatusBars>
     96  <TriggerEntity/>
    9697  <UnitAI>
    9798    <AlertReactiveLevel>2</AlertReactiveLevel>
    9899    <DefaultStance>aggressive</DefaultStance>
  • source/simulation2/Simulation2.cpp

     
    702702
    703703    if (!m->m_StartupScript.empty())
    704704        GetScriptInterface().LoadScript(L"map startup script", m->m_StartupScript);
     705   
     706    // Load the trigger script after we have loaded the simulation and the map.
     707    if (GetScriptInterface().HasProperty(m->m_MapSettings.get(), "TriggerScript"))
     708    {
     709        std::string script_name;
     710        GetScriptInterface().GetProperty(m->m_MapSettings.get(), "TriggerScript", script_name);
     711
     712        script_name = "maps/" + script_name;
     713        m->m_ComponentManager.LoadScript(script_name.data());
     714    }
    705715}
    706716
    707717int CSimulation2::ProgressiveLoad()