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:
parent
847f3a9995
commit
6a66fb8205
@ -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.
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user