Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp
Show First 20 Lines • Show All 113 Lines • ▼ Show 20 Lines | |||||
static const CColor OVERLAY_COLOR_LONG_PATH(1, 1, 1, 1); | static const CColor OVERLAY_COLOR_LONG_PATH(1, 1, 1, 1); | ||||
static const CColor OVERLAY_COLOR_SHORT_PATH(1, 0, 0, 1); | static const CColor OVERLAY_COLOR_SHORT_PATH(1, 0, 0, 1); | ||||
class CCmpUnitMotion : public ICmpUnitMotion | class CCmpUnitMotion : public ICmpUnitMotion | ||||
{ | { | ||||
public: | public: | ||||
static void ClassInit(CComponentManager& componentManager) | static void ClassInit(CComponentManager& componentManager) | ||||
{ | { | ||||
componentManager.SubscribeToMessageType(MT_TurnStart); | |||||
componentManager.SubscribeToMessageType(MT_Update_MotionFormation); | componentManager.SubscribeToMessageType(MT_Update_MotionFormation); | ||||
componentManager.SubscribeToMessageType(MT_Update_MotionUnit); | componentManager.SubscribeToMessageType(MT_Update_MotionUnit); | ||||
componentManager.SubscribeToMessageType(MT_PathResult); | componentManager.SubscribeToMessageType(MT_PathResult); | ||||
componentManager.SubscribeToMessageType(MT_OwnershipChanged); | componentManager.SubscribeToMessageType(MT_OwnershipChanged); | ||||
componentManager.SubscribeToMessageType(MT_ValueModification); | componentManager.SubscribeToMessageType(MT_ValueModification); | ||||
componentManager.SubscribeToMessageType(MT_Deserialized); | componentManager.SubscribeToMessageType(MT_Deserialized); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 176 Lines • ▼ Show 20 Lines | virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) | ||||
if (cmpPathfinder) | if (cmpPathfinder) | ||||
m_PassClass = cmpPathfinder->GetPassabilityClass(m_PassClassName); | m_PassClass = cmpPathfinder->GetPassabilityClass(m_PassClassName); | ||||
} | } | ||||
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) | virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) | ||||
{ | { | ||||
switch (msg.GetType()) | switch (msg.GetType()) | ||||
{ | { | ||||
case MT_TurnStart: | |||||
{ | |||||
TurnStart(); | |||||
break; | |||||
} | |||||
case MT_Update_MotionFormation: | case MT_Update_MotionFormation: | ||||
{ | { | ||||
if (m_FormationController) | if (m_FormationController) | ||||
{ | { | ||||
fixed dt = static_cast<const CMessageUpdate_MotionFormation&> (msg).turnLength; | fixed dt = static_cast<const CMessageUpdate_MotionFormation&> (msg).turnLength; | ||||
Move(dt); | Move(dt); | ||||
} | } | ||||
break; | break; | ||||
▲ Show 20 Lines • Show All 306 Lines • ▼ Show 20 Lines | private: | ||||
} | } | ||||
/** | /** | ||||
* Handle the result of an asynchronous path query. | * Handle the result of an asynchronous path query. | ||||
*/ | */ | ||||
void PathResult(u32 ticket, const WaypointPath& path); | void PathResult(u32 ticket, const WaypointPath& path); | ||||
/** | /** | ||||
* Check if we are at destination early in the turn, this both lets units react faster | |||||
* and ensure that distance comparisons are done while units are not being moved | |||||
* (otherwise they won't be commutative). | |||||
*/ | |||||
void TurnStart(); | |||||
/** | |||||
* Do the per-turn movement and other updates. | * Do the per-turn movement and other updates. | ||||
*/ | */ | ||||
void Move(fixed dt); | void Move(fixed dt); | ||||
/** | /** | ||||
* Returns true if we are possibly at our destination. | * Returns true if we are possibly at our destination. | ||||
* Since the concept of being at destination is dependent on why the move was requested, | * Since the concept of being at destination is dependent on why the move was requested, | ||||
* UnitMotion can only ever hint about this, hence the conditional tone. | * UnitMotion can only ever hint about this, hence the conditional tone. | ||||
▲ Show 20 Lines • Show All 204 Lines • ▼ Show 20 Lines | if (PathingUpdateNeeded(pos)) | ||||
if (!IncrementFailedMovementsAndMaybeNotify()) | if (!IncrementFailedMovementsAndMaybeNotify()) | ||||
MoveObstructed(); | MoveObstructed(); | ||||
// We'll automatically recompute a path when this reaches 0, as a way to improve behaviour. | // We'll automatically recompute a path when this reaches 0, as a way to improve behaviour. | ||||
// (See D665 - this is needed because the target may be moving, and we should adjust to that). | // (See D665 - this is needed because the target may be moving, and we should adjust to that). | ||||
m_FollowKnownImperfectPathCountdown = KNOWN_IMPERFECT_PATH_RESET_COUNTDOWN; | m_FollowKnownImperfectPathCountdown = KNOWN_IMPERFECT_PATH_RESET_COUNTDOWN; | ||||
} | } | ||||
} | } | ||||
void CCmpUnitMotion::Move(fixed dt) | void CCmpUnitMotion::TurnStart() | ||||
{ | { | ||||
PROFILE("Move"); | |||||
// If we were idle and will still be, we can return. | |||||
// TODO: this will need to be removed if pushing is implemented. | |||||
if (m_CurSpeed == fixed::Zero() && m_MoveRequest.m_Type == MoveRequest::NONE) | |||||
return; | |||||
if (PossiblyAtDestination()) | if (PossiblyAtDestination()) | ||||
MoveSucceeded(); | MoveSucceeded(); | ||||
else if (!TargetHasValidPosition()) | else if (!TargetHasValidPosition()) | ||||
{ | { | ||||
// Scrap waypoints - we don't know where to go. | // Scrap waypoints - we don't know where to go. | ||||
// If the move request remains unchanged and the target again has a valid position later on, | // If the move request remains unchanged and the target again has a valid position later on, | ||||
// moving will be resumed. | // moving will be resumed. | ||||
// Units may want to move to move to the target's last known position, | // Units may want to move to move to the target's last known position, | ||||
// but that should be decided by UnitAI (handling MoveFailed), not UnitMotion. | // but that should be decided by UnitAI (handling MoveFailed), not UnitMotion. | ||||
m_LongPath.m_Waypoints.clear(); | m_LongPath.m_Waypoints.clear(); | ||||
m_ShortPath.m_Waypoints.clear(); | m_ShortPath.m_Waypoints.clear(); | ||||
MoveFailed(); | MoveFailed(); | ||||
} | } | ||||
} | |||||
void CCmpUnitMotion::Move(fixed dt) | |||||
{ | |||||
PROFILE("Move"); | |||||
// If we were idle and will still be, we can return. | |||||
// TODO: this will need to be removed if pushing is implemented. | |||||
if (m_CurSpeed == fixed::Zero() && m_MoveRequest.m_Type == MoveRequest::NONE) | |||||
return; | |||||
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | ||||
if (!cmpPosition || !cmpPosition->IsInWorld()) | if (!cmpPosition || !cmpPosition->IsInWorld()) | ||||
return; | return; | ||||
CFixedVector2D initialPos = cmpPosition->GetPosition2D(); | CFixedVector2D initialPos = cmpPosition->GetPosition2D(); | ||||
entity_angle_t initialAngle = cmpPosition->GetRotation().Y; | entity_angle_t initialAngle = cmpPosition->GetRotation().Y; | ||||
▲ Show 20 Lines • Show All 114 Lines • ▼ Show 20 Lines | while (timeLeft > zero) | ||||
CFixedVector2D target; | CFixedVector2D target; | ||||
if (shortPath.m_Waypoints.empty()) | if (shortPath.m_Waypoints.empty()) | ||||
target = CFixedVector2D(longPath.m_Waypoints.back().x, longPath.m_Waypoints.back().z); | target = CFixedVector2D(longPath.m_Waypoints.back().x, longPath.m_Waypoints.back().z); | ||||
else | else | ||||
target = CFixedVector2D(shortPath.m_Waypoints.back().x, shortPath.m_Waypoints.back().z); | target = CFixedVector2D(shortPath.m_Waypoints.back().x, shortPath.m_Waypoints.back().z); | ||||
CFixedVector2D offset = target - pos; | CFixedVector2D offset = target - pos; | ||||
if (turnRate > zero) | if (turnRate > zero && !offset.IsZero()) | ||||
{ | { | ||||
fixed maxRotation = turnRate.Multiply(timeLeft); | fixed maxRotation = turnRate.Multiply(timeLeft); | ||||
fixed angleDiff = angle - atan2_approx(offset.X, offset.Y); | fixed angleDiff = angle - atan2_approx(offset.X, offset.Y); | ||||
if (angleDiff != zero) | if (angleDiff != zero) | ||||
{ | { | ||||
fixed absoluteAngleDiff = angleDiff.Absolute(); | fixed absoluteAngleDiff = angleDiff.Absolute(); | ||||
if (absoluteAngleDiff > entity_angle_t::Pi()) | if (absoluteAngleDiff > entity_angle_t::Pi()) | ||||
absoluteAngleDiff = entity_angle_t::Pi() * 2 - absoluteAngleDiff; | absoluteAngleDiff = entity_angle_t::Pi() * 2 - absoluteAngleDiff; | ||||
▲ Show 20 Lines • Show All 179 Lines • ▼ Show 20 Lines | if (moveRequest.m_Type == MoveRequest::OFFSET) | ||||
// There is an offset, so compute it relative to orientation | // There is an offset, so compute it relative to orientation | ||||
entity_angle_t angle = cmpTargetPosition->GetRotation().Y; | entity_angle_t angle = cmpTargetPosition->GetRotation().Y; | ||||
CFixedVector2D offset = moveRequest.GetOffset().Rotate(angle); | CFixedVector2D offset = moveRequest.GetOffset().Rotate(angle); | ||||
out = cmpTargetPosition->GetPosition2D() + offset; | out = cmpTargetPosition->GetPosition2D() + offset; | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
out = cmpTargetPosition->GetPosition2D(); | out = cmpTargetPosition->GetPosition2D(); | ||||
// If the target is moving, we might never get in range if we just try to reach its current position, | // Because units move one-at-a-time and pathing is asynchronous, we need to account for target movement. | ||||
// so we have to try and move to a position where we will be in-range, including their movement. | // If our entity ID is lower, we move first, and so we need to add a predicted movement to compute a path for next turn. | ||||
// Since we request paths asynchronously a the end of our turn and the order in which two units move is uncertain, | // If our entity ID is higher, the target has already moved, so we can just use the position directly. | ||||
// we need to account for twice the movement speed to be sure that we're targeting the correct point. | // TODO: This does not really aim many turns in advance, with orthogonal trajectories it probably should. | ||||
// TODO: be cleverer about this. It fixes fleeing nicely currently, but orthogonal movement should be considered, | |||||
// and the overall logic could be improved upon. | |||||
CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), moveRequest.m_Entity); | CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), moveRequest.m_Entity); | ||||
if (cmpUnitMotion && cmpUnitMotion->IsMoveRequested()) | if (cmpUnitMotion && cmpUnitMotion->IsMoveRequested() && GetEntityId() < moveRequest.m_Entity) | ||||
{ | { | ||||
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | ||||
if (!cmpPosition || !cmpPosition->IsInWorld()) | if (!cmpPosition || !cmpPosition->IsInWorld()) | ||||
return true; // Still return true since we don't need a position for the target to have one. | return true; // Still return true since we don't need a position for the target to have one. | ||||
CFixedVector2D tempPos = out + (out - cmpTargetPosition->GetPreviousPosition2D()) * 2; | CFixedVector2D tempPos = out + (out - cmpTargetPosition->GetPreviousPosition2D()); | ||||
// Check if we anticipate the target to go through us, in which case we shouldn't anticipate | // 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). | // (or e.g. units fleeing might suddenly turn around towards their attacker). | ||||
if ((out - cmpPosition->GetPosition2D()).RelativeOrientation(tempPos - cmpPosition->GetPosition2D()) >= 0) | if ((out - cmpPosition->GetPosition2D()).RelativeOrientation(tempPos - cmpPosition->GetPosition2D()) >= 0) | ||||
out = tempPos; | out = tempPos; | ||||
} | } | ||||
} | } | ||||
return true; | return true; | ||||
▲ Show 20 Lines • Show All 435 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator