diff --git a/binaries/data/mods/public/simulation/components/Foundation.js b/binaries/data/mods/public/simulation/components/Foundation.js index 73cadb6e16..2eed5082d5 100644 --- a/binaries/data/mods/public/simulation/components/Foundation.js +++ b/binaries/data/mods/public/simulation/components/Foundation.js @@ -109,7 +109,7 @@ Foundation.prototype.Build = function(builderEnt, work) // Otherwise enable this obstruction so it blocks any further // units, and continue building. - var collisions = cmpObstruction.GetConstructionCollisions(); + var collisions = cmpObstruction.GetEntityCollisions(true, true); if (collisions.length) { for each (var ent in collisions) diff --git a/binaries/data/mods/public/simulation/components/Gate.js b/binaries/data/mods/public/simulation/components/Gate.js index 60d9637878..0d1e0c1986 100644 --- a/binaries/data/mods/public/simulation/components/Gate.js +++ b/binaries/data/mods/public/simulation/components/Gate.js @@ -14,7 +14,7 @@ Gate.prototype.Schema = */ Gate.prototype.Init = function() { - this.units = []; + this.allies = []; this.opened = false; this.locked = false; }; @@ -39,6 +39,14 @@ Gate.prototype.OnDestroy = function() var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); if (this.unitsQuery) cmpRangeManager.DestroyActiveQuery(this.unitsQuery); + + // Cancel the closing-blocked timer if it's running. + if (this.timer) + { + var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + cmpTimer.CancelTimer(this.timer); + this.timer = undefined; + } }; /** @@ -48,15 +56,15 @@ Gate.prototype.SetupRangeQuery = function(owner) { var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); + var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(owner), IID_Player); if (this.unitsQuery) cmpRangeManager.DestroyActiveQuery(this.unitsQuery); - var players = [] - var numPlayers = cmpPlayerManager.GetNumPlayers(); - - // Ignore gaia units - for (var i = 1; i < numPlayers; ++i) - players.push(i); + // Only allied units can make the gate open. + var players = []; + for (var i = 0; i < cmpPlayer.GetDiplomacy().length; ++i) + if (cmpPlayer.IsAlly(i)) + players.push(i); var range = this.GetPassRange(); if (range > 0) @@ -77,11 +85,11 @@ Gate.prototype.OnRangeUpdate = function(msg) if (msg.added.length > 0) for each (var entity in msg.added) - this.units.push(entity); + this.allies.push(entity); if (msg.removed.length > 0) for each (var entity in msg.removed) - this.units.splice(this.units.indexOf(entity), 1); + this.allies.splice(this.allies.indexOf(entity), 1); this.OperateGate(); }; @@ -96,29 +104,23 @@ Gate.prototype.GetPassRange = function() /** * Attempt to open or close the gate. - * An ally unit must be in range to open the gate, but there must be - * no units (including enemies) in range to close it again. + * An ally must be in range to open the gate, but an unlocked gate will only close + * if there are no allies in range and no units are inside the gate's obstruction. */ Gate.prototype.OperateGate = function() { - if (this.opened == true ) + // Cancel the closing-blocked timer if it's running. + if (this.timer) { - // If no units are in range, close the gate - if (this.units.length == 0) - this.CloseGate(); - } - else - { - // If one units in range is owned by an ally, open the gate - for each (var ent in this.units) - { - if (IsOwnedByAllyOfEntity(this.entity, ent)) - { - this.OpenGate(); - break; - } - } + var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + cmpTimer.CancelTimer(this.timer); + this.timer = undefined; } + + if (this.opened && (this.allies.length == 0 || this.locked)) + this.CloseGate(); + else if (!this.opened && this.allies.length) + this.OpenGate(); }; Gate.prototype.IsLocked = function() @@ -141,6 +143,8 @@ Gate.prototype.LockGate = function() return; cmpObstruction.SetDisableBlockMovementPathfinding(false, false, 0); } + else + this.OperateGate(); // TODO: Possibly move the lock/unlock sounds to UI? Needs testing PlaySound("gate_locked", this.entity); @@ -157,7 +161,7 @@ Gate.prototype.UnlockGate = function(quiet) return; // Disable 'block pathfinding' - cmpObstruction.SetDisableBlockMovementPathfinding(false, true, 0); + cmpObstruction.SetDisableBlockMovementPathfinding(this.opened, true, 0); this.locked = false; // TODO: Possibly move the lock/unlock sounds to UI? Needs testing @@ -194,6 +198,9 @@ Gate.prototype.OpenGate = function() /** * Close the gate, with sound and animation. + * + * The gate may fail to close due to unit obstruction. If this occurs, the + * gate will start a timer and attempt to close on each simulation update. */ Gate.prototype.CloseGate = function() { @@ -201,6 +208,19 @@ Gate.prototype.CloseGate = function() if (!cmpObstruction) return; + // The gate can't be closed if there are entities colliding with it. + var collisions = cmpObstruction.GetEntityCollisions(false, true); + 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, {}); + } + return; + } + // If we ordered the gate to be locked, enable 'block movement' and 'block pathfinding' if (this.locked) cmpObstruction.SetDisableBlockMovementPathfinding(false, false, 0); diff --git a/source/simulation2/components/CCmpObstruction.cpp b/source/simulation2/components/CCmpObstruction.cpp index c7f189d324..ccfe617dfd 100644 --- a/source/simulation2/components/CCmpObstruction.cpp +++ b/source/simulation2/components/CCmpObstruction.cpp @@ -549,7 +549,7 @@ public: return !cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, NULL); } - virtual std::vector GetConstructionCollisions() + virtual std::vector GetEntityCollisions(bool checkStructures, bool checkUnits) { std::vector ret; @@ -569,15 +569,37 @@ public: // required precondition to use SkipControlGroupsRequireFlagObstructionFilter if (m_ControlGroup == INVALID_ENTITY) { - LOGERROR(L"[CmpObstruction] Cannot test for construction obstructions; primary control group must be valid"); + LOGERROR(L"[CmpObstruction] Cannot test for unit or structure obstructions; primary control group must be valid"); return ret; } - // Ignore collisions within the same control group, or with other non-construction-blocking shapes. + flags_t flags = 0; + bool invertMatch = false; + + // There are four 'block' flags: construction, foundation, movement, + // and pathfinding. Structures have all of these flags, while units + // block only movement and construction. + + // The 'block construction' flag is common to both units and structures. + if (checkStructures && checkUnits) + flags = ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION; + // The 'block foundation' flag is exclusive to structures. + else if (checkStructures) + flags = ICmpObstructionManager::FLAG_BLOCK_FOUNDATION; + else if (checkUnits) + { + // As structures block a superset of what units do, matching units + // but not structures means excluding entities that contain any of + // the structure-specific flags (foundation and pathfinding). + invertMatch = true; + flags = ICmpObstructionManager::FLAG_BLOCK_FOUNDATION | ICmpObstructionManager::FLAG_BLOCK_PATHFINDING; + } + + // Ignore collisions within the same control group, or with other shapes that don't match the filter. // Note that, since the control group for each entity defaults to the entity's ID, this is typically - // equivalent to only ignoring the entity's own shape and other non-construction-blocking shapes. - SkipControlGroupsRequireFlagObstructionFilter filter(m_ControlGroup, m_ControlGroup2, - ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION); + // equivalent to only ignoring the entity's own shape and other shapes that don't match the filter. + SkipControlGroupsRequireFlagObstructionFilter filter(invertMatch, + m_ControlGroup, m_ControlGroup2, flags); if (m_Type == STATIC) cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &ret); diff --git a/source/simulation2/components/ICmpObstruction.cpp b/source/simulation2/components/ICmpObstruction.cpp index cad9d09f7b..74ede56f02 100644 --- a/source/simulation2/components/ICmpObstruction.cpp +++ b/source/simulation2/components/ICmpObstruction.cpp @@ -25,7 +25,7 @@ BEGIN_INTERFACE_WRAPPER(Obstruction) DEFINE_INTERFACE_METHOD_0("GetUnitRadius", entity_pos_t, ICmpObstruction, GetUnitRadius) DEFINE_INTERFACE_METHOD_1("CheckFoundation", bool, ICmpObstruction, CheckFoundation, std::string) DEFINE_INTERFACE_METHOD_0("CheckDuplicateFoundation", bool, ICmpObstruction, CheckDuplicateFoundation) -DEFINE_INTERFACE_METHOD_0("GetConstructionCollisions", std::vector, ICmpObstruction, GetConstructionCollisions) +DEFINE_INTERFACE_METHOD_2("GetEntityCollisions", std::vector, ICmpObstruction, GetEntityCollisions, bool, bool) DEFINE_INTERFACE_METHOD_1("SetActive", void, ICmpObstruction, SetActive, bool) DEFINE_INTERFACE_METHOD_3("SetDisableBlockMovementPathfinding", void, ICmpObstruction, SetDisableBlockMovementPathfinding, bool, bool, int32_t) DEFINE_INTERFACE_METHOD_0("GetBlockMovementFlag", bool, ICmpObstruction, GetBlockMovementFlag) diff --git a/source/simulation2/components/ICmpObstruction.h b/source/simulation2/components/ICmpObstruction.h index e08485eaef..d7a5f0970d 100644 --- a/source/simulation2/components/ICmpObstruction.h +++ b/source/simulation2/components/ICmpObstruction.h @@ -59,11 +59,11 @@ public: virtual bool CheckDuplicateFoundation() = 0; /** - * Returns a list of entities that are colliding with this entity, and that - * are set to block construction. + * Returns a list of entities that are colliding with this entity, + * filtered depending on type of entities that are requested. * @return vector of blocking entities */ - virtual std::vector GetConstructionCollisions() = 0; + virtual std::vector GetEntityCollisions(bool checkStructures, bool checkUnits) = 0; /** * Detects collisions between foundation-blocking entities and diff --git a/source/simulation2/components/ICmpObstructionManager.h b/source/simulation2/components/ICmpObstructionManager.h index aa7d7922d1..2dcef8b6f4 100644 --- a/source/simulation2/components/ICmpObstructionManager.h +++ b/source/simulation2/components/ICmpObstructionManager.h @@ -355,7 +355,9 @@ public: /** * Obstruction test filter that will test only against shapes that: * - are part of neither one of the specified control groups - * - AND have at least one of the specified flags set. + * - AND, depending on the value of the 'exclude' argument: + * - have at least one of the specified flags set. + * - OR have none of the specified flags set. * * The first (primary) control group to reject shapes from must be specified and valid. The secondary * control group to reject entities from may be set to INVALID_ENTITY to not use it. @@ -365,13 +367,41 @@ public: */ class SkipControlGroupsRequireFlagObstructionFilter : public IObstructionTestFilter { + bool m_Exclude; entity_id_t m_Group; entity_id_t m_Group2; flags_t m_Mask; public: + SkipControlGroupsRequireFlagObstructionFilter(bool exclude, entity_id_t group1, entity_id_t group2, flags_t mask) : + m_Exclude(exclude), m_Group(group1), m_Group2(group2), m_Mask(mask) + { + Init(); + } + SkipControlGroupsRequireFlagObstructionFilter(entity_id_t group1, entity_id_t group2, flags_t mask) : - m_Group(group1), m_Group2(group2), m_Mask(mask) + m_Exclude(false), m_Group(group1), m_Group2(group2), m_Mask(mask) + { + Init(); + } + + virtual bool TestShape(tag_t UNUSED(tag), flags_t flags, entity_id_t group, entity_id_t group2) const + { + // Don't test shapes that share one or more of our control groups. + if (group == m_Group || group == m_Group2 || (group2 != INVALID_ENTITY && + (group2 == m_Group || group2 == m_Group2))) + return false; + + // If m_Exclude is true, don't test against shapes that have any of the + // obstruction flags specified in m_Mask. + if (m_Exclude) + return (flags & m_Mask) == 0; + + // Otherwise, only include shapes that match at least one flag in m_Mask. + return (flags & m_Mask) != 0; + } +private: + void Init() { // the primary control group to filter out must be valid ENSURE(m_Group != INVALID_ENTITY); @@ -381,20 +411,6 @@ public: if (m_Group2 == INVALID_ENTITY) m_Group2 = m_Group; } - - virtual bool TestShape(tag_t UNUSED(tag), flags_t flags, entity_id_t group, entity_id_t group2) const - { - // To be included in the testing, a shape must have at least one of the flags in m_Mask set, and its - // primary control group must be valid and must equal neither our primary nor secondary control group. - bool includeInTesting = ((flags & m_Mask) != 0 && group != m_Group && group != m_Group2); - - // If the shape being tested has a valid secondary control group, exclude it from testing if it - // matches either our primary or secondary control group. - if (group2 != INVALID_ENTITY) - includeInTesting = (includeInTesting && group2 != m_Group && group2 != m_Group2); - - return includeInTesting; - } }; /**