Improve gate closing behaviour when locked or with no allies nearby. Fixes #1551.

This was SVN commit r12337.
This commit is contained in:
Deiz
2012-08-09 19:06:01 +00:00
parent a89d131f2d
commit 613f845095
6 changed files with 113 additions and 55 deletions
@@ -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)
@@ -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);
@@ -549,7 +549,7 @@ public:
return !cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, NULL);
}
virtual std::vector<entity_id_t> GetConstructionCollisions()
virtual std::vector<entity_id_t> GetEntityCollisions(bool checkStructures, bool checkUnits)
{
std::vector<entity_id_t> 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);
@@ -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<entity_id_t>, ICmpObstruction, GetConstructionCollisions)
DEFINE_INTERFACE_METHOD_2("GetEntityCollisions", std::vector<entity_id_t>, 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)
@@ -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<entity_id_t> GetConstructionCollisions() = 0;
virtual std::vector<entity_id_t> GetEntityCollisions(bool checkStructures, bool checkUnits) = 0;
/**
* Detects collisions between foundation-blocking entities and
@@ -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;
}
};
/**