Index: ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js +++ ps/trunk/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: ps/trunk/binaries/data/mods/public/simulation/components/Gate.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Gate.js +++ ps/trunk/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: ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js +++ ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js @@ -209,7 +209,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; @@ -269,7 +269,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; @@ -295,7 +295,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; @@ -322,7 +322,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; @@ -451,7 +451,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; @@ -477,7 +477,7 @@ }, "Order.Patrol": function(msg) { - if (this.IsAnimal() || this.IsTurret()) + if (this.IsAnimal() || !this.AbleToMove()) { this.FinishOrder(); return; @@ -630,7 +630,7 @@ }, "Order.Garrison": function(msg) { - if (this.IsTurret()) + if (!this.AbleToMove()) { this.SetNextState("IDLE"); return; @@ -3055,6 +3055,7 @@ if (cmpGarrisonHolder.Garrison(this.entity)) { this.isGarrisoned = true; + this.SetImmobile(true); if (this.formationController) { @@ -3386,6 +3387,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; @@ -3485,6 +3487,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(); @@ -3921,9 +3947,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. @@ -4513,13 +4538,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) @@ -4528,12 +4553,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); @@ -4541,7 +4566,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); }; /** @@ -4559,6 +4584,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); @@ -4595,7 +4624,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); }; @@ -4605,7 +4633,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); }; /** @@ -4620,7 +4648,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); @@ -4629,7 +4657,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) @@ -4643,7 +4671,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); }; /** @@ -5028,7 +5056,7 @@ */ UnitAI.prototype.ShouldChaseTargetedEntity = function(target, force) { - if (this.IsTurret()) + if (!this.AbleToMove()) return false; if (this.GetStance().respondChase) @@ -5354,11 +5382,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); @@ -5420,7 +5448,10 @@ UnitAI.prototype.Ungarrison = function() { if (this.IsGarrisoned()) + { + this.SetImmobile(false); this.AddOrder("Ungarrison", null, false); + } }; /** Index: ps/trunk/binaries/data/mods/public/simulation/components/interfaces/UnitAI.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/interfaces/UnitAI.js +++ ps/trunk/binaries/data/mods/public/simulation/components/interfaces/UnitAI.js @@ -7,6 +7,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, * sent from UnitAI whenever the unit's stance changes. Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js @@ -61,6 +61,7 @@ (unitAI, player_ent, target_ent) => { unitAI.CanGarrison = (target) => target == target_ent; unitAI.MoveToGarrisonRange = (target) => target == target_ent; + unitAI.AbleToMove = () => true; AddMock(target_ent, IID_GarrisonHolder, { "CanPickup": () => false Index: ps/trunk/source/simulation2/components/CCmpObstruction.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpObstruction.cpp +++ ps/trunk/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: ps/trunk/source/simulation2/components/ICmpObstruction.h =================================================================== --- ps/trunk/source/simulation2/components/ICmpObstruction.h +++ ps/trunk/source/simulation2/components/ICmpObstruction.h @@ -105,6 +105,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: ps/trunk/source/simulation2/components/ICmpObstruction.cpp =================================================================== --- ps/trunk/source/simulation2/components/ICmpObstruction.cpp +++ ps/trunk/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) Index: ps/trunk/source/simulation2/components/tests/test_ObstructionManager.h =================================================================== --- ps/trunk/source/simulation2/components/tests/test_ObstructionManager.h +++ ps/trunk/source/simulation2/components/tests/test_ObstructionManager.h @@ -41,6 +41,7 @@ virtual std::string CheckFoundation_wrapper(const std::string& UNUSED(className), bool UNUSED(onlyCenterPoint)) const { return std::string(); } virtual bool CheckDuplicateFoundation() const { return true; } virtual std::vector GetEntitiesByFlags(ICmpObstructionManager::flags_t UNUSED(flags)) const { return std::vector(); } + virtual std::vector GetEntitiesBlockingMovement() const { return std::vector(); } virtual std::vector GetEntitiesBlockingConstruction() const { return std::vector(); } virtual std::vector GetEntitiesDeletedUponConstruction() const { return std::vector(); } virtual void ResolveFoundationCollisions() const { }