Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp
Show First 20 Lines • Show All 786 Lines • ▼ Show 20 Lines | if (m_State == STATE_STOPPING) | ||||
m_State = STATE_IDLE; | m_State = STATE_IDLE; | ||||
MoveSucceeded(); | MoveSucceeded(); | ||||
return; | return; | ||||
} | } | ||||
if (m_State == STATE_IDLE) | if (m_State == STATE_IDLE) | ||||
return; | return; | ||||
switch (m_PathState) | if (m_PathState == PATHSTATE_NONE || | ||||
{ | m_PathState == PATHSTATE_WAITING_REQUESTING_LONG || | ||||
case PATHSTATE_NONE: | m_PathState == PATHSTATE_WAITING_REQUESTING_SHORT) | ||||
{ | |||||
// If we're not pathing, do nothing | |||||
return; | return; | ||||
} | |||||
case PATHSTATE_WAITING_REQUESTING_LONG: | CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | ||||
case PATHSTATE_WAITING_REQUESTING_SHORT: | if (!cmpPosition || !cmpPosition->IsInWorld()) | ||||
{ | |||||
// If we're waiting for a path and don't have one yet, do nothing | |||||
return; | return; | ||||
} | |||||
case PATHSTATE_FOLLOWING: | CFixedVector2D initialPos = cmpPosition->GetPosition2D(); | ||||
case PATHSTATE_FOLLOWING_REQUESTING_SHORT: | |||||
case PATHSTATE_FOLLOWING_REQUESTING_LONG: | // Keep track of the current unit's position during the update | ||||
CFixedVector2D pos = initialPos; | |||||
bool wasObstructed = false; | |||||
if (m_PathState == PATHSTATE_FOLLOWING || | |||||
m_PathState == PATHSTATE_FOLLOWING_REQUESTING_SHORT || | |||||
m_PathState == PATHSTATE_FOLLOWING_REQUESTING_LONG) | |||||
{ | { | ||||
// 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()); | ||||
if (!cmpPathfinder) | if (!cmpPathfinder) | ||||
return; | return; | ||||
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | |||||
if (!cmpPosition || !cmpPosition->IsInWorld()) | |||||
return; | |||||
CFixedVector2D initialPos = cmpPosition->GetPosition2D(); | |||||
// 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) | if (m_PathState == PATHSTATE_FOLLOWING) | ||||
TryGoingStraightToTargetEntity(initialPos); | TryGoingStraightToTargetEntity(initialPos); | ||||
// Keep track of the current unit's position during the update | |||||
CFixedVector2D pos = initialPos; | |||||
fixed basicSpeed = m_Speed; | fixed basicSpeed = m_Speed; | ||||
// If in formation, run to keep up; otherwise just walk | // If in formation, run to keep up; otherwise just walk | ||||
if (IsFormationMember()) | if (IsFormationMember()) | ||||
basicSpeed = m_Speed.Multiply(m_RunMultiplier); | basicSpeed = m_Speed.Multiply(m_RunMultiplier); | ||||
// Find the speed factor of the underlying terrain | // Find the speed factor of the underlying terrain | ||||
// (We only care about the tile we start on - it doesn't matter if we're moving | // (We only care about the tile we start on - it doesn't matter if we're moving | ||||
// partially onto a much slower/faster tile) | // partially onto a much slower/faster tile) | ||||
// TODO: Terrain-dependent speeds are not currently supported | // TODO: Terrain-dependent speeds are not currently supported | ||||
fixed terrainSpeed = fixed::FromInt(1); | fixed terrainSpeed = fixed::FromInt(1); | ||||
fixed maxSpeed = basicSpeed.Multiply(terrainSpeed); | fixed maxSpeed = basicSpeed.Multiply(terrainSpeed); | ||||
bool wasObstructed = false; | |||||
// We want to move (at most) maxSpeed*dt units from pos towards the next waypoint | // We want to move (at most) maxSpeed*dt units from pos towards the next waypoint | ||||
fixed timeLeft = dt; | fixed timeLeft = dt; | ||||
fixed zero = fixed::Zero(); | fixed zero = fixed::Zero(); | ||||
while (timeLeft > zero) | while (timeLeft > zero) | ||||
{ | { | ||||
// If we ran out of path, we have to stop | // If we ran out of path, we have to stop | ||||
▲ Show 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | while (timeLeft > zero) | ||||
if (cmpPathfinder->CheckMovement(GetObstructionFilter(), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass)) | if (cmpPathfinder->CheckMovement(GetObstructionFilter(), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass)) | ||||
pos = target; | pos = target; | ||||
else | else | ||||
wasObstructed = true; // Error - path was obstructed | wasObstructed = true; // Error - path was obstructed | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | |||||
// Update the Position component after our movement (if we actually moved anywhere) | // Update the Position component after our movement (if we actually moved anywhere) | ||||
if (pos != initialPos) | if (pos != initialPos) | ||||
{ | { | ||||
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. | ||||
m_CurSpeed = cmpPosition->GetDistanceTravelled() / dt; | m_CurSpeed = cmpPosition->GetDistanceTravelled() / dt; | ||||
} | } | ||||
if (wasObstructed) | if (wasObstructed) | ||||
{ | { | ||||
// Oops, we hit something (very likely another unit). | // Oops, we hit something (very likely another unit). | ||||
// This is when we might easily get stuck wrongly. | // This is when we might easily get stuck wrongly. | ||||
// check if we've arrived. | // check if we've arrived. | ||||
if (CloseEnoughFromDestinationToStop(pos)) | if (CloseEnoughFromDestinationToStop(pos)) | ||||
{ | { | ||||
MoveSucceeded(); | MoveSucceeded(); | ||||
return; | return; | ||||
} | } | ||||
// If we still have long waypoints, try and compute a short path | // If we still have long waypoints, try and compute a short path | ||||
// This will get us around units, amongst others. | // This will get us around units, amongst others. | ||||
// However in some cases a long waypoint will be in located in the obstruction of | // However in some cases a long waypoint will be in located in the obstruction of | ||||
// an idle unit. In that case, we need to scrap that waypoint or we might never be able to reach it. | // an idle unit. In that case, we need to scrap that waypoint or we might never be able to reach it. | ||||
// I am not sure why this happens but the following code seems to work. | // I am not sure why this happens but the following code seems to work. | ||||
if (!m_LongPath.m_Waypoints.empty()) | if (!m_LongPath.m_Waypoints.empty()) | ||||
{ | { | ||||
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); | CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); | ||||
if (cmpObstructionManager) | if (cmpObstructionManager) | ||||
{ | { | ||||
// create a fake obstruction to represent our waypoint. | // create a fake obstruction to represent our waypoint. | ||||
ICmpObstructionManager::ObstructionSquare square; | ICmpObstructionManager::ObstructionSquare square; | ||||
square.hh = m_Clearance; | square.hh = m_Clearance; | ||||
square.hw = m_Clearance; | square.hw = m_Clearance; | ||||
square.u = CFixedVector2D(entity_pos_t::FromInt(1),entity_pos_t::FromInt(0)); | square.u = CFixedVector2D(entity_pos_t::FromInt(1),entity_pos_t::FromInt(0)); | ||||
square.v = CFixedVector2D(entity_pos_t::FromInt(0),entity_pos_t::FromInt(1)); | square.v = CFixedVector2D(entity_pos_t::FromInt(0),entity_pos_t::FromInt(1)); | ||||
square.x = m_LongPath.m_Waypoints.back().x; | square.x = m_LongPath.m_Waypoints.back().x; | ||||
square.z = m_LongPath.m_Waypoints.back().z; | square.z = m_LongPath.m_Waypoints.back().z; | ||||
std::vector<entity_id_t> unitOnGoal; | std::vector<entity_id_t> unitOnGoal; | ||||
// don't ignore moving units as those might be units like us, ie not really moving. | // don't ignore moving units as those might be units like us, ie not really moving. | ||||
cmpObstructionManager->GetUnitsOnObstruction(square, unitOnGoal, GetObstructionFilter(), true); | cmpObstructionManager->GetUnitsOnObstruction(square, unitOnGoal, GetObstructionFilter(), true); | ||||
if (!unitOnGoal.empty()) | if (!unitOnGoal.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()) | ||||
{ | { | ||||
PathGoal goal; | PathGoal goal; | ||||
if (m_LongPath.m_Waypoints.size() > 1 || m_FinalGoal.DistanceToPoint(pos) > LONG_PATH_MIN_DIST) | if (m_LongPath.m_Waypoints.size() > 1 || m_FinalGoal.DistanceToPoint(pos) > LONG_PATH_MIN_DIST) | ||||
goal = { PathGoal::POINT, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z }; | goal = { PathGoal::POINT, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z }; | ||||
else | else | ||||
{ | { | ||||
UpdateFinalGoal(); | UpdateFinalGoal(); | ||||
goal = m_FinalGoal; | goal = m_FinalGoal; | ||||
m_LongPath.m_Waypoints.clear(); | m_LongPath.m_Waypoints.clear(); | ||||
CFixedVector2D target = goal.NearestPointOnGoal(pos); | CFixedVector2D target = goal.NearestPointOnGoal(pos); | ||||
m_LongPath.m_Waypoints.emplace_back(Waypoint{ target.X, target.Y }); | m_LongPath.m_Waypoints.emplace_back(Waypoint{ target.X, target.Y }); | ||||
} | } | ||||
RequestShortPath(pos, goal, true); | RequestShortPath(pos, goal, true); | ||||
m_PathState = PATHSTATE_WAITING_REQUESTING_SHORT; | m_PathState = PATHSTATE_WAITING_REQUESTING_SHORT; | ||||
return; | return; | ||||
} | } | ||||
} | } | ||||
// Else, just entirely recompute | // Else, just entirely recompute | ||||
UpdateFinalGoal(); | UpdateFinalGoal(); | ||||
BeginPathing(pos, m_FinalGoal); | BeginPathing(pos, m_FinalGoal); | ||||
// 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; | return; | ||||
} | } | ||||
// We successfully moved along our path, until running out of | // We successfully moved along our path, until running out of | ||||
// waypoints or time. | // waypoints or time. | ||||
if (m_PathState == PATHSTATE_FOLLOWING) | if (m_PathState == PATHSTATE_FOLLOWING) | ||||
{ | { | ||||
// If we're not currently computing any new paths: | // If we're not currently computing any new paths: | ||||
if (m_LongPath.m_Waypoints.empty() && m_ShortPath.m_Waypoints.empty()) | if (m_LongPath.m_Waypoints.empty() && m_ShortPath.m_Waypoints.empty()) | ||||
{ | { | ||||
if (IsFormationMember()) | if (IsFormationMember()) | ||||
{ | { | ||||
// We've reached our assigned position. If the controller | // We've reached our assigned position. If the controller | ||||
// is idle, send a notification in case it should disband, | // is idle, send a notification in case it should disband, | ||||
// otherwise continue following the formation next turn. | // otherwise continue following the formation next turn. | ||||
CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), m_MoveRequest.m_Entity); | CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), m_MoveRequest.m_Entity); | ||||
if (cmpUnitMotion && !cmpUnitMotion->IsMoving()) | if (cmpUnitMotion && !cmpUnitMotion->IsMoving()) | ||||
{ | { | ||||
CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle()); | CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle()); | ||||
if (cmpObstruction) | if (cmpObstruction) | ||||
cmpObstruction->SetMovingFlag(false); | cmpObstruction->SetMovingFlag(false); | ||||
CMessageMotionChanged msg(false); | CMessageMotionChanged msg(false); | ||||
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); | GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); | ||||
return; | |||||
} | } | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); | CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); | ||||
CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), m_MoveRequest.m_Entity); | CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), m_MoveRequest.m_Entity); | ||||
if (!cmpUnitMotion || cmpObstructionManager->IsInTargetRange(GetEntityId(), m_MoveRequest.m_Entity, m_MoveRequest.m_MinRange, m_MoveRequest.m_MaxRange, false)) | if (!cmpUnitMotion || cmpObstructionManager->IsInTargetRange(GetEntityId(), m_MoveRequest.m_Entity, m_MoveRequest.m_MinRange, m_MoveRequest.m_MaxRange, false)) | ||||
{ | { | ||||
m_State = STATE_IDLE; | m_State = STATE_IDLE; | ||||
MoveSucceeded(); | MoveSucceeded(); | ||||
if (m_FacePointAfterMove) | if (m_FacePointAfterMove) | ||||
FaceTowardsPointFromPos(pos, m_FinalGoal.x, m_FinalGoal.z); | FaceTowardsPointFromPos(pos, m_FinalGoal.x, m_FinalGoal.z); | ||||
// TODO: if the goal was a square building, we ought to point towards the | // TODO: if the goal was a square building, we ought to point towards the | ||||
// nearest point on the square, not towards its center | // nearest point on the square, not towards its center | ||||
} | } | ||||
} | } | ||||
} | } | ||||
// If we have a target entity, and we're not miles away from the end of | // If we have a target entity, and we're not miles away from the end of | ||||
// our current path, and the target moved enough, then recompute our | // our current path, and the target moved enough, then recompute our | ||||
// whole path | // whole path | ||||
if (IsFormationMember()) | if (IsFormationMember()) | ||||
CheckTargetMovement(pos, CHECK_TARGET_MOVEMENT_MIN_DELTA_FORMATION); | CheckTargetMovement(pos, CHECK_TARGET_MOVEMENT_MIN_DELTA_FORMATION); | ||||
else | else | ||||
CheckTargetMovement(pos, CHECK_TARGET_MOVEMENT_MIN_DELTA); | CheckTargetMovement(pos, CHECK_TARGET_MOVEMENT_MIN_DELTA); | ||||
} | } | ||||
} | } | ||||
} | |||||
} | |||||
bool CCmpUnitMotion::ComputeTargetPosition(CFixedVector2D& out) const | bool CCmpUnitMotion::ComputeTargetPosition(CFixedVector2D& out) const | ||||
{ | { | ||||
if (m_MoveRequest.m_Entity == INVALID_ENTITY) | if (m_MoveRequest.m_Entity == INVALID_ENTITY) | ||||
return false; | return false; | ||||
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_MoveRequest.m_Entity); | CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_MoveRequest.m_Entity); | ||||
if (!cmpPosition || !cmpPosition->IsInWorld()) | if (!cmpPosition || !cmpPosition->IsInWorld()) | ||||
▲ Show 20 Lines • Show All 602 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator