1
0
forked from 0ad/0ad

Chasing fix - ignore the target's obstruction to avoid colliding with it.

Units movement is currently "all or nothing". This means that a chasing
entity that moves fast enough is likely to collide with its target, if
the latter is moving also. This means that it might fail to get in range
if the max range is smaller than the movement speed over a turn.
This happens to be very much the case in MP, as cavalry range is 4,
melee cav speed is ~20 and turns are 500ms.

This problem depends on which unit moves first (i.e. which unit is
lowest-ID).

To fix this, ignore the obstruction of the target, if it is moving, when
moving. This however means sometimes chasers will 'overshoot' and block
their target pathing, making the chase easier than it probably should
be.
Fleeing units don't suffer from this problem since they also ignore
their target (and their code handles it).

This new problem introduced in this diff is heavily dependent on the
exact speeds and ranges at play, and a further diff will improve the
situation to acceptable levels.

Reported by: FeldFeld
Refs #5936

Differential Revision: https://code.wildfiregames.com/D3482
This was SVN commit r24798.
This commit is contained in:
wraitii 2021-01-27 17:44:31 +00:00
parent 847f3a9995
commit 6a66fb8205
2 changed files with 73 additions and 12 deletions

View File

@ -717,7 +717,17 @@ private:
/**
* Returns an appropriate obstruction filter for use with path requests.
*/
ControlGroupMovementObstructionFilter GetObstructionFilter() const;
ControlGroupMovementObstructionFilter GetObstructionFilter() const
{
return ControlGroupMovementObstructionFilter(ShouldAvoidMovingUnits(), GetGroup());
}
/**
* Filter a specific tag on top of the existing control groups.
*/
SkipMovingTagAndControlGroupObstructionFilter GetObstructionFilter(const ICmpObstructionManager::tag_t& tag) const
{
return SkipMovingTagAndControlGroupObstructionFilter(tag, GetGroup());
}
/**
* Decide whether to approximate the given range from a square target as a circle,
@ -1009,6 +1019,14 @@ bool CCmpUnitMotion::PerformMove(fixed dt, const fixed& turnRate, WaypointPath&
fixed timeLeft = dt;
fixed zero = fixed::Zero();
ICmpObstructionManager::tag_t specificIgnore;
if (m_MoveRequest.m_Type == MoveRequest::ENTITY)
{
CmpPtr<ICmpObstruction> cmpTargetObstruction(GetSimContext(), m_MoveRequest.m_Entity);
if (cmpTargetObstruction)
specificIgnore = cmpTargetObstruction->GetObstruction();
}
while (timeLeft > zero)
{
// If we ran out of path, we have to stop.
@ -1057,7 +1075,7 @@ bool CCmpUnitMotion::PerformMove(fixed dt, const fixed& turnRate, WaypointPath&
fixed offsetLength = offset.Length();
if (offsetLength <= maxdist)
{
if (cmpPathfinder->CheckMovement(GetObstructionFilter(), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass))
if (cmpPathfinder->CheckMovement(GetObstructionFilter(specificIgnore), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass))
{
pos = target;
@ -1083,7 +1101,7 @@ bool CCmpUnitMotion::PerformMove(fixed dt, const fixed& turnRate, WaypointPath&
offset.Normalize(maxdist);
target = pos + offset;
if (cmpPathfinder->CheckMovement(GetObstructionFilter(), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass))
if (cmpPathfinder->CheckMovement(GetObstructionFilter(specificIgnore), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass))
pos = target;
else
return true;
@ -1225,15 +1243,33 @@ bool CCmpUnitMotion::ComputeTargetPosition(CFixedVector2D& out, const MoveReques
CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), moveRequest.m_Entity);
if (cmpUnitMotion && cmpUnitMotion->IsMoveRequested() && GetEntityId() < moveRequest.m_Entity)
{
// Add predicted movement.
CFixedVector2D tempPos = out + (out - cmpTargetPosition->GetPreviousPosition2D());
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
if (!cmpPosition || !cmpPosition->IsInWorld())
return true; // Still return true since we don't need a position for the target to have one.
CFixedVector2D tempPos = out + (out - cmpTargetPosition->GetPreviousPosition2D());
// Fleeing fix: if we anticipate the target to go through us, we'll suddenly turn around, which is bad.
// Pretend that the target is still behind us in those cases.
if (m_MoveRequest.m_MinRange > fixed::Zero())
{
if ((out - cmpPosition->GetPosition2D()).RelativeOrientation(tempPos - cmpPosition->GetPosition2D()) >= 0)
out = tempPos;
}
else
out = tempPos;
}
else if (cmpUnitMotion && cmpUnitMotion->IsMoveRequested() && GetEntityId() > moveRequest.m_Entity)
{
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
if (!cmpPosition || !cmpPosition->IsInWorld())
return true; // Still return true since we don't need a position for the target to have one.
// Check if we anticipate the target to go through us, in which case we shouldn't anticipate
// (or e.g. units fleeing might suddenly turn around towards their attacker).
if ((out - cmpPosition->GetPosition2D()).RelativeOrientation(tempPos - cmpPosition->GetPosition2D()) >= 0)
// Fleeing fix: opposite to above, check if our target has travelled through us already this turn.
CFixedVector2D tempPos = out - (out - cmpTargetPosition->GetPreviousPosition2D());
if (m_MoveRequest.m_MinRange > fixed::Zero() &&
(out - cmpPosition->GetPosition2D()).RelativeOrientation(tempPos - cmpPosition->GetPosition2D()) < 0)
out = tempPos;
}
}
@ -1381,11 +1417,6 @@ void CCmpUnitMotion::FaceTowardsPointFromPos(const CFixedVector2D& pos, entity_p
}
}
ControlGroupMovementObstructionFilter CCmpUnitMotion::GetObstructionFilter() const
{
return ControlGroupMovementObstructionFilter(ShouldAvoidMovingUnits(), GetGroup());
}
// The pathfinder cannot go to "rounded rectangles" goals, which are what happens with square targets and a non-null range.
// Depending on what the best approximation is, we either pretend the target is a circle or a square.
// One needs to be careful that the approximated geometry will be in the range.

View File

@ -529,6 +529,36 @@ public:
}
};
/**
* Obstruction test filter that reject shapes in a given control group or with the given tag (if that tag is moving),
* and rejects shapes that don't block unit movement. See D3482 for why this exists.
*/
class SkipMovingTagAndControlGroupObstructionFilter : public IObstructionTestFilter
{
entity_id_t m_Group;
tag_t m_Tag;
public:
SkipMovingTagAndControlGroupObstructionFilter(tag_t tag, entity_id_t group) :
m_Tag(tag), m_Group(group)
{}
virtual bool TestShape(tag_t tag, flags_t flags, entity_id_t group, entity_id_t group2) const
{
if (tag.n == m_Tag.n && (flags & ICmpObstructionManager::FLAG_MOVING))
return false;
if (group == m_Group || (group2 != INVALID_ENTITY && group2 == m_Group))
return false;
if (!(flags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT))
return false;
return true;
}
};
/**
* Obstruction test filter that will test only against shapes that:
* - do not have the specified tag