Index: binaries/data/mods/public/simulation/components/GarrisonHolder.js
===================================================================
--- binaries/data/mods/public/simulation/components/GarrisonHolder.js
+++ binaries/data/mods/public/simulation/components/GarrisonHolder.js
@@ -249,6 +249,11 @@
if (cmpUnitAI)
cmpUnitAI.SetTurretStance();
+ // Remove the unit's obstruction to avoid interfering with pathing.
+ let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction);
+ if (cmpObstruction)
+ cmpObstruction.SetDisableBlockMovementPathfinding(true, true, -1);
+
isVisiblyGarrisoned = true;
}
else
@@ -366,6 +371,11 @@
break;
}
+ // Reset the obstruction flags to template defaults.
+ let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction);
+ if (cmpObstruction)
+ cmpObstruction.SetDisableBlockMovementPathfinding(false, false, -1);
+
if (cmpEntUnitAI)
cmpEntUnitAI.Ungarrison();
Index: binaries/data/mods/public/simulation/components/Gate.js
===================================================================
--- binaries/data/mods/public/simulation/components/Gate.js
+++ binaries/data/mods/public/simulation/components/Gate.js
@@ -15,10 +15,25 @@
Gate.prototype.Init = function()
{
this.allies = [];
+ this.ignoreList = [];
this.opened = false;
this.locked = false;
};
+/**
+ * Handle the renaming case (from long-wall to gate)
+ * because that does not trigger ownershipchange or diplomacy change
+ * and units don't get added to the ignorelist.
+ */
+Gate.prototype.OnEntityRenamed = function(msg)
+{
+ let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+ if (!cmpOwnership || cmpOwnership.GetOwner() == INVALID_PLAYER)
+ return;
+
+ this.SetupRangeQuery();
+};
+
Gate.prototype.OnOwnershipChanged = function(msg)
{
if (msg.to != INVALID_PLAYER)
@@ -32,10 +47,11 @@
Gate.prototype.OnDiplomacyChanged = function(msg)
{
- var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+ let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (cmpOwnership && cmpOwnership.GetOwner() == msg.player)
{
this.allies = [];
+ this.ignoreList = [];
this.SetupRangeQuery(msg.player);
}
};
@@ -91,11 +107,36 @@
if (msg.added.length > 0)
for (let entity of msg.added)
+ {
+ // Ignore entities that cannot move as those won't be able to go through the gate.
+ let unitAI = Engine.QueryInterface(entity, IID_UnitAI);
+ if (!unitAI.AbleToMove())
+ this.ignoreList.push(entity);
this.allies.push(entity);
+ }
if (msg.removed.length > 0)
for (let entity of msg.removed)
+ {
+ let index = this.ignoreList.indexOf(entity);
+ if (index !== -1)
+ this.ignoreList.splice(index, 1);
this.allies.splice(this.allies.indexOf(entity), 1);
+ }
+
+ this.OperateGate();
+};
+
+Gate.prototype.OnGlobalUnitAbleToMoveChanged = function(msg)
+{
+ if (this.allies.indexOf(msg.entity) === -1)
+ return;
+
+ let index = this.ignoreList.indexOf(msg.entity);
+ if (msg.ableToMove && index !== -1)
+ this.ignoreList.splice(index, 1);
+ else if (!msg.ableToMove && index === -1)
+ this.ignoreList.push(msg.entity);
this.OperateGate();
};
@@ -108,6 +149,11 @@
return +this.template.PassRange;
};
+Gate.prototype.ShouldOpen = function()
+{
+ return this.allies.some(ent => this.ignoreList.indexOf(ent) === -1);
+};
+
/**
* Attempt to open or close the gate.
* An ally must be in range to open the gate, but an unlocked gate will only close
@@ -122,10 +168,9 @@
cmpTimer.CancelTimer(this.timer);
this.timer = undefined;
}
-
- if (this.opened && (this.allies.length == 0 || this.locked))
+ if (this.opened && (this.locked || !this.ShouldOpen()))
this.CloseGate();
- else if (!this.opened && this.allies.length)
+ else if (!this.opened && this.ShouldOpen())
this.OpenGate();
};
@@ -215,19 +260,19 @@
*/
Gate.prototype.CloseGate = function()
{
- var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
+ let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
if (!cmpObstruction)
return;
// The gate can't be closed if there are entities colliding with it.
- var collisions = cmpObstruction.GetEntitiesBlockingConstruction();
+ let collisions = cmpObstruction.GetEntitiesBlockingMovement();
if (collisions.length)
{
if (!this.timer)
{
// Set an "instant" timer which will run on the next simulation turn.
- var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
- this.timer = cmpTimer.SetTimeout(this.entity, IID_Gate, "OperateGate", 0, {});
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ this.timer = cmpTimer.SetTimeout(this.entity, IID_Gate, "OperateGate", 0);
}
return;
}
@@ -241,7 +286,7 @@
this.opened = false;
PlaySound("gate_closing", this.entity);
- var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
+ let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (cmpVisual)
cmpVisual.SelectAnimation("gate_closing", true, 1.0);
};
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js
+++ binaries/data/mods/public/simulation/components/UnitAI.js
@@ -202,7 +202,7 @@
// Called when being told to walk as part of a formation
"Order.FormationWalk": function(msg) {
// Let players move captured domestic animals around
- if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret())
+ if (this.IsAnimal() && !this.IsDomestic() || !this.AbleToMove())
{
this.FinishOrder();
return;
@@ -262,7 +262,7 @@
"Order.Walk": function(msg) {
// Let players move captured domestic animals around
- if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret())
+ if (this.IsAnimal() && !this.IsDomestic() || !this.AbleToMove())
{
this.FinishOrder();
return;
@@ -288,7 +288,7 @@
"Order.WalkAndFight": function(msg) {
// Let players move captured domestic animals around
- if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret())
+ if (this.IsAnimal() && !this.IsDomestic() || !this.AbleToMove())
{
this.FinishOrder();
return;
@@ -315,7 +315,7 @@
"Order.WalkToTarget": function(msg) {
// Let players move captured domestic animals around
- if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret())
+ if (this.IsAnimal() && !this.IsDomestic() || !this.AbleToMove())
{
this.FinishOrder();
return;
@@ -444,7 +444,7 @@
// If we can't reach the target, but are standing ground, then abandon this attack order.
// Unless we're hunting, that's a special case where we should continue attacking our target.
- if (this.GetStance().respondStandGround && !this.order.data.force && !this.order.data.hunting || this.IsTurret())
+ if (this.GetStance().respondStandGround && !this.order.data.force && !this.order.data.hunting || !this.AbleToMove())
{
this.FinishOrder();
return;
@@ -470,7 +470,7 @@
},
"Order.Patrol": function(msg) {
- if (this.IsAnimal() || this.IsTurret())
+ if (this.IsAnimal() || !this.AbleToMove())
{
this.FinishOrder();
return;
@@ -623,7 +623,7 @@
},
"Order.Garrison": function(msg) {
- if (this.IsTurret())
+ if (!this.AbleToMove())
{
this.SetNextState("IDLE");
return;
@@ -3042,6 +3042,7 @@
if (cmpGarrisonHolder.Garrison(this.entity))
{
this.isGarrisoned = true;
+ this.SetImmobile(true);
if (this.formationController)
{
@@ -3373,6 +3374,7 @@
this.formationController = INVALID_ENTITY; // entity with IID_Formation that we belong to
this.isGarrisoned = false;
this.isIdle = false;
+ this.isImmobile = false; // True if the unit is currently unable to move (garrisoned,...)
this.finishedOrder = false; // used to find if all formation members finished the order
this.heldPosition = undefined;
@@ -3472,6 +3474,30 @@
return !this.orderQueue.length || this.orderQueue[0].type == "Garrison";
};
+UnitAI.prototype.SetImmobile = function(immobile)
+{
+ this.isImmobile = immobile;
+ Engine.PostMessage(this.entity, MT_UnitAbleToMoveChanged, {
+ "entity": this.entity,
+ "ableToMove": this.AbleToMove()
+ });
+};
+
+/**
+ * @param cmpUnitMotion - optionally pass unitMotion to avoid querying it here
+ * @returns true if the entity can move, i.e. has UnitMotion and isn't immobile.
+ */
+UnitAI.prototype.AbleToMove = function(cmpUnitMotion)
+{
+ if (this.isImmobile || this.IsTurret())
+ return false;
+
+ if (!cmpUnitMotion)
+ cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
+
+ return !!cmpUnitMotion;
+};
+
UnitAI.prototype.IsFleeing = function()
{
var state = this.GetCurrentState().split(".").pop();
@@ -3908,9 +3934,8 @@
// If foundation is not ally of entity, or if entity is unpacked siege,
// ignore the order.
if (!IsOwnedByAllyOfEntity(this.entity, target) &&
- !Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager).IsCeasefireActive() ||
- checkPacking && this.IsPacking() ||
- this.CanPack() || this.IsTurret())
+ !Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager).IsCeasefireActive() ||
+ checkPacking && this.IsPacking() || this.CanPack() || !this.AbleToMove())
return false;
// Move a tile outside the building.
@@ -4492,13 +4517,13 @@
UnitAI.prototype.MoveToPoint = function(x, z)
{
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
- return cmpUnitMotion && cmpUnitMotion.MoveToPointRange(x, z, 0, 0); // For point goals, allow a max range of 0.
+ return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToPointRange(x, z, 0, 0); // For point goals, allow a max range of 0.
};
UnitAI.prototype.MoveToPointRange = function(x, z, rangeMin, rangeMax)
{
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
- return cmpUnitMotion && cmpUnitMotion.MoveToPointRange(x, z, rangeMin, rangeMax);
+ return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToPointRange(x, z, rangeMin, rangeMax);
};
UnitAI.prototype.MoveToTarget = function(target)
@@ -4507,12 +4532,12 @@
return false;
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
- return cmpUnitMotion && cmpUnitMotion.MoveToTargetRange(target, 0, 1);
+ return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToTargetRange(target, 0, 1);
};
UnitAI.prototype.MoveToTargetRange = function(target, iid, type)
{
- if (!this.CheckTargetVisible(target) || this.IsTurret())
+ if (!this.CheckTargetVisible(target))
return false;
let range = this.GetRange(iid, type);
@@ -4520,7 +4545,7 @@
return false;
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
- return cmpUnitMotion && cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
+ return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
};
/**
@@ -4538,6 +4563,10 @@
return false;
}
+ let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
+ if (!this.AbleToMove(cmpUnitMotion))
+ return false;
+
let cmpFormation = Engine.QueryInterface(target, IID_Formation);
if (cmpFormation)
target = cmpFormation.GetClosestMember(this.entity);
@@ -4574,7 +4603,6 @@
// The parabole changes while walking so be cautious:
let guessedMaxRange = parabolicMaxRange > range.max ? (range.max + parabolicMaxRange) / 2 : parabolicMaxRange;
- let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion && cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange);
};
@@ -4584,7 +4612,7 @@
return false;
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
- return cmpUnitMotion && cmpUnitMotion.MoveToTargetRange(target, min, max);
+ return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToTargetRange(target, min, max);
};
/**
@@ -4599,7 +4627,7 @@
if (cmpTargetFormation)
target = cmpTargetFormation.GetClosestMember(this.entity);
- if (!this.CheckTargetVisible(target) || this.IsTurret())
+ if (!this.CheckTargetVisible(target))
return false;
let cmpFormationAttack = Engine.QueryInterface(this.entity, IID_Attack);
@@ -4608,7 +4636,7 @@
let range = cmpFormationAttack.GetRange(target);
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
- return cmpUnitMotion && cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
+ return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
};
UnitAI.prototype.MoveToGarrisonRange = function(target)
@@ -4622,7 +4650,7 @@
var range = cmpGarrisonHolder.GetLoadingRange();
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
- return cmpUnitMotion && cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
+ return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
};
/**
@@ -5007,7 +5035,7 @@
*/
UnitAI.prototype.ShouldChaseTargetedEntity = function(target, force)
{
- if (this.IsTurret())
+ if (!this.AbleToMove())
return false;
if (this.GetStance().respondChase)
@@ -5333,11 +5361,11 @@
{
// If we're already being told to leave a foundation, then
// ignore this new request so we don't end up being too indecisive
- // to ever actually move anywhere
- // Ignore also the request if we are packing
+ // to ever actually move anywhere.
if (this.order && (this.order.type == "LeaveFoundation" || (this.order.type == "Flee" && this.order.data.target == target)))
return;
+ // Ignore also the request if we are packing.
if (this.orderQueue.length && this.orderQueue[0].type == "Unpack" && this.WillMoveFromFoundation(target, false))
{
let cmpPack = Engine.QueryInterface(this.entity, IID_Pack);
@@ -5399,7 +5427,10 @@
UnitAI.prototype.Ungarrison = function()
{
if (this.IsGarrisoned())
+ {
+ this.SetImmobile(false);
this.AddOrder("Ungarrison", null, false);
+ }
};
/**
Index: binaries/data/mods/public/simulation/components/interfaces/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/UnitAI.js
+++ binaries/data/mods/public/simulation/components/interfaces/UnitAI.js
@@ -6,6 +6,13 @@
*/
Engine.RegisterMessageType("UnitIdleChanged");
+/**
+ * Message of the form { "ableToMove": boolean }
+ * sent from UnitAI whenever the unit's ability to move changes.
+ */
+Engine.RegisterMessageType("UnitAbleToMoveChanged");
+
+
/**
* Message of the form { "to": string }
* where "to" value is a UnitAI stance,
Index: binaries/data/mods/public/simulation/templates/structures/iber_wall_gate.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/structures/iber_wall_gate.xml
+++ binaries/data/mods/public/simulation/templates/structures/iber_wall_gate.xml
@@ -4,6 +4,25 @@
12.7
+
+ 3
+ Ranged+Infantry
+ 0.1
+ Unit
+ 0
+ 2
+
+
+ 011.50
+
+
+ 411.50
+
+
+ -411.50
+
+
+
iber
Biko Sarbide
Index: binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_gate.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_gate.xml
+++ binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_gate.xml
@@ -7,7 +7,14 @@
60
-
+
+ 3
+ Ranged+Infantry
+ 0.1
+ Unit
+ 0
+ 2
+
20
@@ -29,7 +36,6 @@
-
Index: source/simulation2/components/CCmpObstruction.cpp
===================================================================
--- source/simulation2/components/CCmpObstruction.cpp
+++ source/simulation2/components/CCmpObstruction.cpp
@@ -651,6 +651,11 @@
return ret;
}
+ virtual std::vector GetEntitiesBlockingMovement() const
+ {
+ return GetEntitiesByFlags(ICmpObstructionManager::FLAG_BLOCK_MOVEMENT);
+ }
+
virtual std::vector GetEntitiesBlockingConstruction() const
{
return GetEntitiesByFlags(ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION);
Index: source/simulation2/components/ICmpObstruction.h
===================================================================
--- source/simulation2/components/ICmpObstruction.h
+++ source/simulation2/components/ICmpObstruction.h
@@ -104,6 +104,12 @@
*/
virtual std::vector GetEntitiesByFlags(ICmpObstructionManager::flags_t flags) const = 0;
+ /**
+ * Returns a list of entities that are blocking movement.
+ * @return vector of blocking entities
+ */
+ virtual std::vector GetEntitiesBlockingMovement() const = 0;
+
/**
* Returns a list of entities that are blocking construction of a foundation.
* @return vector of blocking entities
Index: source/simulation2/components/ICmpObstruction.cpp
===================================================================
--- source/simulation2/components/ICmpObstruction.cpp
+++ source/simulation2/components/ICmpObstruction.cpp
@@ -50,6 +50,7 @@
DEFINE_INTERFACE_METHOD_CONST_0("CheckShorePlacement", bool, ICmpObstruction, CheckShorePlacement)
DEFINE_INTERFACE_METHOD_CONST_2("CheckFoundation", std::string, ICmpObstruction, CheckFoundation_wrapper, std::string, bool)
DEFINE_INTERFACE_METHOD_CONST_0("CheckDuplicateFoundation", bool, ICmpObstruction, CheckDuplicateFoundation)
+DEFINE_INTERFACE_METHOD_CONST_0("GetEntitiesBlockingMovement", std::vector, ICmpObstruction, GetEntitiesBlockingMovement)
DEFINE_INTERFACE_METHOD_CONST_0("GetEntitiesBlockingConstruction", std::vector, ICmpObstruction, GetEntitiesBlockingConstruction)
DEFINE_INTERFACE_METHOD_CONST_0("GetEntitiesDeletedUponConstruction", std::vector, ICmpObstruction, GetEntitiesDeletedUponConstruction)
DEFINE_INTERFACE_METHOD_1("SetActive", void, ICmpObstruction, SetActive, bool)