Index: binaries/data/mods/public/simulation/components/BuildingAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/BuildingAI.js (revision 14710)
+++ binaries/data/mods/public/simulation/components/BuildingAI.js (working copy)
@@ -146,7 +146,6 @@
*/
BuildingAI.prototype.OnRangeUpdate = function(msg)
{
-
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
if (!cmpAttack)
return;
Index: binaries/data/mods/public/simulation/components/Foundation.js
===================================================================
--- binaries/data/mods/public/simulation/components/Foundation.js (revision 14710)
+++ binaries/data/mods/public/simulation/components/Foundation.js (working copy)
@@ -202,6 +202,10 @@
// (via CCmpTemplateManager). Now we need to remove that temporary
// blocker-disabling, so that we'll perform standard unit blocking instead.
cmpObstruction.SetDisableBlockMovementPathfinding(false, false, -1);
+
+ // Call the related trigger event
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnConstructionStarted", {"foundation": this.entity, "template": this.finalTemplateName});
}
// Switch foundation to scaffold variant
Index: binaries/data/mods/public/simulation/components/interfaces/Trigger.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/Trigger.js (revision 0)
+++ binaries/data/mods/public/simulation/components/interfaces/Trigger.js (working copy)
@@ -0,0 +1 @@
+Engine.RegisterInterface("Trigger");
Index: binaries/data/mods/public/simulation/components/ProductionQueue.js
===================================================================
--- binaries/data/mods/public/simulation/components/ProductionQueue.js (revision 14710)
+++ binaries/data/mods/public/simulation/components/ProductionQueue.js (working copy)
@@ -288,6 +288,10 @@
"timeTotal": time*1000,
"timeRemaining": time*1000,
});
+
+ // Call the related trigger event
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnTrainingQueued", {"playerid": cmpPlayer.GetPlayerID(), "unitTemplate": templateName, "count": count, "metadata": metadata, "trainerEntity": this.entity});
}
else if (type == "technology")
{
@@ -324,6 +328,10 @@
"timeTotal": time*1000,
"timeRemaining": time*1000,
});
+
+ // Call the related trigger event
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnResearchQueued", {"playerid": cmpPlayer.GetPlayerID(), "technologyTemplate": templateName, "researcherEntity": this.entity});
}
else
{
Index: binaries/data/mods/public/simulation/components/ResourceTrickle.js
===================================================================
--- binaries/data/mods/public/simulation/components/ResourceTrickle.js (revision 14711)
+++ binaries/data/mods/public/simulation/components/ResourceTrickle.js (working copy)
@@ -57,7 +57,6 @@
if (cmpPlayer)
for (var resource in rates)
cmpPlayer.AddResource(resource, rates[resource]);
-
};
Engine.RegisterComponentType(IID_ResourceTrickle, "ResourceTrickle", ResourceTrickle);
Index: binaries/data/mods/public/simulation/components/TechnologyManager.js
===================================================================
--- binaries/data/mods/public/simulation/components/TechnologyManager.js (revision 14710)
+++ binaries/data/mods/public/simulation/components/TechnologyManager.js (working copy)
@@ -283,6 +283,10 @@
return;
}
+ // Call the related trigger event
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnResearchFinished", {"researcher": this.entity, "tech": tech});
+
var modifiedComponents = {};
this.researchedTechs[tech] = template;
// store the modifications in an easy to access structure
Index: binaries/data/mods/public/simulation/components/Trigger.js
===================================================================
--- binaries/data/mods/public/simulation/components/Trigger.js (revision 0)
+++ binaries/data/mods/public/simulation/components/Trigger.js (working copy)
@@ -0,0 +1,310 @@
+function Trigger() {};
+
+Trigger.prototype.Schema =
+ "";
+
+Trigger.prototype.Init = function()
+{
+ var eventNames = [
+ "OnEntityTookDamage",
+ "OnEntityKilled",
+ "OnStructureBuilt",
+ "OnConstructionStarted",
+ "OnTrainingFinished",
+ "OnTrainingQueued",
+ "OnResearchFinished",
+ "OnResearchQueued",
+ "OnUnitIssuedOrder",
+ "OnUnitsIssuedOrder"
+ ];
+
+ // Each event has its own set of actions determined by the map maker.
+ for each (var eventName in eventNames)
+ this["event" + eventName + "Actions"] = [];
+
+ // Special events
+ this.eventOnUnitRangeFromEntityData = {};
+ this.eventOnIntervalData = {};
+
+ // We are going to have all of the triggers here to be able to enable/disbale them in runtime.
+ this.triggerEnabled = {};
+
+ // We should define the trigger variables here so that they can be serialized
+ this.triggerVariables = {};
+
+ // 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
+ var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Trigger, "DoAction", 0, {"action": "InitTriggers", "data": {}});
+};
+
+// Global variables that are going to be used in triggers should be defined here. If not, They'll be lost after by saving/loading.
+Trigger.prototype.SetVariable = function(name, value)
+{
+ this.triggerVariables[name] = value;
+}
+
+Trigger.prototype.GetVariable = function(name)
+{
+ return this.triggerVariables[name];
+}
+
+/** Binds the "action" function to one of the implemented events.
+ *
+ * @param eventName Name of the event (see the list in init)
+ * @param action Functionname of a function available under the g_TriggersData global object
+ */
+Trigger.prototype.RegisterTrigger = function(event, action, enabled)
+{
+ var eventString = "event" + event + "Actions";
+ if (this[eventString])
+ {
+ this[eventString].push(action);
+ this.triggerEnabled[[event, action]] = enabled
+ }
+ else
+ error("Invalid trigger event \"" + event + "\".")
+};
+
+// Disable trigger
+Trigger.prototype.DisableTrigger = function(event, action)
+{
+ if (this.triggerEnabled[[event, action]] !== undefined)
+ this.triggerEnabled[[event, action]] = false;
+ else
+ warn("Trigger key doesn't exist:" + event + " & " + action);
+}
+
+// Enable trigger
+Trigger.prototype.EnableTrigger = function(event, action)
+{
+ if (this.triggerEnabled[[event, action]] !== undefined)
+ this.triggerEnabled[[event, action]] = true;
+ else
+ warn("Trigger key doesn't exist:" + event + " & " + action);
+}
+
+/**
+ * This is an event with more than just the key argument,
+ * so we should have a special register function for it.
+ *
+ * @param event Either "entered" or "left" based on the wanter behaviour.
+ * @param entity Entity id around which the range is checked
+ * @param radius Radius of the range
+ * @param action Functionname to execute on a range change (entities entered or left)
+ * @param players Array of player ids (0 to 8). Or null if you want to check for all players
+ * @param componentID Only entities with this component id will be noticed. 0 for all entities
+ * @param enabled Determines whether if the trigger is active by default
+ */
+Trigger.prototype.RegisterOnUnitRangeFromEntityTrigger = function(event, entity, radius, action, players, component_id, enabled)
+{
+ // If the players parameter is not specified use all players.
+ if (!players)
+ {
+ var playerEntities = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayerEntities();
+ players = [];
+ for each (var pentity in playerEntities)
+ players.push(Engine.QueryInterface(pentity, IID_Player).GetPlayerID());
+ }
+
+ // To determine if a unit has "moved" or "was inside" the range, we should know if it was in the range before. We have "currentCollection" for this purpose.
+ // For the purpose of finding out the intersection of the previous and the current rangeQuery faster, we are sorting the current one.
+ this.eventOnUnitRangeFromEntityData[action] = {"event": event, "action": action, "entity": entity, "radius": radius, "currentCollection": [], "players": players};
+
+ var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+
+ this.eventOnUnitRangeFromEntityData[action].rangeQuery = cmpRangeManager.CreateActiveQuery(entity, 0, radius, players, component_id, cmpRangeManager.GetEntityFlagMask("normal"));
+ if (enabled)
+ cmpRangeManager.EnableActiveQuery(this.eventOnUnitRangeFromEntityData[action].rangeQuery);
+}
+
+
+// Range triggers are different in nature, and should have their own Enable and disable functions
+Trigger.prototype.DisableRangeTrigger = function(action)
+{
+ var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ cmpRangeManager.DisableActiveQuery(this.eventOnUnitRangeFromEntityData[action].rangeQuery);
+}
+
+Trigger.prototype.EnableRangeTrigger = function(action)
+{
+ var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ cmpRangeManager.EnableActiveQuery(this.eventOnUnitRangeFromEntityData[action].rangeQuery);
+}
+
+/**
+ * This is an event with more than just the key argument,
+ * so we should have a special register function for it.
+ *
+ * @param action Functionname to execute on interval
+ * @param delay How many miliseconds before the first call?
+ * @param interval How many miliseconds between each call?
+ * @param enabled Determines whether if the trigger is active by default
+ */
+Trigger.prototype.RegisterOnIntervalTrigger = function(action, delay, interval, enabled)
+{
+ // If the trigger was active by default, start the update timer
+ if (enabled)
+ {
+ var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ this.eventOnIntervalData[action] = cmpTimer.SetInterval(this.entity, IID_Trigger, "TimerUpdate", delay, interval, {"action" : action});
+ }
+ else // If not, save it for future when we activate the trigger
+ this.eventOnIntervalData[action] = [delay, interval, {"action" : action}];
+}
+
+// Interval triggers are different in nature, and should have their own Enable and disable functions
+Trigger.prototype.DisableIntervalTrigger = function(action)
+{
+ var timer = this.eventOnIntervalData[action];
+ // Check if it is active
+ if (typeof (timer) === 'number')
+ {
+ var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ cmpTimer.CancelTimer(timer);
+ }
+}
+
+Trigger.prototype.EnableIntervalTrigger = function(action)
+{
+ var timer = this.eventOnIntervalData[action];
+ // Check if it is inactive
+ if (typeof (timer) !== 'number')
+ {
+ var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ this.eventOnIntervalData[action] = cmpTimer.SetInterval(this.entity, IID_Trigger, "TimerUpdate", timer[0], timer[1], timer[2]);
+ }
+}
+
+/**
+ * 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 event Name of the event (see the list in init)
+ * @param data Data object that will be passed to the actions
+ */
+Trigger.prototype.CallEvent = function(event, data)
+{
+ // A special case, when a unit dies, the range event associated with it should also be removed
+ if (event == "OnEntityKilled")
+ {
+ for (var i in this.eventOnEntityKilledActions)
+ {
+ if (this.triggerEnabled[[event, i]]) // Check if it is enabled
+ g_TriggerData[this.eventOnEntityKilledActions[i]](data); // The data for this one is {"killerEntity": killerEntity, "killedEntity": targetEntity}
+
+ // Remove the "OnUnitFromRangeEntity" triggers which have this entity as their argument
+ for (var j in this.eventOnUnitRangeFromEntityData)
+ if (this.eventOnUnitRangeFromEntityData[j].entity == data.killedEntity)
+ {
+ DisableRangeTrigger(j);
+ delete this.eventOnUnitRangeFromEntityData[j];
+ }
+ }
+ return;
+ }
+
+ var actions = this["event" + event + "Actions"];
+
+ if (actions === undefined)
+ {
+ warn("Unknown trigger event:\"" + event + "\".");
+ return;
+ }
+
+ for (var i in actions)
+ {
+ if (this.triggerEnabled[[event, actions[i]]])
+ g_TriggerData[this["event" + event + "Actions"][i]](data);
+ }
+}
+
+// Handles "OnStructureBuilt" event.
+Trigger.prototype.OnGlobalConstructionFinished = function(msg)
+{
+ this.CallEvent("OnStructureBuilt", {"building": msg.newentity}); // The data for this one is {"building": constructedBuilding}
+}
+
+// Handles "OnTrainingFinished" event.
+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
+}
+
+// Handle "OnInterval" event.
+Trigger.prototype.TimerUpdate = function(data, lateness)
+{
+ g_TriggerData[data.action](lateness);
+}
+
+// Handles "OnUnitRangeFromEntity"
+Trigger.prototype.OnGlobalRangeUpdate = function(msg)
+{
+ // search the right query
+ for (var action in this.eventOnUnitRangeFromEntityData)
+ {
+ if (msg.tag == this.eventOnUnitRangeFromEntityData[action].rangeQuery)
+ {
+ var updatedQuery = this.eventOnUnitRangeFromEntityData[action];
+ break;
+ }
+ }
+
+ if (!updatedQuery)
+ return;
+
+ for each (var entity in msg.removed)
+ {
+ var index = updatedQuery.currentCollection.indexOf(entity);
+ if (index > -1)
+ updatedQuery.currentCollection.splice(index, 1);
+ }
+
+ for each (var entity in msg.added)
+ updatedQuery.currentCollection.push(entity);
+
+ if (updatedQuery.event == "entered")
+ for each (var entity in msg.added)
+ g_TriggerData[updatedQuery.action]({"entity": entity});
+ else if (updatedQuery.event == "left")
+ for each (var entity in msg.removed)
+ g_TriggerData[updatedQuery.action]({"entity": entity});
+}
+
+/**
+ * Execute a function after a certain delay
+ * @param time The delay expressed in milleseconds
+ * @param action Name of the action function
+ * @param data Data object that will be passed to the action function
+ */
+Trigger.prototype.DoAfterDelay = function(miliseconds, action, data)
+{
+ var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ return cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Trigger, "DoAction", miliseconds, {"action": action, "data": data});
+}
+
+/**
+ * Called by the trigger listeners to exucute the actual action. Including sanity checks.
+ */
+Trigger.prototype.DoAction = function(msg)
+{
+ if (typeof g_TriggerData === 'undefined')
+ {
+ // We don't raise an error if g_TriggerData is undefined because it always happens in maps without triggers
+ log ("No trigger scripts loaded for this map.");
+ }
+ else
+ {
+ if (g_TriggerData[msg.action])
+ g_TriggerData[msg.action](msg.data);
+ else
+ error("called a trigger action '" + msg.action + "' that wasn't added to the g_TriggersData global");
+ }
+
+}
+
+Engine.RegisterComponentType(IID_Trigger, "Trigger", Trigger);
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js (revision 14710)
+++ binaries/data/mods/public/simulation/components/UnitAI.js (working copy)
@@ -199,6 +199,9 @@
cmpUnitMotion.MoveToFormationOffset(msg.data.target, msg.data.x, msg.data.z);
this.SetNextStateAlwaysEntering("FORMATIONMEMBER.WALKING");
+
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.FormationWalk", "metadata": msg});
},
// Special orders:
@@ -225,6 +228,9 @@
// We are already at the target, or can't move at all
this.FinishOrder();
}
+
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.LeaveFoundation", "metadata": msg});
},
// Individual orders:
@@ -247,7 +253,9 @@
this.SetNextState("ANIMAL.IDLE");
else
this.SetNextState("INDIVIDUAL.IDLE");
-
+
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Stop", "metadata": msg});
},
"Order.Walk": function(msg) {
@@ -258,6 +266,9 @@
return;
}
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Walk", "metadata": msg});
+
// For packable units:
// 1. If packed, we can move.
// 2. If unpacked, we first need to pack, then follow case 1.
@@ -284,6 +295,9 @@
return;
}
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.WalkAndFight", "metadata": msg});
+
// For packable units:
// 1. If packed, we can move.
// 2. If unpacked, we first need to pack, then follow case 1.
@@ -310,7 +324,10 @@
this.FinishOrder();
return;
}
-
+
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.WalkToTarget", "metadata": msg, "target": this.order.data.target});
+
// For packable units:
// 1. If packed, we can move.
// 2. If unpacked, we first need to pack, then follow case 1.
@@ -358,6 +375,9 @@
this.StopMoving();
this.SetNextState("INDIVIDUAL.PICKUP.LOADING");
}
+
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.PickupUnit", "metadata": msg, "target": this.order.data.target});
},
"Order.Guard": function(msg) {
@@ -371,6 +391,9 @@
this.SetNextState("INDIVIDUAL.GUARD.ESCORTING");
else
this.SetNextState("INDIVIDUAL.GUARD.GUARDING");
+
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Guard", "metadata": msg, "target": this.order.data.target});
},
"Order.Flee": function(msg) {
@@ -379,6 +402,9 @@
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
if (cmpUnitMotion.MoveToTargetRange(this.order.data.target, distance, -1))
{
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Flee", "metadata": msg, "target": this.order.data.target});
+
// We've started fleeing from the given target
if (this.IsAnimal())
this.SetNextState("ANIMAL.FLEEING");
@@ -391,6 +417,8 @@
this.StopMoving();
this.FinishOrder();
}
+
+
},
"Order.Attack": function(msg) {
@@ -411,6 +439,10 @@
}
this.order.data.attackType = type;
+ // Call "OnUnitIssuedOrder" event of the triggers.
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Attack", "metadata": msg, "target": this.order.data.target});
+
// If we are already at the target, try attacking it from here
if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType))
{
@@ -495,6 +527,7 @@
// We can't reach the target, and can't move towards it,
// so abandon this attack order
this.FinishOrder();
+
},
"Order.Heal": function(msg) {
@@ -515,6 +548,10 @@
// Check if the target is in range
if (this.CheckTargetRange(this.order.data.target, IID_Heal))
{
+ // Call "OnUnitIssuedOrder" event of the triggers.
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Heal", "metadata": msg, "target": this.order.data.target});
+
this.StopMoving();
this.SetNextState("INDIVIDUAL.HEAL.HEALING");
return;
@@ -531,6 +568,10 @@
// Try to move within heal range
if (this.MoveToTargetRange(this.order.data.target, IID_Heal))
{
+ // Call "OnUnitIssuedOrder" event of the triggers.
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Heal", "metadata": msg, "target": this.order.data.target});
+
// We've started walking to the given point
this.SetNextState("INDIVIDUAL.HEAL.APPROACHING");
return;
@@ -588,12 +629,20 @@
this.StopMoving();
this.SetNextStateAlwaysEntering("INDIVIDUAL.GATHER.GATHERING");
}
+
+ // Call "OnUnitIssuedOrder" event of the triggers.
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Gather", "metadata": msg, "target": this.order.data.target});
},
"Order.GatherNearPosition": function(msg) {
// Move the unit to the position to gather from.
this.MoveToPoint(this.order.data.x, this.order.data.z);
this.SetNextState("INDIVIDUAL.GATHER.WALKING");
+
+ // Call "OnUnitIssuedOrder" event of the triggers.
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.GatherNearPosition", "metadata": msg});
},
"Order.ReturnResource": function(msg) {
@@ -617,6 +666,10 @@
// Try to move to the dropsite
if (this.MoveToTargetRange(this.order.data.target, IID_ResourceGatherer))
{
+ // Call "OnUnitIssuedOrder" event of the triggers.
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.ReturnResource", "metadata": msg, "target": this.order.data.target});
+
// We've started walking to the target
this.SetNextState("INDIVIDUAL.RETURNRESOURCE.APPROACHING");
return;
@@ -649,6 +702,10 @@
this.waypoints = undefined;
if (this.MoveToMarket(nextMarket))
{
+ // Call "OnUnitIssuedOrder" event of the triggers.
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Trade", "metadata": msg, "target": this.order.data.target});
+
// We've started walking to the next market
this.SetNextState(state);
}
@@ -671,9 +728,17 @@
this.StopMoving();
this.SetNextState("INDIVIDUAL.REPAIR.REPAIRING");
}
+
+ // Call "OnUnitIssuedOrder" event of the triggers.
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Repair", "metadata": msg, "target": this.order.data.target});
},
"Order.Garrison": function(msg) {
+ // Call "OnUnitIssuedOrder" event of the triggers.
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Garrison", "metadata": msg, "target": this.order.data.target});
+
// For packable units:
// 1. If packed, we can move to the garrison target.
// 2. If unpacked, we first need to pack, then follow case 1.
@@ -698,6 +763,10 @@
"Order.Autogarrison": function(msg) {
this.SetNextState("INDIVIDUAL.AUTOGARRISON");
+
+ // Call "OnUnitIssuedOrder" event of the triggers.
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Autogarrison", "metadata": msg});
},
"Order.Alert": function(msg) {
@@ -711,10 +780,18 @@
this.ReplaceOrder("Garrison", {"target": this.alertGarrisoningTarget});
else
this.FinishOrder();
+
+ // Call "OnUnitIssuedOrder" event of the triggers.
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Alert", "metadata": msg});
},
"Order.Cheering": function(msg) {
this.SetNextState("INDIVIDUAL.CHEERING");
+
+ // Call "OnUnitIssuedOrder" event of the triggers.
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Cheering", "metadata": msg});
},
"Order.Pack": function(msg) {
@@ -722,6 +799,10 @@
{
this.StopMoving();
this.SetNextState("INDIVIDUAL.PACKING");
+
+ // Call "OnUnitIssuedOrder" event of the triggers.
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Pack", "metadata": msg});
}
},
@@ -730,6 +811,10 @@
{
this.StopMoving();
this.SetNextState("INDIVIDUAL.UNPACKING");
+
+ // Call "OnUnitIssuedOrder" event of the triggers.
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.Unpack", "metadata": msg});
}
},
@@ -738,6 +823,10 @@
if (cmpPack && cmpPack.IsPacking() && !cmpPack.IsPacked())
cmpPack.CancelPack();
this.FinishOrder();
+
+ // Call "OnUnitIssuedOrder" event of the triggers.
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.CancelPack", "metadata": msg});
},
"Order.CancelUnpack": function(msg) {
@@ -745,6 +834,10 @@
if (cmpPack && cmpPack.IsPacking() && cmpPack.IsPacked())
cmpPack.CancelPack();
this.FinishOrder();
+
+ // Call "OnUnitIssuedOrder" event of the triggers.
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitIssuedOrder", {"entity": this.entity, "order": "Order.CancelUnpack", "metadata": msg});
},
// States for the special entity representing a group of units moving in formation:
@@ -809,7 +902,7 @@
var cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI);
if (cmpTargetUnitAI && cmpTargetUnitAI.IsFormationMember())
target = cmpTargetUnitAI.GetFormationController();
-
+
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
// Check if we are already in range, otherwise walk there
if (!this.CheckTargetAttackRange(target, target))
@@ -838,6 +931,7 @@
this.FinishOrder();
return;
}
+
// Check if we are already in range, otherwise walk there
if (!this.CheckGarrisonRange(msg.data.target))
{
@@ -915,7 +1009,7 @@
}
this.CallMemberFunction("GatherNearPosition", [msg.data.x, msg.data.z, msg.data.type, msg.data.template, false]);
-
+
this.SetNextStateAlwaysEntering("MEMBER");
},
@@ -934,7 +1028,7 @@
}
this.CallMemberFunction("Heal", [msg.data.target, false]);
-
+
this.SetNextStateAlwaysEntering("MEMBER");
},
@@ -1188,7 +1282,7 @@
// Stop moving as soon as the formation disbands
this.StopMoving();
-
+
// If the controller handled an order but some members rejected it,
// they will have no orders and be in the FORMATIONMEMBER.IDLE state.
if (this.orderQueue.length)
Index: binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Commands.js (revision 14710)
+++ binaries/data/mods/public/simulation/helpers/Commands.js (working copy)
@@ -87,12 +87,26 @@
break;
case "walk":
+ // Handle OnUnitsIssuedOrder event of the triggers
+ if (entities.length > 1)
+ {
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitsIssuedOrder", {"entities": entities, "order": "Order.Walk", "metadata": {"x": cmd.x, "z": cmd.z, "queued": cmd.queued}});
+ }
+
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.Walk(cmd.x, cmd.z, cmd.queued);
});
break;
case "attack-walk":
+ // Handle OnUnitsIssuedOrder event of the triggers
+ if (entities.length > 1)
+ {
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitsIssuedOrder", {"entities": entities, "order": "Order.WalkAndFight", "metadata": {"x": cmd.x, "z": cmd.z, "queued": cmd.queued}});
+ }
+
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.queued);
});
@@ -104,7 +118,14 @@
// This check is for debugging only!
warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd));
}
-
+
+ // Handle OnUnitsIssuedOrder event of the triggers
+ if (entities.length > 1)
+ {
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitsIssuedOrder", {"entities": entities, "order": "Order.Attack", "metadata": {"target": cmd.target, "queued": cmd.queued}});
+ }
+
// See UnitAI.CanAttack for target checks
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.Attack(cmd.target, cmd.queued);
@@ -118,6 +139,13 @@
warn("Invalid command: heal target is not owned by player "+player+" or their ally: "+uneval(cmd));
}
+ // Handle OnUnitsIssuedOrder event of the triggers
+ if (entities.length > 1)
+ {
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitsIssuedOrder", {"entities": entities, "order": "Order.Heal", "metadata": {"target": cmd.target, "queued": cmd.queued}});
+ }
+
// See UnitAI.CanHeal for target checks
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.Heal(cmd.target, cmd.queued);
@@ -132,6 +160,13 @@
warn("Invalid command: repair target is not owned by ally of player "+player+": "+uneval(cmd));
}
+ // Handle OnUnitsIssuedOrder event of the triggers
+ if (entities.length > 1)
+ {
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitsIssuedOrder", {"entities": entities, "order": "Order.Repair", "metadata": {"target": cmd.target, "autocontinue": cmd.autocontinue, "queued": cmd.queued}});
+ }
+
// See UnitAI.CanRepair for target checks
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.Repair(cmd.target, cmd.autocontinue, cmd.queued);
@@ -145,6 +180,13 @@
warn("Invalid command: resource is not owned by gaia or player "+player+": "+uneval(cmd));
}
+ // Handle OnUnitsIssuedOrder event of the triggers
+ if (entities.length > 1)
+ {
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitsIssuedOrder", {"entities": entities, "order": "Order.Gather", "metadata": {"target": cmd.target, "queued": cmd.queued}});
+ }
+
// See UnitAI.CanGather for target checks
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.Gather(cmd.target, cmd.queued);
@@ -152,6 +194,13 @@
break;
case "gather-near-position":
+ // Handle OnUnitsIssuedOrder event of the triggers
+ if (entities.length > 1)
+ {
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitsIssuedOrder", {"entities": entities, "order": "Order.GatherNearPosition", "metadata": {"x": cmd.x, "z": cmd.z, "type": cmd.resourceType, "template": cmd.resourceTemplate, "queued": cmd.queued}});
+ }
+
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.GatherNearPosition(cmd.x, cmd.z, cmd.resourceType, cmd.resourceTemplate, cmd.queued);
});
@@ -165,6 +214,13 @@
warn("Invalid command: dropsite is not owned by player "+player+": "+uneval(cmd));
}
+ // Handle OnUnitsIssuedOrder event of the triggers
+ if (entities.length > 1)
+ {
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitsIssuedOrder", {"entities": entities, "order": "Order.ReturnResource", "metadata": {"target": cmd.target, "queued": cmd.queued}});
+ }
+
// See UnitAI.CanReturnResource for target checks
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.ReturnResource(cmd.target, cmd.queued);
@@ -325,6 +381,14 @@
// Verify that the building can be controlled by the player or is mutualAlly
if (CanControlUnitOrIsAlly(cmd.target, player, controlAllUnits))
{
+
+ // Handle OnUnitsIssuedOrder event of the triggers
+ if (entities.length > 1)
+ {
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnUnitsIssuedOrder", {"entities": entities, "order": "Order.Garrison", "metadata": {"target": cmd.target, "queued": cmd.queued}});
+ }
+
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.Garrison(cmd.target, cmd.queued);
});
Index: binaries/data/mods/public/simulation/helpers/Damage.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Damage.js (revision 14710)
+++ binaries/data/mods/public/simulation/helpers/Damage.js (working copy)
@@ -74,6 +74,12 @@
// Damage the target
var targetState = cmpDamageReceiver.TakeDamage(data.strengths.hack * data.multiplier, data.strengths.pierce * data.multiplier, data.strengths.crush * data.multiplier, data.attacker);
+ // Call the related trigger event
+ // We have to call the event instead of listening to it by messages because the message is sent
+ // after the target gets killed, which is not our intended case
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnEntityTookDamage", {"attackerEntity": data.attacker, "attackedEntity": data.target, "type":data.type, "ammount":-targetState.change});
+
// If the target was killed run some cleanup
if (targetState.killed)
Damage.TargetKilled(data.attacker, data.target);
@@ -110,6 +116,7 @@
// Call RangeManager with dummy entity and return the result.
var rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var rangeQuery = rangeManager.ExecuteQueryAroundPos(origin, 0, radius, players, IID_DamageReceiver);
+
return rangeQuery;
};
@@ -133,6 +140,10 @@
var cmpLooter = Engine.QueryInterface(killerEntity, IID_Looter);
if (cmpLooter)
cmpLooter.Collect(targetEntity);
+
+ // Call the related trigger event
+ var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ cmpTrigger.CallEvent("OnEntityKilled", {"killerEntity": killerEntity, "killedEntity": targetEntity});
};
Engine.RegisterGlobal("Damage", Damage);
Index: binaries/data/mods/public/simulation/helpers/TriggerHelpers.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/TriggerHelpers.js (revision 0)
+++ binaries/data/mods/public/simulation/helpers/TriggerHelpers.js (working copy)
@@ -0,0 +1,133 @@
+// Contains standardized functions suitable for using in trigger scripts.
+// Do not use them in any other simulation script.
+
+/**
+ * A function to get the owner of an entity.
+ * Returns the ID of a player. Returns 0 if the owner is Gaia.
+ */
+function GetEntityOwner(entity)
+{
+ return Engine.QueryInterface(entity, IID_Ownership).GetOwner()
+}
+
+/**
+ * A function to get the generic name of an entity
+ */
+function GetEntityGenericName(entity)
+{
+ var cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
+ if (!cmpIdentity)
+ return undefined;
+ return cmpIdentity.GetGenericName();
+}
+
+/**
+ * A function to get a list of the classes of an entity
+ */
+function GetEntityClassesList(entity)
+{
+ var cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
+ if (!cmpIdentity)
+ return undefined;
+ return cmpIdentity.GetClassesList();
+}
+
+/**
+ * A function to determine if an entity has a specific class
+ */
+function EntityHasClass(entity, classname)
+{
+ var classes = GetEntityClassesList(entity);
+ return (classes && classes.indexOf(classname) != -1);
+}
+
+/**
+ * Returns the entity id of a player based on the id of the player
+ * Useful when one wants to change a player's properties.
+ */
+function GetPlayerEntityByID(id)
+{
+ var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
+ return cmpPlayerMan.GetPlayerByID(id);
+}
+
+/**
+ * Can be used to "force" a building to spawn a group of entities.
+ * Only works for buildings that can already train units.
+ */
+function BuildingSpawnUnits(entity, template, count)
+{
+ var cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue);
+ if (!cmpProductionQueue)
+ return;
+ cmpProductionQueue.SpawnUnits(template, count, null);
+}
+
+/**
+ * Shows a message in the top center of the screen
+ */
+function PushGUINotificationInTopCenter(player, message)
+{
+ var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+ cmpGUIInterface.PushNotification({"player": player, "message": message});
+}
+
+/**
+ * Returns the resource type that can be gathered from an entity
+ */
+function GetEntityResourceType(entity)
+{
+ var cmpResourceSupply = Engine.QueryInterface(entity, IID_ResourceSupply);
+ if (!cmpResourceSupply)
+ return undefined;
+ return cmpResourceSupply.GetType();
+}
+
+/**
+ * Returns a list of entities owned by the player
+ */
+function GetEntitiesByPlayer(playerID)
+{
+ var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ return cmpRangeManager.GetEntitiesByPlayer(playerID);
+}
+
+/**
+ * Wins the game for a player
+ */
+function SetPlayerWon(playerID)
+{
+ var playerEnt = GetPlayerEntityByID(playerID);
+ var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
+ cmpPlayer.SetState("won")
+}
+
+/**
+ * Defeats a player
+ */
+function DefeatPlayer(playerID)
+{
+ var playerEnt = GetPlayerEntityByID(playerID);
+ Engine.PostMessage(playerEnt, MT_PlayerDefeated, { "playerId": playerID } );
+}
+
+/**
+ * Returns the system trigger component.
+ */
+function GetTriggerComponent()
+{
+ return Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+}
+
+Engine.RegisterGlobal("GetEntityOwner", GetEntityOwner);
+Engine.RegisterGlobal("GetEntityGenericName", GetEntityGenericName);
+Engine.RegisterGlobal("GetEntityClassesList", GetEntityClassesList);
+Engine.RegisterGlobal("EntityHasClass", EntityHasClass);
+Engine.RegisterGlobal("GetPlayerEntityByID", GetPlayerEntityByID);
+Engine.RegisterGlobal("BuildingSpawnUnits", BuildingSpawnUnits);
+Engine.RegisterGlobal("PushGUINotificationInTopCenter", PushGUINotificationInTopCenter);
+Engine.RegisterGlobal("GetEntityResourceType", GetEntityResourceType);
+Engine.RegisterGlobal("GetEntitiesByPlayer", GetEntitiesByPlayer);
+Engine.RegisterGlobal("SetPlayerWon", SetPlayerWon);
+Engine.RegisterGlobal("DefeatPlayer", DefeatPlayer);
+Engine.RegisterGlobal("GetTriggerComponent", GetTriggerComponent);
\ No newline at end of file
Index: source/simulation2/Simulation2.cpp
===================================================================
--- source/simulation2/Simulation2.cpp (revision 14710)
+++ source/simulation2/Simulation2.cpp (working copy)
@@ -133,6 +133,7 @@
LOAD_SCRIPTED_COMPONENT("PlayerManager");
LOAD_SCRIPTED_COMPONENT("TechnologyTemplateManager");
LOAD_SCRIPTED_COMPONENT("Timer");
+ LOAD_SCRIPTED_COMPONENT("Trigger");
LOAD_SCRIPTED_COMPONENT("ValueModificationManager");
#undef LOAD_SCRIPTED_COMPONENT
@@ -748,6 +749,16 @@
if (!m->m_StartupScript.empty())
GetScriptInterface().LoadScript(L"map startup script", m->m_StartupScript);
+
+ // Load the trigger script after we have loaded the simulation and the map.
+ if (GetScriptInterface().HasProperty(m->m_MapSettings.get(), "TriggerScript"))
+ {
+ std::string script_name;
+ GetScriptInterface().GetProperty(m->m_MapSettings.get(), "TriggerScript", script_name);
+
+ script_name = "maps/" + script_name;
+ m->m_ComponentManager.LoadScript(script_name.data());
+ }
}
int CSimulation2::ProgressiveLoad()