| 1 | function Trigger() {} |
| 2 | |
| 3 | Trigger.prototype.Schema = |
| 4 | "<a:component type='system'/><empty/>"; |
| 5 | |
| 6 | Trigger.prototype.Init = function() |
| 7 | { |
| 8 | // Each event has its own set of actions determined by the player. |
| 9 | this.eventOnEntityTookDamageActions = {}; |
| 10 | this.eventOnEntityKilledActions = {}; |
| 11 | this.eventOnStructureBuiltActions = {}; |
| 12 | this.eventOnConstructionStartedActions = {}; |
| 13 | this.eventOnTrainingFinishedActions = {}; |
| 14 | this.eventOnTrainingQueuedActions = {}; |
| 15 | this.eventOnResearchFinishedActions = {}; |
| 16 | this.eventOnResearchQueuedActions = {}; |
| 17 | this.eventAlwaysActions = {}; |
| 18 | this.eventOnUnitRangeFromEntityData = {}; |
| 19 | |
| 20 | // We are going to have all of the triggers here to be able to enable/disbale them in runtime. |
| 21 | this.triggerEnabled = {}; |
| 22 | |
| 23 | |
| 24 | }; |
| 25 | |
| 26 | Trigger.prototype.StartUpdateTimer = function(interval) |
| 27 | { |
| 28 | // Stop the last timer before starting a new one |
| 29 | if (this.timer !== undefined) |
| 30 | this.StopUpdateTimer(); |
| 31 | |
| 32 | // Start the update timer |
| 33 | var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); |
| 34 | this.timer = cmpTimer.SetInterval(this.entity, IID_Trigger, "TimerUpdate", 0, interval, undefined); |
| 35 | } |
| 36 | |
| 37 | Trigger.prototype.StopUpdateTimer = function() |
| 38 | { |
| 39 | // Call the update timer |
| 40 | if (this.timer !== undefined) |
| 41 | { |
| 42 | var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); |
| 43 | cmpTimer.CancelTimer(this.timer); |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | // Binds the "action" function to one of the implemented events. "name" is a string for further access |
| 48 | // to the trigger for enabling/disabling. |
| 49 | Trigger.prototype.RegisterTrigger = function(name, event, action, enabled) |
| 50 | { |
| 51 | enabled = (enabled !== undefined ? enabled : true); |
| 52 | var validEvent = true; |
| 53 | switch (event) |
| 54 | { |
| 55 | case "OnEntityTookDamage": |
| 56 | this.eventOnEntityTookDamageActions[name] = action; |
| 57 | break; |
| 58 | case "OnEntityKilled": |
| 59 | this.eventOnEntityKilledActions[name] = action; |
| 60 | break; |
| 61 | case "OnStructureBuilt": |
| 62 | this.eventOnStructureBuiltActions[name] = action; |
| 63 | break; |
| 64 | case "OnConstructionStarted": |
| 65 | this.eventOnConstructionStartedActions[name] = action; |
| 66 | break; |
| 67 | case "OnTrainingFinished": |
| 68 | this.eventOnTrainingFinishedActions[name] = action; |
| 69 | break; |
| 70 | case "OnTrainingQueued": |
| 71 | this.eventOnTrainingQueuedActions[name] = action; |
| 72 | break; |
| 73 | case "OnResearchFinished": |
| 74 | this.eventOnResearchFinishedActions[name] = action; |
| 75 | break; |
| 76 | case "OnResearchQueued": |
| 77 | this.eventOnResearchQueuedActions[name] = action; |
| 78 | break; |
| 79 | case "Always": |
| 80 | this.eventAlwaysActions[name] = action; |
| 81 | break; |
| 82 | default: |
| 83 | validEvent = false; |
| 84 | break; |
| 85 | } |
| 86 | if (validEvent) |
| 87 | this.triggerEnabled[name] = enabled |
| 88 | else |
| 89 | error("Invalid trigger event \"" + event + "\"."); |
| 90 | }; |
| 91 | |
| 92 | // This is a performance taxing event to check for, so we should have a special register function for it. |
| 93 | Trigger.prototype.RegisterOnUnitRangeFromEntityTrigger = function(event, name, entity, radius, action, players, enabled) |
| 94 | { |
| 95 | |
| 96 | enabled = (enabled !== undefined ? enabled : true); |
| 97 | |
| 98 | // If the players parameter is not specified use all players. |
| 99 | if (!players) |
| 100 | { |
| 101 | var playerEntities = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayerEntities(); |
| 102 | players = []; |
| 103 | for each (var pentity in playerEntities) |
| 104 | players.push(Engine.QueryInterface(pentity, IID_Player).GetPlayerID()); |
| 105 | } |
| 106 | |
| 107 | // To determine if a unit has "entered" or "left" the range, we should know if it was in the range before. We have "currentCollection" for this purpose. |
| 108 | // For the purpose of finding out the intersection of the previous and the current rangeQuery faster, we are sorting the current one. |
| 109 | this.eventOnUnitRangeFromEntityData[name] = {"event": event, "action": action, "entity": entity, "radius": radius, "currentCollection": [], "players": players}; |
| 110 | this.triggerEnabled[name] = enabled; |
| 111 | //log(uneval(this.eventOnUnitRangeFromEntityData)); |
| 112 | } |
| 113 | |
| 114 | // Disable trigger |
| 115 | Trigger.prototype.DisableTrigger = function(name) |
| 116 | { |
| 117 | if (this.triggerEnabled[name] !== undefined) |
| 118 | this.triggerEnabled[name] = false; |
| 119 | else |
| 120 | warn("Trigger doesn't exist:" + name); |
| 121 | } |
| 122 | |
| 123 | // Enable trigger |
| 124 | Trigger.prototype.EnableTrigger = function(name) |
| 125 | { |
| 126 | if (this.triggerEnabled[name] !== undefined) |
| 127 | this.triggerEnabled[name] = true; |
| 128 | else |
| 129 | warn("Trigger doesn't exist:" + name); |
| 130 | } |
| 131 | |
| 132 | // To handle the events that cannot be tracked by messages, this function should be called in the simulation code. |
| 133 | Trigger.prototype.CallEvent = function(event, data) |
| 134 | { |
| 135 | switch (event) |
| 136 | { |
| 137 | case "OnEntityTookDamage": |
| 138 | for (var i in this.eventOnEntityTookDamageActions) |
| 139 | if (this.triggerEnabled[i]) // Check if it is enabled |
| 140 | this.eventOnEntityTookDamageActions[i](data); // The data for this one is {"attackerEntity": data.attacker, |
| 141 | //"attackedEntity": data.target, "type":data.type, "ammount":-targetState.change} |
| 142 | break; |
| 143 | case "OnTrainingQueued": |
| 144 | for (var i in this.eventOnTrainingQueuedActions) |
| 145 | if (this.triggerEnabled[i]) // Check if it is enabled |
| 146 | this.eventOnTrainingQueuedActions[i](data); // The data for this one is {"playerid": playerid, "unitTemplate": templateName, "count": count, "metadata": metadata, "trainerEntity": this.entity} |
| 147 | break; |
| 148 | case "OnEntityKilled": |
| 149 | for (var i in this.eventOnEntityKilledActions) |
| 150 | { |
| 151 | if (this.triggerEnabled[i]) // Check if it is enabled |
| 152 | this.eventOnEntityKilledActions[i](data); // The data for this one is {"killerEntity": killerEntity, "killedEntity": targetEntity} |
| 153 | |
| 154 | // Remove the "OnUnitFromRangeEntity" triggers which have this entity as their argument |
| 155 | for (var j in this.eventOnUnitRangeFromEntityData) |
| 156 | { |
| 157 | if (this.eventOnUnitRangeFromEntityData[j].entity == data.killedEntity) |
| 158 | { |
| 159 | delete this.eventOnUnitRangeFromEntityData[j]; |
| 160 | delete this.triggerEnabled[j]; |
| 161 | } |
| 162 | } |
| 163 | } |
| 164 | break; |
| 165 | case "OnConstructionStarted": |
| 166 | for (var i in this.eventOnConstructionStartedActions) |
| 167 | if (this.triggerEnabled[i]) // Check if it is enabled |
| 168 | this.eventOnConstructionStartedActions[i](data); // The data for this one is {"foundation": entity, "template": templateName} |
| 169 | break; |
| 170 | case "OnResearchQueued": |
| 171 | for (var i in this.eventOnResearchQueuedActions) |
| 172 | if (this.triggerEnabled[i]) // Check if it is enabled |
| 173 | this.eventOnResearchQueuedActions[i](data); // The data for this one is {"playerid": cmpPlayer.GetPlayerID(), "technologyTemplate": templateName, "researcherEntity": this.entity} |
| 174 | break; |
| 175 | case "OnResearchFinished": |
| 176 | for (var i in this.eventOnResearchFinishedActions) |
| 177 | if (this.triggerEnabled[i]) // Check if it is enabled |
| 178 | this.eventOnResearchFinishedActions[i](data); // The data for this one is {"playerid": cmpPlayer.GetPlayerID(), "technologyTemplate": templateName, "researcherEntity": this.entity} |
| 179 | break; |
| 180 | default: |
| 181 | warn("Invalid trigger event \"" + event + "\" called."); |
| 182 | break; |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | // Handles "OnStructureBuilt" event. |
| 187 | Trigger.prototype.OnGlobalConstructionFinished = function(msg) |
| 188 | { |
| 189 | for (var i in this.eventOnStructureBuiltActions) |
| 190 | if (this.triggerEnabled[i]) // Check if it is enabled |
| 191 | this.eventOnStructureBuiltActions[i]({"building": msg.newentity}); // The data for this one is {"building": constructedBuilding} |
| 192 | } |
| 193 | |
| 194 | // Handles "OnTrainingFinished" event. |
| 195 | Trigger.prototype.OnGlobalTrainingFinished = function(msg) |
| 196 | { |
| 197 | for (var i in this.eventOnTrainingFinishedActions) |
| 198 | if (this.triggerEnabled[i]) // Check if it is enabled |
| 199 | this.eventOnTrainingFinishedActions[i](msg); // The data for this one is {"entities": createdEnts, |
| 200 | // "owner": cmpOwnership.GetOwner(), |
| 201 | // "metadata": metadata,} |
| 202 | // See function "SpawnUnits" in ProductionQueue for more details |
| 203 | } |
| 204 | |
| 205 | // Handles an event that occurs by the interval the map designer specifies |
| 206 | // Also handles "OnUnitRangeFromEntity" |
| 207 | Trigger.prototype.TimerUpdate = function(data, lateness) |
| 208 | { |
| 209 | // Handle "Always" event. |
| 210 | for (var i in this.eventAlwaysActions) |
| 211 | if (this.triggerEnabled[i]) // Check if it is enabled |
| 212 | this.eventAlwaysActions[i](); // This function event has no data |
| 213 | |
| 214 | // OnUnitRangeFromEntity handler |
| 215 | for (var i in this.eventOnUnitRangeFromEntityData) |
| 216 | { |
| 217 | if (this.triggerEnabled[i]) // Check if it is enabled |
| 218 | { |
| 219 | var players = this.eventOnUnitRangeFromEntityData[i].players; |
| 220 | if (!players) |
| 221 | { |
| 222 | var playerEntities = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayerEntities(); |
| 223 | players = []; |
| 224 | for each (var entity in playerEntities) |
| 225 | players.push(Engine.QueryInterface(entity, IID_Player).GetPlayerID()); |
| 226 | } |
| 227 | |
| 228 | var rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); |
| 229 | |
| 230 | var rangeQuery = rangeManager.ExecuteQuery(this.eventOnUnitRangeFromEntityData[i].entity, 0, this.eventOnUnitRangeFromEntityData[i].radius, players, 0); |
| 231 | var previousQuery = this.eventOnUnitRangeFromEntityData[i].currentCollection; |
| 232 | |
| 233 | // To find the entities that have entered and left the range: |
| 234 | var entitiesMovingInside = []; |
| 235 | var entitiesEntered = []; |
| 236 | var entitiesLeft = []; |
| 237 | |
| 238 | var isMovingInsideEvent = false, isEnteredEvent = false, isLeftEvent = false; |
| 239 | if (this.eventOnUnitRangeFromEntityData[i].event == "moved") |
| 240 | isMovingInsideEvent = true; |
| 241 | else if (this.eventOnUnitRangeFromEntityData[i].event == "entered") |
| 242 | isEnteredEvent = true; |
| 243 | else |
| 244 | isLeftEvent = true; |
| 245 | |
| 246 | rangeQuery.sort(); |
| 247 | var m = 0, n = 0; |
| 248 | |
| 249 | // Find the entities that entered, the ones that left, and the ones that are moving inside the range |
| 250 | while (m < previousQuery.length && n < rangeQuery.length) |
| 251 | { |
| 252 | if (previousQuery[m] == rangeQuery[n]) |
| 253 | { |
| 254 | if (isMovingInsideEvent) |
| 255 | { |
| 256 | var cmpUnitMotion = Engine.QueryInterface(previousQuery[m], IID_UnitMotion); |
| 257 | if (cmpUnitMotion && cmpUnitMotion.IsMoving()) |
| 258 | entitiesMovingInside.push(previousQuery[m]); |
| 259 | } |
| 260 | ++m; |
| 261 | ++n; |
| 262 | } |
| 263 | else if (previousQuery[m] > rangeQuery[n]) |
| 264 | { |
| 265 | if (isEnteredEvent) |
| 266 | entitiesEntered.push(rangeQuery[n]); |
| 267 | ++n; |
| 268 | } |
| 269 | else |
| 270 | { |
| 271 | if (isLeftEvent) |
| 272 | entitiesLeft.push(previousQuery[m]); |
| 273 | ++m; |
| 274 | } |
| 275 | } |
| 276 | if (isLeftEvent) |
| 277 | while (m < previousQuery.length) |
| 278 | { |
| 279 | entitiesLeft.push(previousQuery[m]); |
| 280 | ++m; |
| 281 | } |
| 282 | else if (isEnteredEvent) |
| 283 | while (n < rangeQuery.length) |
| 284 | { |
| 285 | entitiesEntered.push(rangeQuery[n]); |
| 286 | ++n; |
| 287 | } |
| 288 | |
| 289 | // Now do the actions for all of the entities that are calling those events |
| 290 | if (isMovingInsideEvent) |
| 291 | for (var m = 0; m < entitiesMovingInside.length; ++m) |
| 292 | this.eventOnUnitRangeFromEntityData[i].action({"entity": entitiesMovingInside[m]}); |
| 293 | else if (isEnteredEvent) |
| 294 | for (var m = 0; m < entitiesEntered.length; ++m) |
| 295 | this.eventOnUnitRangeFromEntityData[i].action({"entity": entitiesEntered[m]}); |
| 296 | else |
| 297 | for (var m = 0; m < entitiesLeft.length; ++m) |
| 298 | this.eventOnUnitRangeFromEntityData[i].action({"entity": entitiesLeft[m]}); |
| 299 | |
| 300 | this.eventOnUnitRangeFromEntityData[i].currentCollection = rangeQuery; |
| 301 | } |
| 302 | } |
| 303 | } |
| 304 | |
| 305 | Engine.RegisterComponentType(IID_Trigger, "Trigger", Trigger); |