diff --git a/binaries/data/mods/public/simulation/components/Formation.js b/binaries/data/mods/public/simulation/components/Formation.js
index b9f4ef792d..cd2d9c33c5 100644
--- a/binaries/data/mods/public/simulation/components/Formation.js
+++ b/binaries/data/mods/public/simulation/components/Formation.js
@@ -995,6 +995,8 @@ Formation.prototype.LoadFormation = function(newTemplate)
cmpNewUnitAI.AddOrders(orders);
else
cmpNewUnitAI.MoveIntoFormation();
+
+ Engine.BroadcastMessage(MT_EntityRenamed, {"entity": this.entity, "newentity": newFormation});
};
Engine.RegisterComponentType(IID_Formation, "Formation", Formation);
diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js
index caf7d06c84..300ba321fd 100644
--- a/binaries/data/mods/public/simulation/components/UnitAI.js
+++ b/binaries/data/mods/public/simulation/components/UnitAI.js
@@ -790,6 +790,7 @@ var UnitFsmSpec = {
"Order.Stop": function(msg) {
this.CallMemberFunction("Stop", [false]);
+ this.FinishOrder();
},
"Order.Attack": function(msg) {
@@ -812,7 +813,7 @@ var UnitFsmSpec = {
return;
}
this.CallMemberFunction("Attack", [target, false]);
- this.SetNextState("MEMBER");
+ this.SetNextState("ATTACKING");
},
"Order.Garrison": function(msg) {
@@ -972,6 +973,10 @@ var UnitFsmSpec = {
},
"IDLE": {
+ "enter": function(msg) {
+ var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
+ cmpFormation.SetRearrange(false);
+ },
},
"WALKING": {
@@ -1080,6 +1085,53 @@ var UnitFsmSpec = {
}
},
+ "ATTACKING": {
+ // Wait for individual members to finish
+ "enter": function(msg) {
+ var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
+ cmpFormation.SetRearrange(true);
+ cmpFormation.MoveMembersIntoFormation(false, false);
+ this.StartTimer(1000, 1000);
+
+ var target = this.order.data.target;
+ // Check if we are already in range, otherwise walk there
+ if (!this.CheckTargetAttackRange(target, target))
+ {
+ if (this.TargetIsAlive(target) && this.CheckTargetVisible(target))
+ {
+ var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
+ var range = cmpAttack.GetRange(target);
+ this.PushOrderFront("WalkToTargetRange", { "target": target, "min": range.min, "max": range.max });
+ return;
+ }
+ this.FinishOrder();
+ return;
+ }
+
+ },
+
+ "Timer": function(msg) {
+ var target = this.order.data.target;
+ // Check if we are already in range, otherwise walk there
+ if (!this.CheckTargetAttackRange(target, target))
+ {
+ if (this.TargetIsAlive(target) && this.CheckTargetVisible(target))
+ {
+ var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
+ var range = cmpAttack.GetRange(target);
+ this.PushOrderFront("WalkToTargetRange", { "target": target, "min": range.min, "max": range.max });
+ return;
+ }
+ this.FinishOrder();
+ return;
+ }
+ },
+
+ "leave": function(msg) {
+ this.StopTimer();
+ },
+ },
+
"MEMBER": {
// Wait for individual members to finish
"enter": function(msg) {
@@ -1166,98 +1218,11 @@ var UnitFsmSpec = {
"IDLE": {
"enter": function() {
- var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation);
- if (!cmpFormation)
- {
- warn("UnitAI: " + this.entity + " got into FORMATIONMEMBER.IDLE mode without a valid formationController");
- if (this.IsAnimal())
- this.SetNextState("ANIMAL.IDLE");
- else
- this.SetNextState("INDIVIDUAL.IDLE");
- return true;
- }
-
- // Switch back to idle animation to guarantee we won't
- // get stuck with an incorrect animation
- this.SelectAnimation(cmpFormation.GetFormationAnimation(this.entity, "idle"));
-
- // If the unit is guarding/escorting, go back to its duty
- if (this.isGuardOf)
- {
- this.Guard(this.isGuardOf, false);
- cmpFormation.RemoveMembers([this.entity]);
- return true;
- }
-
- // The GUI and AI want to know when a unit is idle, but we don't
- // want to send frequent spurious messages if the unit's only
- // idle for an instant and will quickly go off and do something else.
- // So we'll set a timer here and only report the idle event if we
- // remain idle
- this.StartTimer(1000);
-
- // If a unit can heal and attack we first want to heal wounded units,
- // so check if we are a healer and find whether there's anybody nearby to heal.
- // (If anyone approaches later it'll be handled via LosHealRangeUpdate.)
- // If anyone in sight gets hurt that will be handled via LosHealRangeUpdate.
- if (this.IsHealer() && this.FindNewHealTargets())
- {
- cmpFormation.RemoveMembers([this.entity]);
- return true; // (abort the FSM transition since we may have already switched state)
- }
-
- // If we entered the idle state we must have nothing better to do,
- // so immediately check whether there's anybody nearby to attack.
- // (If anyone approaches later, it'll be handled via LosRangeUpdate.)
- if (this.FindNewTargets())
- {
- cmpFormation.RemoveMembers([this.entity]);
- return true; // (abort the FSM transition since we may have already switched state)
- }
-
- // Nobody to attack - stay in idle
- return false;
- },
-
- "leave": function() {
- var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
- if (this.losRangeQuery)
- rangeMan.DisableActiveQuery(this.losRangeQuery);
- if (this.losHealRangeQuery)
- rangeMan.DisableActiveQuery(this.losHealRangeQuery);
-
- this.StopTimer();
-
- if (this.isIdle)
- {
- this.isIdle = false;
- Engine.PostMessage(this.entity, MT_UnitIdleChanged, { "idle": this.isIdle });
- }
- },
-
- "LosRangeUpdate": function(msg) {
- if (this.GetStance().targetVisibleEnemies)
- {
- // Start attacking one of the newly-seen enemy (if any)
- this.AttackEntitiesByPreference(msg.data.added);
- }
- },
-
- "LosHealRangeUpdate": function(msg) {
- if (this.RespondToHealableEntities(msg.data.added))
- {
- var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation);
- if (cmpFormation)
- cmpFormation.RemoveMembers([this.entity]);
- }
- },
-
- "Timer": function(msg) {
- if (!this.isIdle)
- {
- this.isIdle = true;
- Engine.PostMessage(this.entity, MT_UnitIdleChanged, { "idle": this.isIdle });
- }
+ if (this.IsAnimal())
+ this.SetNextState("ANIMAL.IDLE");
+ else
+ this.SetNextState("INDIVIDUAL.IDLE");
+ return true;
},
},
@@ -1380,7 +1345,14 @@ var UnitFsmSpec = {
"enter": function() {
// Switch back to idle animation to guarantee we won't
// get stuck with an incorrect animation
- this.SelectAnimation("idle");
+ var animationName = "idle";
+ if (this.IsFormationMember())
+ {
+ var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation);
+ if (cmpFormation)
+ animationName = cmpFormation.GetFormationAnimation(this.entity, animationName);
+ }
+ this.SelectAnimation(animationName);
// If the unit is guarding/escorting, go back to its duty
if (this.isGuardOf)
@@ -1737,7 +1709,7 @@ var UnitFsmSpec = {
// Can't reach it - try to chase after it
if (this.ShouldChaseTargetedEntity(target, this.order.data.force))
{
- if (this.MoveToTargetRange(target, IID_Attack, this.order.data.attackType))
+ if (this.MoveToTargetAttackRange(target, this.order.data.attackType))
{
this.SetNextState("COMBAT.CHASING");
return;
@@ -1839,7 +1811,7 @@ var UnitFsmSpec = {
if (cmpTargetFormation)
{
this.order.data.target = this.order.data.formationTarget;
- this.TimerHandler(msg.data, msg.lateness);
+ //this.TimerHandler(msg.data, msg.lateness);
return;
}
@@ -3744,8 +3716,12 @@ UnitAI.prototype.OnGlobalConstructionFinished = function(msg)
UnitAI.prototype.OnGlobalEntityRenamed = function(msg)
{
for each (var order in this.orderQueue)
+ {
if (order.data && order.data.target && order.data.target == msg.entity)
order.data.target = msg.newentity;
+ if (order.data && order.data.formationTarget && order.data.formationTarget == msg.entity)
+ order.data.formationTarget = msg.newentity;
+ }
if (this.isGuardOf && this.isGuardOf == msg.entity)
this.isGuardOf = msg.newentity;
@@ -4113,6 +4089,15 @@ UnitAI.prototype.MoveToTargetRange = function(target, iid, type)
*/
UnitAI.prototype.MoveToTargetAttackRange = function(target, type)
{
+ // for formation members, the formation will take care of the range check
+ if (this.IsFormationMember())
+ {
+ var cmpFormationAttack = Engine.QueryInterface(this.formationController, IID_Attack);
+ var cmpFormationUnitAI = Engine.QueryInterface(this.formationController, IID_UnitAI);
+ if (cmpFormationAttack && cmpFormationAttack.CanAttackAsFormation() && cmpFormationUnitAI && cmpFormationUnitAI.GetCurrentState == "FORMATIONCONTROLLER.ATTACKING")
+ return false;
+ }
+
var cmpFormation = Engine.QueryInterface(target, IID_Formation)
if (cmpFormation)
target = cmpFormation.GetClosestMember(this.entity);
@@ -4204,6 +4189,15 @@ UnitAI.prototype.CheckTargetRange = function(target, iid, type)
*/
UnitAI.prototype.CheckTargetAttackRange = function(target, type)
{
+ // for formation members, the formation will take care of the range check
+ if (this.IsFormationMember())
+ {
+ var cmpFormationAttack = Engine.QueryInterface(this.formationController, IID_Attack);
+ var cmpFormationUnitAI = Engine.QueryInterface(this.formationController, IID_UnitAI);
+ if (cmpFormationAttack && cmpFormationAttack.CanAttackAsFormation() && cmpFormationUnitAI && cmpFormationUnitAI.GetCurrentState == "FORMATIONCONTROLLER.ATTACKING")
+ return true;
+ }
+
var cmpFormation = Engine.QueryInterface(target, IID_Formation)
if (cmpFormation)
target = cmpFormation.GetClosestMember(this.entity);
diff --git a/binaries/data/mods/public/simulation/templates/formations/phalanx.xml b/binaries/data/mods/public/simulation/templates/formations/phalanx.xml
index b894fadf78..b206926472 100644
--- a/binaries/data/mods/public/simulation/templates/formations/phalanx.xml
+++ b/binaries/data/mods/public/simulation/templates/formations/phalanx.xml
@@ -9,4 +9,7 @@
6
2
+
+ true
+
diff --git a/binaries/data/mods/public/simulation/templates/formations/skirmish.xml b/binaries/data/mods/public/simulation/templates/formations/skirmish.xml
index e89627bb01..bf6cf6769c 100644
--- a/binaries/data/mods/public/simulation/templates/formations/skirmish.xml
+++ b/binaries/data/mods/public/simulation/templates/formations/skirmish.xml
@@ -8,4 +8,7 @@
2
3
+
+ true
+
diff --git a/binaries/data/mods/public/simulation/templates/formations/syntagma.xml b/binaries/data/mods/public/simulation/templates/formations/syntagma.xml
index 7186923dc5..a4828f1d1c 100644
--- a/binaries/data/mods/public/simulation/templates/formations/syntagma.xml
+++ b/binaries/data/mods/public/simulation/templates/formations/syntagma.xml
@@ -5,4 +5,7 @@
9 pike infantry units required
Syntagma
+
+ true
+
diff --git a/binaries/data/mods/public/simulation/templates/formations/testudo.xml b/binaries/data/mods/public/simulation/templates/formations/testudo.xml
index 3673d9f65b..d61ea9b9a2 100644
--- a/binaries/data/mods/public/simulation/templates/formations/testudo.xml
+++ b/binaries/data/mods/public/simulation/templates/formations/testudo.xml
@@ -6,4 +6,7 @@
Testudo
0.7
+
+ true
+
diff --git a/binaries/data/mods/public/simulation/templates/formations/wedge.xml b/binaries/data/mods/public/simulation/templates/formations/wedge.xml
index f70ed3e285..fc0d5003e4 100644
--- a/binaries/data/mods/public/simulation/templates/formations/wedge.xml
+++ b/binaries/data/mods/public/simulation/templates/formations/wedge.xml
@@ -6,4 +6,7 @@
Wedge
triangle
+
+ true
+