Changeset View
Changeset View
Standalone View
Standalone View
source/simulation2/components/CCmpUnitMotion.cpp
Show First 20 Lines • Show All 110 Lines • ▼ Show 20 Lines | public: | ||||
entity_pos_t m_Clearance; | entity_pos_t m_Clearance; | ||||
// cached for efficiency | // cached for efficiency | ||||
fixed m_WalkSpeed, m_RunMultiplier; | fixed m_WalkSpeed, m_RunMultiplier; | ||||
bool m_FacePointAfterMove; | bool m_FacePointAfterMove; | ||||
enum PathState | struct Ticket { | ||||
{ | u32 m_Ticket = 0; // asynchronous request ID we're waiting for, or 0 if none | ||||
/* | enum Type { | ||||
* There is no path. | SHORT_PATH, | ||||
* (This should only happen in IDLE and STOPPING.) | LONG_PATH | ||||
*/ | } m_Type; | ||||
PATHSTATE_NONE, | |||||
/* | |||||
* We have an outstanding long path request. | |||||
* No paths are usable yet, so we can't move anywhere. | |||||
*/ | |||||
PATHSTATE_WAITING_REQUESTING_LONG, | |||||
/* | |||||
* We have an outstanding short path request. | |||||
* m_LongPath is valid. | |||||
* m_ShortPath is not yet valid, so we can't move anywhere. | |||||
*/ | |||||
PATHSTATE_WAITING_REQUESTING_SHORT, | |||||
/* | |||||
* We are following our path, and have no path requests. | |||||
* m_LongPath and m_ShortPath are valid. | |||||
*/ | |||||
PATHSTATE_FOLLOWING, | |||||
/* | |||||
* We are following our path, and have an outstanding long path request. | |||||
* (This is because our target moved a long way and we need to recompute | |||||
* the whole path). | |||||
* m_LongPath and m_ShortPath are valid. | |||||
*/ | |||||
PATHSTATE_FOLLOWING_REQUESTING_LONG, | |||||
/* | |||||
* We are following our path, and have an outstanding short path request. | |||||
* (This is because our target moved and we've got a new long path | |||||
* which we need to follow). | |||||
* m_LongPath is valid; m_ShortPath is valid but obsolete. | |||||
*/ | |||||
PATHSTATE_FOLLOWING_REQUESTING_SHORT, | |||||
PATHSTATE_MAX | |||||
}; | |||||
u8 m_PathState; | |||||
u32 m_ExpectedPathTicket; // asynchronous request ID we're waiting for, or 0 if none | void clear() { m_Ticket = 0; } | ||||
} m_ExpectedPathTicket; | |||||
Stan: Why not Request and m_RequestId ? or PathComputeRequest but that's a bit long. | |||||
struct MoveRequest { | struct MoveRequest { | ||||
enum Type { | enum Type { | ||||
NONE, | NONE, | ||||
POINT, | POINT, | ||||
ENTITY, | ENTITY, | ||||
OFFSET | OFFSET | ||||
} m_Type = NONE; | } m_Type = NONE; | ||||
▲ Show 20 Lines • Show All 71 Lines • ▼ Show 20 Lines | if (cmpPathfinder) | ||||
m_PassClass = cmpPathfinder->GetPassabilityClass(m_PassClassName); | m_PassClass = cmpPathfinder->GetPassabilityClass(m_PassClassName); | ||||
m_Clearance = cmpPathfinder->GetClearance(m_PassClass); | m_Clearance = cmpPathfinder->GetClearance(m_PassClass); | ||||
CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle()); | CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle()); | ||||
if (cmpObstruction) | if (cmpObstruction) | ||||
cmpObstruction->SetUnitClearance(m_Clearance); | cmpObstruction->SetUnitClearance(m_Clearance); | ||||
} | } | ||||
m_PathState = PATHSTATE_NONE; | |||||
m_ExpectedPathTicket = 0; | |||||
m_Tries = 0; | m_Tries = 0; | ||||
m_DebugOverlayEnabled = false; | m_DebugOverlayEnabled = false; | ||||
} | } | ||||
virtual void Deinit() | virtual void Deinit() | ||||
{ | { | ||||
} | } | ||||
template<typename S> | template<typename S> | ||||
void SerializeCommon(S& serialize) | void SerializeCommon(S& serialize) | ||||
{ | { | ||||
serialize.NumberU8("path state", m_PathState, 0, PATHSTATE_MAX-1); | |||||
serialize.StringASCII("pass class", m_PassClassName, 0, 64); | serialize.StringASCII("pass class", m_PassClassName, 0, 64); | ||||
serialize.NumberU32_Unbounded("ticket", m_ExpectedPathTicket); | 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<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 173 Lines • ▼ Show 20 Lines | if (m_FacePointAfterMove) | ||||
{ | { | ||||
CFixedVector2D targetPos; | CFixedVector2D targetPos; | ||||
if (ComputeTargetPosition(targetPos)) | if (ComputeTargetPosition(targetPos)) | ||||
FaceTowardsPointFromPos(cmpPosition->GetPosition2D(), targetPos.X, targetPos.Y); | FaceTowardsPointFromPos(cmpPosition->GetPosition2D(), targetPos.X, targetPos.Y); | ||||
} | } | ||||
} | } | ||||
m_MoveRequest = MoveRequest(); | m_MoveRequest = MoveRequest(); | ||||
m_ExpectedPathTicket = 0; | m_ExpectedPathTicket.clear(); | ||||
m_PathState = PATHSTATE_NONE; | |||||
m_LongPath.m_Waypoints.clear(); | m_LongPath.m_Waypoints.clear(); | ||||
m_ShortPath.m_Waypoints.clear(); | m_ShortPath.m_Waypoints.clear(); | ||||
} | } | ||||
virtual entity_pos_t GetUnitClearance() const | virtual entity_pos_t GetUnitClearance() const | ||||
{ | { | ||||
return m_Clearance; | return m_Clearance; | ||||
} | } | ||||
Show All 10 Lines | bool IsFormationMember() const | ||||
return m_MoveRequest.m_Type == MoveRequest::OFFSET; | return m_MoveRequest.m_Type == MoveRequest::OFFSET; | ||||
} | } | ||||
entity_id_t GetGroup() const | entity_id_t GetGroup() const | ||||
{ | { | ||||
return IsFormationMember() ? m_MoveRequest.m_Entity : GetEntityId(); | return IsFormationMember() ? m_MoveRequest.m_Entity : GetEntityId(); | ||||
} | } | ||||
bool HasValidPath() const | |||||
{ | |||||
return m_PathState == PATHSTATE_FOLLOWING | |||||
|| m_PathState == PATHSTATE_FOLLOWING_REQUESTING_LONG | |||||
|| m_PathState == PATHSTATE_FOLLOWING_REQUESTING_SHORT; | |||||
} | |||||
void MoveFailed() | void MoveFailed() | ||||
{ | { | ||||
CMessageMotionChanged msg(true); | CMessageMotionChanged msg(true); | ||||
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); | GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); | ||||
} | } | ||||
void MoveSucceeded() | void MoveSucceeded() | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 154 Lines • ▼ Show 20 Lines | private: | ||||
void RenderSubmit(SceneCollector& collector); | void RenderSubmit(SceneCollector& collector); | ||||
}; | }; | ||||
REGISTER_COMPONENT_TYPE(UnitMotion) | REGISTER_COMPONENT_TYPE(UnitMotion) | ||||
void CCmpUnitMotion::PathResult(u32 ticket, const WaypointPath& path) | void CCmpUnitMotion::PathResult(u32 ticket, const WaypointPath& path) | ||||
{ | { | ||||
// Ignore obsolete path requests | // Ignore obsolete path requests | ||||
if (ticket != m_ExpectedPathTicket) | if (ticket != m_ExpectedPathTicket.m_Ticket) | ||||
return; | return; | ||||
m_ExpectedPathTicket = 0; // we don't expect to get this result again | m_ExpectedPathTicket.m_Ticket = 0; // Keep ticket metadata | ||||
// Check that we are still able to do something with that path | // Check that we are still able to do something with that path | ||||
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | ||||
if (!cmpPosition || !cmpPosition->IsInWorld()) | if (!cmpPosition || !cmpPosition->IsInWorld()) | ||||
{ | { | ||||
// We will probably fail to move so inform components but keep on trying anyways. | // We will probably fail to move so inform components but keep on trying anyways. | ||||
MoveFailed(); | MoveFailed(); | ||||
return; | return; | ||||
} | } | ||||
if (m_PathState == PATHSTATE_WAITING_REQUESTING_LONG || m_PathState == PATHSTATE_FOLLOWING_REQUESTING_LONG) | if (m_ExpectedPathTicket.m_Type == Ticket::LONG_PATH) | ||||
{ | { | ||||
m_LongPath = path; | m_LongPath = path; | ||||
// If we are following a path, leave the old m_ShortPath so we can carry on following it | |||||
// until a new short path has been computed | |||||
if (m_PathState == PATHSTATE_WAITING_REQUESTING_LONG) | |||||
m_ShortPath.m_Waypoints.clear(); | |||||
// 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), so if we're stuck and the user clicks | // (via the short pathfinder), 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 | ||||
if (m_LongPath.m_Waypoints.empty()) | if (m_LongPath.m_Waypoints.empty()) | ||||
{ | { | ||||
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 }); | ||||
} | } | ||||
m_PathState = PATHSTATE_FOLLOWING; | |||||
} | } | ||||
else if (m_PathState == PATHSTATE_WAITING_REQUESTING_SHORT || m_PathState == PATHSTATE_FOLLOWING_REQUESTING_SHORT) | else | ||||
{ | { | ||||
m_ShortPath = path; | m_ShortPath = path; | ||||
// 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 (m_ShortPath.m_Waypoints.empty()) | if (m_ShortPath.m_Waypoints.empty()) | ||||
{ | { | ||||
// If we're globally following a long path, try to remove the next waypoint, it might be obstructed | // If we're globally following a long path, try to remove the next waypoint, it might be obstructed (e.g. by idle entities) | ||||
// If not, and we are not in a formation, retry | // If not, and we are not in a formation, retry | ||||
// unless we are close to our target and we don't have a target entity. | // unless we are close to our target and we don't have a target entity. | ||||
// This makes sure that units don't clump too much when they are not in a formation and tasked to move. | // This makes sure that units don't clump too much when they are not in a formation and tasked to move. | ||||
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(); | ||||
CMessageMotionChanged msg(false); | CMessageMotionChanged msg(false); | ||||
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); | GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); | ||||
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | ||||
if (!cmpPosition || !cmpPosition->IsInWorld()) | if (!cmpPosition || !cmpPosition->IsInWorld()) | ||||
return; | return; | ||||
CFixedVector2D pos = cmpPosition->GetPosition2D(); | CFixedVector2D pos = cmpPosition->GetPosition2D(); | ||||
if (CloseEnoughFromDestinationToStop(pos)) | if (CloseEnoughFromDestinationToStop(pos)) | ||||
{ | { | ||||
MoveSucceeded(); | MoveSucceeded(); | ||||
return; | return; | ||||
} | } | ||||
PathGoal goal; | PathGoal goal; | ||||
ComputeGoal(goal, m_MoveRequest); | ComputeGoal(goal, m_MoveRequest); | ||||
RequestLongPath(pos, goal); | RequestLongPath(pos, goal); | ||||
m_PathState = PATHSTATE_WAITING_REQUESTING_LONG; | |||||
return; | return; | ||||
} | } | ||||
// else we could, so reset our number of tries. | // else we could, so reset our number of tries. | ||||
m_Tries = 0; | m_Tries = 0; | ||||
m_PathState = PATHSTATE_FOLLOWING; | |||||
} | } | ||||
else | |||||
LOGWARNING("unexpected PathResult (%u %d)", GetEntityId(), m_PathState); | |||||
} | } | ||||
void CCmpUnitMotion::Move(fixed dt) | void CCmpUnitMotion::Move(fixed dt) | ||||
{ | { | ||||
PROFILE("Move"); | PROFILE("Move"); | ||||
// If we were idle and will still be, we can return. | // If we were idle and will still be, we can return. | ||||
// TODO: this will need to be removed if pushing is implemented. | // TODO: this will need to be removed if pushing is implemented. | ||||
Show All 22 Lines | void CCmpUnitMotion::Move(fixed dt) | ||||
CFixedVector2D initialPos = cmpPosition->GetPosition2D(); | CFixedVector2D initialPos = cmpPosition->GetPosition2D(); | ||||
// Keep track of the current unit's position during the update | // Keep track of the current unit's position during the update | ||||
CFixedVector2D pos = initialPos; | CFixedVector2D pos = initialPos; | ||||
// If we're chasing a potentially-moving unit and are currently close | // If we're chasing a potentially-moving unit and are currently close | ||||
// enough to its current position, and we can head in a straight line | // enough to its current position, and we can head in a straight line | ||||
// to it, then throw away our current path and go straight to it | // to it, then throw away our current path and go straight to it | ||||
if (m_PathState == PATHSTATE_FOLLOWING || | |||||
m_PathState == PATHSTATE_FOLLOWING_REQUESTING_SHORT || | |||||
m_PathState == PATHSTATE_FOLLOWING_REQUESTING_LONG) | |||||
TryGoingStraightToTarget(initialPos); | TryGoingStraightToTarget(initialPos); | ||||
bool wasObstructed = PerformMove(dt, m_ShortPath, m_LongPath, pos); | bool wasObstructed = PerformMove(dt, m_ShortPath, m_LongPath, pos); | ||||
// Update our speed over this turn so that the visual actor shows the correct animation. | // Update our speed over this turn so that the visual actor shows the correct animation. | ||||
if (pos == initialPos) | if (pos == initialPos) | ||||
UpdateMovementState(fixed::Zero()); | UpdateMovementState(fixed::Zero()); | ||||
else | else | ||||
{ | { | ||||
// Update the Position component after our movement (if we actually moved anywhere) | // Update the Position component after our movement (if we actually moved anywhere) | ||||
CFixedVector2D offset = pos - initialPos; | CFixedVector2D offset = pos - initialPos; | ||||
// Face towards the target | // Face towards the target | ||||
entity_angle_t angle = atan2_approx(offset.X, offset.Y); | entity_angle_t angle = atan2_approx(offset.X, offset.Y); | ||||
cmpPosition->MoveAndTurnTo(pos.X,pos.Y, angle); | cmpPosition->MoveAndTurnTo(pos.X,pos.Y, angle); | ||||
// Calculate the mean speed over this past turn. | // Calculate the mean speed over this past turn. | ||||
UpdateMovementState(offset.Length() / dt); | UpdateMovementState(offset.Length() / dt); | ||||
} | } | ||||
if (wasObstructed && HandleObstructedMove()) | if (wasObstructed && HandleObstructedMove()) | ||||
return; | return; | ||||
if (m_PathState == PATHSTATE_FOLLOWING) | |||||
{ | |||||
// We may need to recompute our path sometimes (e.g. if our target moves). | // We may need to recompute our path sometimes (e.g. if our target moves). | ||||
// Since we request paths asynchronously anyways, this does not need to be done before moving. | // Since we request paths asynchronously anyways, this does not need to be done before moving. | ||||
if (PathingUpdateNeeded(pos)) | if (PathingUpdateNeeded(pos)) | ||||
{ | { | ||||
PathGoal goal; | PathGoal goal; | ||||
ComputeGoal(goal, m_MoveRequest); | ComputeGoal(goal, m_MoveRequest); | ||||
BeginPathing(pos, goal); | BeginPathing(pos, goal); | ||||
m_PathState = PATHSTATE_FOLLOWING_REQUESTING_LONG; | |||||
} | |||||
} | } | ||||
} | } | ||||
bool CCmpUnitMotion::PossiblyAtDestination() const | bool CCmpUnitMotion::PossiblyAtDestination() const | ||||
{ | { | ||||
if (m_MoveRequest.m_Type == MoveRequest::NONE) | if (m_MoveRequest.m_Type == MoveRequest::NONE) | ||||
return false; | return false; | ||||
Show All 15 Lines | if (m_MoveRequest.m_Type == MoveRequest::OFFSET) | ||||
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | ||||
return cmpObstructionManager->IsInPointRange(GetEntityId(), targetPos.X, targetPos.Y, m_MoveRequest.m_MinRange, m_MoveRequest.m_MaxRange, false); | return cmpObstructionManager->IsInPointRange(GetEntityId(), targetPos.X, targetPos.Y, m_MoveRequest.m_MinRange, m_MoveRequest.m_MaxRange, false); | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
bool CCmpUnitMotion::PerformMove(fixed dt, WaypointPath& shortPath, WaypointPath& longPath, CFixedVector2D& pos) const | bool CCmpUnitMotion::PerformMove(fixed dt, WaypointPath& shortPath, WaypointPath& longPath, CFixedVector2D& pos) const | ||||
{ | { | ||||
if (m_PathState != PATHSTATE_FOLLOWING && | if (shortPath.m_Waypoints.empty() && longPath.m_Waypoints.empty()) | ||||
m_PathState != PATHSTATE_FOLLOWING_REQUESTING_SHORT && | |||||
m_PathState != PATHSTATE_FOLLOWING_REQUESTING_LONG) | |||||
return false; | return false; | ||||
// TODO: there's some asymmetry here when units look at other | // TODO: there's some asymmetry here when units look at other | ||||
// units' positions - the result will depend on the order of execution. | // units' positions - the result will depend on the order of execution. | ||||
// Maybe we should split the updates into multiple phases to minimise | // Maybe we should split the updates into multiple phases to minimise | ||||
// that problem. | // that problem. | ||||
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | ||||
▲ Show 20 Lines • Show All 93 Lines • ▼ Show 20 Lines | if (CloseEnoughFromDestinationToStop(pos)) | ||||
return 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 | ||||
if (!m_LongPath.m_Waypoints.empty()) | if (!m_LongPath.m_Waypoints.empty()) | ||||
{ | { | ||||
PathGoal goal = { PathGoal::POINT, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z }; | PathGoal goal = { PathGoal::POINT, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z }; | ||||
RequestShortPath(pos, goal, true); | RequestShortPath(pos, goal, true); | ||||
m_PathState = PATHSTATE_WAITING_REQUESTING_SHORT; | |||||
return true; | return true; | ||||
} | } | ||||
// Else, just entirely recompute | // Else, just entirely recompute | ||||
PathGoal goal; | PathGoal goal; | ||||
ComputeGoal(goal, m_MoveRequest); | ComputeGoal(goal, m_MoveRequest); | ||||
BeginPathing(pos, goal); | BeginPathing(pos, goal); | ||||
▲ Show 20 Lines • Show All 314 Lines • ▼ Show 20 Lines | else if (moveRequest.m_MaxRange >= fixed::Zero() && distance > moveRequest.m_MaxRange) | ||||
} | } | ||||
} | } | ||||
// Do nothing in particular in case we are already in range. | // Do nothing in particular in case we are already in range. | ||||
return true; | return true; | ||||
} | } | ||||
void CCmpUnitMotion::BeginPathing(const CFixedVector2D& from, const PathGoal& goal) | void CCmpUnitMotion::BeginPathing(const CFixedVector2D& from, const PathGoal& goal) | ||||
{ | { | ||||
m_ExpectedPathTicket = 0; | m_ExpectedPathTicket.clear(); | ||||
m_PathState = PATHSTATE_NONE; | |||||
#if DISABLE_PATHFINDER | #if DISABLE_PATHFINDER | ||||
{ | { | ||||
CmpPtr<ICmpPathfinder> cmpPathfinder (GetSimContext(), SYSTEM_ENTITY); | CmpPtr<ICmpPathfinder> cmpPathfinder (GetSimContext(), SYSTEM_ENTITY); | ||||
CFixedVector2D goalPos = m_FinalGoal.NearestPointOnGoal(from); | CFixedVector2D goalPos = m_FinalGoal.NearestPointOnGoal(from); | ||||
m_LongPath.m_Waypoints.clear(); | m_LongPath.m_Waypoints.clear(); | ||||
m_ShortPath.m_Waypoints.clear(); | m_ShortPath.m_Waypoints.clear(); | ||||
m_ShortPath.m_Waypoints.emplace_back(Waypoint{ goalPos.X, goalPos.Y }); | m_ShortPath.m_Waypoints.emplace_back(Waypoint{ goalPos.X, goalPos.Y }); | ||||
m_PathState = PATHSTATE_FOLLOWING; | |||||
return; | return; | ||||
} | } | ||||
#endif | #endif | ||||
// If the target is close and we can reach it in a straight line, | // If the target is close and we can reach it in a straight line, | ||||
// then we'll just go along the straight line instead of computing a path. | // then we'll just go along the straight line instead of computing a path. | ||||
if (TryGoingStraightToTarget(from)) | if (TryGoingStraightToTarget(from)) | ||||
{ | |||||
m_PathState = PATHSTATE_FOLLOWING; | |||||
return; | return; | ||||
} | |||||
// 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. | ||||
if (goal.DistanceToPoint(from) < LONG_PATH_MIN_DIST) | if (goal.DistanceToPoint(from) < LONG_PATH_MIN_DIST) | ||||
{ | { | ||||
// add our final goal as a long range waypoint so we don't forget | // add our final goal as a long range waypoint so we don't forget | ||||
// where we are going if the short-range pathfinder returns | // where we are going if the short-range pathfinder returns | ||||
// an aborted path. | // an aborted path. | ||||
m_LongPath.m_Waypoints.clear(); | m_LongPath.m_Waypoints.clear(); | ||||
CFixedVector2D target = goal.NearestPointOnGoal(from); | CFixedVector2D target = goal.NearestPointOnGoal(from); | ||||
m_LongPath.m_Waypoints.emplace_back(Waypoint{ target.X, target.Y }); | m_LongPath.m_Waypoints.emplace_back(Waypoint{ target.X, target.Y }); | ||||
m_PathState = PATHSTATE_WAITING_REQUESTING_SHORT; | |||||
RequestShortPath(from, goal, true); | RequestShortPath(from, goal, true); | ||||
} | } | ||||
else | else | ||||
{ | |||||
m_PathState = PATHSTATE_WAITING_REQUESTING_LONG; | |||||
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. | ||||
// this value here seems sensible enough. | // this value here seems sensible enough. | ||||
PathGoal improvedGoal = goal; | PathGoal improvedGoal = goal; | ||||
improvedGoal.maxdist = SHORT_PATH_MIN_SEARCH_RANGE - entity_pos_t::FromInt(1); | improvedGoal.maxdist = SHORT_PATH_MIN_SEARCH_RANGE - entity_pos_t::FromInt(1); | ||||
cmpPathfinder->SetDebugPath(from.X, from.Y, improvedGoal, m_PassClass); | cmpPathfinder->SetDebugPath(from.X, from.Y, improvedGoal, m_PassClass); | ||||
m_ExpectedPathTicket = cmpPathfinder->ComputePathAsync(from.X, from.Y, improvedGoal, m_PassClass, GetEntityId()); | m_ExpectedPathTicket.m_Type = Ticket::LONG_PATH; | ||||
m_ExpectedPathTicket.m_Ticket = cmpPathfinder->ComputePathAsync(from.X, from.Y, improvedGoal, m_PassClass, GetEntityId()); | |||||
} | } | ||||
void CCmpUnitMotion::RequestShortPath(const CFixedVector2D &from, const PathGoal& goal, bool avoidMovingUnits) | void CCmpUnitMotion::RequestShortPath(const CFixedVector2D &from, const PathGoal& goal, bool avoidMovingUnits) | ||||
{ | { | ||||
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | ||||
if (!cmpPathfinder) | if (!cmpPathfinder) | ||||
return; | return; | ||||
// wrapping around on m_Tries isn't really a problem so don't check for overflow. | // wrapping around on m_Tries isn't really a problem so don't check for overflow. | ||||
fixed searchRange = std::max(SHORT_PATH_MIN_SEARCH_RANGE * ++m_Tries, goal.DistanceToPoint(from)); | fixed searchRange = std::max(SHORT_PATH_MIN_SEARCH_RANGE * ++m_Tries, goal.DistanceToPoint(from)); | ||||
if (goal.type != PathGoal::POINT && searchRange < goal.hw && searchRange < SHORT_PATH_MIN_SEARCH_RANGE * 2) | if (goal.type != PathGoal::POINT && searchRange < goal.hw && searchRange < SHORT_PATH_MIN_SEARCH_RANGE * 2) | ||||
searchRange = std::min(goal.hw, SHORT_PATH_MIN_SEARCH_RANGE * 2); | searchRange = std::min(goal.hw, SHORT_PATH_MIN_SEARCH_RANGE * 2); | ||||
if (searchRange > SHORT_PATH_MAX_SEARCH_RANGE) | if (searchRange > SHORT_PATH_MAX_SEARCH_RANGE) | ||||
searchRange = SHORT_PATH_MAX_SEARCH_RANGE; | searchRange = SHORT_PATH_MAX_SEARCH_RANGE; | ||||
m_ExpectedPathTicket = cmpPathfinder->ComputeShortPathAsync(from.X, from.Y, m_Clearance, searchRange, goal, m_PassClass, avoidMovingUnits, GetGroup(), GetEntityId()); | m_ExpectedPathTicket.m_Type = Ticket::SHORT_PATH; | ||||
m_ExpectedPathTicket.m_Ticket = cmpPathfinder->ComputeShortPathAsync(from.X, from.Y, m_Clearance, searchRange, goal, m_PassClass, avoidMovingUnits, GetGroup(), GetEntityId()); | |||||
} | } | ||||
bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) | bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) | ||||
{ | { | ||||
PROFILE("MoveToPointRange"); | PROFILE("MoveToPointRange"); | ||||
MoveRequest moveRequest(CFixedVector2D(x, z), minRange, maxRange); | MoveRequest moveRequest(CFixedVector2D(x, z), minRange, maxRange); | ||||
▲ Show 20 Lines • Show All 102 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator
Why not Request and m_RequestId ? or PathComputeRequest but that's a bit long.