Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp
Show First 20 Lines • Show All 131 Lines • ▼ Show 20 Lines | public: | ||||
bool m_FacePointAfterMove; | bool m_FacePointAfterMove; | ||||
// Number of path computations that failed (in a row). | // Number of path computations that failed (in a row). | ||||
// When this gets above MAX_FAILED_PATH_COMPUTATIONS, inform other components | // When this gets above MAX_FAILED_PATH_COMPUTATIONS, inform other components | ||||
// that the move will likely fail. | // that the move will likely fail. | ||||
u8 m_FailedPathComputations = 0; | u8 m_FailedPathComputations = 0; | ||||
// If true, PathingUpdateNeeded returns false always. | // If true, PathingUpdateNeeded returns false always. | ||||
// This is an optimisation against unreachable goals, where otherwise we would always | // This exists because the goal may be unreachable to the short/long pathfinder. | ||||
// be recomputing a path. | // In such cases, we would compute inacceptable paths and PathingUpdateNeeded would trigger every turn. | ||||
bool m_PretendLongPathIsCorrect = false; | // To avoid that, when we know the new path is imperfect, treat it as OK and follow it until the end. | ||||
// When reaching the end, we'll run through HandleObstructedMove and this will be reset. | |||||
bool m_FollowKnownImperfectPath = false; | |||||
struct Ticket { | struct Ticket { | ||||
u32 m_Ticket = 0; // asynchronous request ID we're waiting for, or 0 if none | u32 m_Ticket = 0; // asynchronous request ID we're waiting for, or 0 if none | ||||
enum Type { | enum Type { | ||||
SHORT_PATH, | SHORT_PATH, | ||||
LONG_PATH | LONG_PATH | ||||
} m_Type = SHORT_PATH; // Pick some default value to avoid UB. | } m_Type = SHORT_PATH; // Pick some default value to avoid UB. | ||||
▲ Show 20 Lines • Show All 94 Lines • ▼ Show 20 Lines | public: | ||||
void SerializeCommon(S& serialize) | void SerializeCommon(S& serialize) | ||||
{ | { | ||||
serialize.StringASCII("pass class", m_PassClassName, 0, 64); | serialize.StringASCII("pass class", m_PassClassName, 0, 64); | ||||
serialize.NumberU32_Unbounded("ticket", m_ExpectedPathTicket.m_Ticket); | serialize.NumberU32_Unbounded("ticket", m_ExpectedPathTicket.m_Ticket); | ||||
SerializeU8_Enum<Ticket::Type, Ticket::Type::LONG_PATH>()(serialize, "ticket type", m_ExpectedPathTicket.m_Type); | SerializeU8_Enum<Ticket::Type, Ticket::Type::LONG_PATH>()(serialize, "ticket type", m_ExpectedPathTicket.m_Type); | ||||
serialize.NumberU8("failed path computations", m_FailedPathComputations, 0, 255); | serialize.NumberU8("failed path computations", m_FailedPathComputations, 0, 255); | ||||
serialize.Bool("pretendLongPathIsCorrect", m_PretendLongPathIsCorrect); | serialize.Bool("followknownimperfectpath", m_FollowKnownImperfectPath); | ||||
SerializeU8_Enum<MoveRequest::Type, MoveRequest::Type::OFFSET>()(serialize, "target type", m_MoveRequest.m_Type); | SerializeU8_Enum<MoveRequest::Type, MoveRequest::Type::OFFSET>()(serialize, "target type", m_MoveRequest.m_Type); | ||||
serialize.NumberU32_Unbounded("target entity", m_MoveRequest.m_Entity); | serialize.NumberU32_Unbounded("target entity", m_MoveRequest.m_Entity); | ||||
serialize.NumberFixed_Unbounded("target pos x", m_MoveRequest.m_Position.X); | serialize.NumberFixed_Unbounded("target pos x", m_MoveRequest.m_Position.X); | ||||
serialize.NumberFixed_Unbounded("target pos y", m_MoveRequest.m_Position.Y); | serialize.NumberFixed_Unbounded("target pos y", m_MoveRequest.m_Position.Y); | ||||
serialize.NumberFixed_Unbounded("target min range", m_MoveRequest.m_MinRange); | serialize.NumberFixed_Unbounded("target min range", m_MoveRequest.m_MinRange); | ||||
serialize.NumberFixed_Unbounded("target max range", m_MoveRequest.m_MaxRange); | serialize.NumberFixed_Unbounded("target max range", m_MoveRequest.m_MaxRange); | ||||
▲ Show 20 Lines • Show All 255 Lines • ▼ Show 20 Lines | private: | ||||
} | } | ||||
/** | /** | ||||
* If path would take us farther away from the goal than pos currently is, return false, else return true. | * If path would take us farther away from the goal than pos currently is, return false, else return true. | ||||
*/ | */ | ||||
bool RejectFartherPaths(const PathGoal& goal, const WaypointPath& path, const CFixedVector2D& pos) const; | bool RejectFartherPaths(const PathGoal& goal, const WaypointPath& path, const CFixedVector2D& pos) const; | ||||
/** | /** | ||||
* If there are 2 waypoints of more remaining in longPath, return SHORT_PATH_LONG_WAYPOINT_RANGE. | |||||
* Otherwise the pathing should be exact. | |||||
*/ | |||||
entity_pos_t ShortPathWaypointRange(const WaypointPath& longPath) const | |||||
{ | |||||
return longPath.m_Waypoints.size() >= 2 ? SHORT_PATH_LONG_WAYPOINT_RANGE : entity_pos_t::Zero(); | |||||
} | |||||
bool InShortPathRange(const PathGoal& goal, const CFixedVector2D& pos) const | |||||
{ | |||||
return goal.DistanceToPoint(pos) < LONG_PATH_MIN_DIST; | |||||
} | |||||
/** | |||||
* 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); | ||||
/** | /** | ||||
* Do the per-turn movement and other updates. | * Do the per-turn movement and other updates. | ||||
*/ | */ | ||||
void Move(fixed dt); | void Move(fixed dt); | ||||
▲ Show 20 Lines • Show All 151 Lines • ▼ Show 20 Lines | if (ticketType == Ticket::LONG_PATH) | ||||
if (RejectFartherPaths(goal, path, pos)) | if (RejectFartherPaths(goal, path, pos)) | ||||
{ | { | ||||
IncrementFailedPathComputationAndMaybeNotify(); | IncrementFailedPathComputationAndMaybeNotify(); | ||||
return; | return; | ||||
} | } | ||||
m_LongPath = path; | m_LongPath = path; | ||||
m_PretendLongPathIsCorrect = false; | m_FollowKnownImperfectPath = false; | ||||
// If there's no waypoints then we couldn't get near the target. | // If there's no waypoints then we couldn't get near the target. | ||||
// Sort of hack: Just try going directly to the goal point instead | // Sort of hack: Just try going directly to the goal point instead | ||||
// (via the short pathfinder over the next turns), so if we're stuck and the user clicks | // (via the short pathfinder over the next turns), so if we're stuck and the user clicks | ||||
// close enough to the unit then we can probably get unstuck | // close enough to the unit then we can probably get unstuck | ||||
// NB: this relies on HandleObstructedMove requesting short paths if we still have long waypoints. | // NB: this relies on HandleObstructedMove requesting short paths if we still have long waypoints. | ||||
if (m_LongPath.m_Waypoints.empty()) | if (m_LongPath.m_Waypoints.empty()) | ||||
{ | { | ||||
IncrementFailedPathComputationAndMaybeNotify(); | IncrementFailedPathComputationAndMaybeNotify(); | ||||
CFixedVector2D targetPos; | CFixedVector2D targetPos; | ||||
if (ComputeTargetPosition(targetPos)) | if (ComputeTargetPosition(targetPos)) | ||||
m_LongPath.m_Waypoints.emplace_back(Waypoint{ targetPos.X, targetPos.Y }); | m_LongPath.m_Waypoints.emplace_back(Waypoint{ targetPos.X, targetPos.Y }); | ||||
} | } | ||||
// If this new path won't put us in range, it's highly likely that we are going somewhere unreachable. | // If this new path won't put us in range, it's highly likely that we are going somewhere unreachable. | ||||
// This means we will try to recompute the path every turn. | // This means we will try to recompute the path every turn. | ||||
// To avoid this, act as if our current path leads us to the correct destination. | // To avoid this, act as if our current path leads us to the correct destination. | ||||
// (we will still fail the move when we arrive to the best possible position, and if we were blocked by | // (we will still fail the move when we arrive to the best possible position, and if we were blocked by | ||||
// an obstruction and it goes away we will notice when getting there as having no waypoint goes through | // an obstruction and it goes away we will notice when getting there as having no waypoint goes through | ||||
// HandleObstructedMove, so this is safe). | // HandleObstructedMove, so this is safe). | ||||
// TODO: For now, we won't warn components straight away as that could lead to units idling earlier than expected, | // TODO: For now, we won't warn components straight away as that could lead to units idling earlier than expected, | ||||
// but it should be done someday when the message can differentiate between different failure causes. | // but it should be done someday when the message can differentiate between different failure causes. | ||||
else if (PathingUpdateNeeded(pos)) | else if (PathingUpdateNeeded(pos)) | ||||
m_PretendLongPathIsCorrect = true; | m_FollowKnownImperfectPath = true; | ||||
return; | return; | ||||
} | } | ||||
// Reject new short paths if they were aiming at the goal directly (i.e. no long waypoints still exists). | // Reject new short paths if they were aiming at the goal directly (i.e. no long waypoints still exists). | ||||
if (m_LongPath.m_Waypoints.empty() && RejectFartherPaths(goal, path, pos)) | if (m_LongPath.m_Waypoints.empty() && RejectFartherPaths(goal, path, pos)) | ||||
{ | { | ||||
IncrementFailedPathComputationAndMaybeNotify(); | IncrementFailedPathComputationAndMaybeNotify(); | ||||
return; | return; | ||||
} | } | ||||
m_ShortPath = path; | m_ShortPath = path; | ||||
m_FollowKnownImperfectPath = false; | |||||
if (!m_ShortPath.m_Waypoints.empty()) | if (!m_ShortPath.m_Waypoints.empty()) | ||||
{ | |||||
if (PathingUpdateNeeded(pos)) | |||||
m_FollowKnownImperfectPath = true; | |||||
return; | return; | ||||
} | |||||
if (m_FailedPathComputations >= 1) | if (m_FailedPathComputations >= 1) | ||||
{ | { | ||||
// Inform other components - we might be ordered to stop, and computeGoal will then fail and return early. | // Inform other components - we might be ordered to stop, and computeGoal will then fail and return early. | ||||
CMessageMotionUpdate msg(CMessageMotionUpdate::OBSTRUCTED); | CMessageMotionUpdate msg(CMessageMotionUpdate::OBSTRUCTED); | ||||
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); | GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); | ||||
} | } | ||||
// Don't notify if we are a formation member - we can occasionally be stuck for a long time | // Don't notify if we are a formation member - we can occasionally be stuck for a long time | ||||
// if our current offset is unreachable. | // if our current offset is unreachable. | ||||
if (!IsFormationMember()) | if (!IsFormationMember()) | ||||
IncrementFailedPathComputationAndMaybeNotify(); | IncrementFailedPathComputationAndMaybeNotify(); | ||||
// If there's no waypoints then we couldn't get near the target | // If there's no waypoints then we couldn't get near the target | ||||
// If we're globally following a long path, try to remove the next waypoint, | // If we're globally following a long path, try to remove the next waypoint, | ||||
// it might be obstructed (e.g. by idle entities which the long-range pathfinder doesn't see). | // it might be obstructed (e.g. by idle entities which the long-range pathfinder doesn't see). | ||||
if (!m_LongPath.m_Waypoints.empty()) | if (!m_LongPath.m_Waypoints.empty()) | ||||
{ | { | ||||
m_LongPath.m_Waypoints.pop_back(); | m_LongPath.m_Waypoints.pop_back(); | ||||
if (!m_LongPath.m_Waypoints.empty()) | if (!m_LongPath.m_Waypoints.empty()) | ||||
{ | { | ||||
// Get close enough - this will likely help the short path efficiency, and if we end up taking a wrong way | // Get close enough - this will likely help the short path efficiency, and if we end up taking a wrong way | ||||
// we'll easily be able to revert it using a long path. | // we'll easily be able to revert it using a long path. | ||||
PathGoal goal = { PathGoal::CIRCLE, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z, SHORT_PATH_LONG_WAYPOINT_RANGE }; | PathGoal goal = { PathGoal::CIRCLE, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z, ShortPathWaypointRange(m_LongPath) }; | ||||
RequestShortPath(pos, goal, true); | RequestShortPath(pos, goal, true); | ||||
return; | return; | ||||
} | } | ||||
} | } | ||||
ComputePathToGoal(pos, goal); | ComputePathToGoal(pos, goal); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 227 Lines • ▼ Show 20 Lines | bool CCmpUnitMotion::HandleObstructedMove() | ||||
CFixedVector2D pos = cmpPosition->GetPosition2D(); | CFixedVector2D pos = cmpPosition->GetPosition2D(); | ||||
// Oops, we hit something (very likely another unit). | // Oops, we hit something (very likely another unit). | ||||
PathGoal goal; | PathGoal goal; | ||||
if (!ComputeGoal(goal, m_MoveRequest)) | if (!ComputeGoal(goal, m_MoveRequest)) | ||||
return false; | return false; | ||||
// If close enough, just compute a short path to the goal | if (!InShortPathRange(goal, pos)) | ||||
if (goal.DistanceToPoint(pos) < LONG_PATH_MIN_DIST) | |||||
{ | { | ||||
m_LongPath.m_Waypoints.clear(); | |||||
RequestShortPath(pos, goal, true); | |||||
return true; | |||||
} | |||||
// If we still have long waypoints, try and compute a short path. | // If we still have long waypoints, try and compute a short path. | ||||
// Assume the next waypoint is impassable | // Assume the next waypoint is impassable | ||||
if (m_LongPath.m_Waypoints.size() > 1) | if (m_LongPath.m_Waypoints.size() > 1) | ||||
m_LongPath.m_Waypoints.pop_back(); | m_LongPath.m_Waypoints.pop_back(); | ||||
if (!m_LongPath.m_Waypoints.empty()) | if (!m_LongPath.m_Waypoints.empty()) | ||||
{ | { | ||||
// Get close enough - this will likely help the short path efficiency, and if we end up taking a wrong way | // Get close enough - this will likely help the short path efficiency, and if we end up taking a wrong way | ||||
// we'll easily be able to revert it using a long path. | // we'll easily be able to revert it using a long path. | ||||
PathGoal goal = { PathGoal::CIRCLE, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z, SHORT_PATH_LONG_WAYPOINT_RANGE }; | PathGoal goal = { PathGoal::CIRCLE, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z, ShortPathWaypointRange(m_LongPath) }; | ||||
RequestShortPath(pos, goal, true); | RequestShortPath(pos, goal, true); | ||||
return true; | return true; | ||||
} | } | ||||
// Else, just entirely recompute | } | ||||
// Else, just entirely recompute. This will ensure we occasionally run a long path so avoid getting stuck | |||||
// in the short pathfinder, which can happen when an entity is right ober an obstruction's edge. | |||||
ComputePathToGoal(pos, goal); | ComputePathToGoal(pos, goal); | ||||
// potential TODO: We could switch the short-range pathfinder for something else entirely. | // potential TODO: We could switch the short-range pathfinder for something else entirely. | ||||
return true; | return true; | ||||
} | } | ||||
bool CCmpUnitMotion::TargetHasValidPosition(const MoveRequest& moveRequest) const | bool CCmpUnitMotion::TargetHasValidPosition(const MoveRequest& moveRequest) const | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 94 Lines • ▼ Show 20 Lines | |||||
{ | { | ||||
if (m_MoveRequest.m_Type == MoveRequest::NONE) | if (m_MoveRequest.m_Type == MoveRequest::NONE) | ||||
return false; | return false; | ||||
CFixedVector2D targetPos; | CFixedVector2D targetPos; | ||||
if (!ComputeTargetPosition(targetPos)) | if (!ComputeTargetPosition(targetPos)) | ||||
return false; | return false; | ||||
if (m_PretendLongPathIsCorrect) | if (m_FollowKnownImperfectPath) | ||||
return false; | return false; | ||||
if (PossiblyAtDestination()) | if (PossiblyAtDestination()) | ||||
return false; | return false; | ||||
// Get the obstruction shape and translate it where we estimate the target to be. | // Get the obstruction shape and translate it where we estimate the target to be. | ||||
ICmpObstructionManager::ObstructionSquare estimatedTargetShape; | ICmpObstructionManager::ObstructionSquare estimatedTargetShape; | ||||
if (m_MoveRequest.m_Type == MoveRequest::ENTITY) | if (m_MoveRequest.m_Type == MoveRequest::ENTITY) | ||||
▲ Show 20 Lines • Show All 218 Lines • ▼ Show 20 Lines | #endif | ||||
// Otherwise we need to compute a path. | // Otherwise we need to compute a path. | ||||
// If it's close then just do a short path, not a long path | // If it's close then just do a short path, not a long path | ||||
// TODO: If it's close on the opposite side of a river then we really | // TODO: If it's close on the opposite side of a river then we really | ||||
// need a long path, so we shouldn't simply check linear distance | // need a long path, so we shouldn't simply check linear distance | ||||
// the check is arbitrary but should be a reasonably small distance. | // the check is arbitrary but should be a reasonably small distance. | ||||
// To avoid getting stuck because the short-range pathfinder is bounded, occasionally compute a long path instead. | // To avoid getting stuck because the short-range pathfinder is bounded, occasionally compute a long path instead. | ||||
if (m_FailedPathComputations != MAX_FAILED_PATH_COMPUTATIONS_BEFORE_LONG_PATH && goal.DistanceToPoint(from) < LONG_PATH_MIN_DIST) | if (m_FailedPathComputations != MAX_FAILED_PATH_COMPUTATIONS_BEFORE_LONG_PATH && InShortPathRange(goal, from)) | ||||
{ | { | ||||
m_LongPath.m_Waypoints.clear(); | m_LongPath.m_Waypoints.clear(); | ||||
RequestShortPath(from, goal, true); | RequestShortPath(from, goal, true); | ||||
} | } | ||||
else | else | ||||
{ | |||||
m_ShortPath.m_Waypoints.clear(); | |||||
RequestLongPath(from, goal); | RequestLongPath(from, goal); | ||||
} | } | ||||
} | |||||
void CCmpUnitMotion::RequestLongPath(const CFixedVector2D& from, const PathGoal& goal) | void CCmpUnitMotion::RequestLongPath(const CFixedVector2D& from, const PathGoal& goal) | ||||
{ | { | ||||
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | ||||
if (!cmpPathfinder) | if (!cmpPathfinder) | ||||
return; | return; | ||||
// this is by how much our waypoints will be apart at most. | // this is by how much our waypoints will be apart at most. | ||||
Show All 30 Lines | if (!cmpPosition || !cmpPosition->IsInWorld()) | ||||
return false; | return false; | ||||
PathGoal goal; | PathGoal goal; | ||||
if (!ComputeGoal(goal, request)) | if (!ComputeGoal(goal, request)) | ||||
return false; | return false; | ||||
m_MoveRequest = request; | m_MoveRequest = request; | ||||
m_FailedPathComputations = 0; | m_FailedPathComputations = 0; | ||||
m_PretendLongPathIsCorrect = false; | m_FollowKnownImperfectPath = false; | ||||
ComputePathToGoal(cmpPosition->GetPosition2D(), goal); | ComputePathToGoal(cmpPosition->GetPosition2D(), goal); | ||||
return true; | return true; | ||||
} | } | ||||
void CCmpUnitMotion::RenderPath(const WaypointPath& path, std::vector<SOverlayLine>& lines, CColor color) | void CCmpUnitMotion::RenderPath(const WaypointPath& path, std::vector<SOverlayLine>& lines, CColor color) | ||||
{ | { | ||||
bool floating = false; | bool floating = false; | ||||
Show All 40 Lines |
Wildfire Games · Phabricator