mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-06-21 02:46:49 +00:00
Improve gate closing behaviour when locked or with no allies nearby. Fixes #1551.
This was SVN commit r12337.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user