Index: source/simulation2/components/CCmpUnitMotion.cpp =================================================================== --- source/simulation2/components/CCmpUnitMotion.cpp +++ source/simulation2/components/CCmpUnitMotion.cpp @@ -241,8 +241,6 @@ // Motion planning u8 m_Tries; // how many tries we've done to get to our current Final Goal. - PathGoal m_FinalGoal; - static std::string GetSchema() { return @@ -299,8 +297,6 @@ m_Tries = 0; - m_FinalGoal.type = PathGoal::POINT; - m_DebugOverlayEnabled = false; } @@ -329,8 +325,6 @@ SerializeVector()(serialize, "long path", m_LongPath.m_Waypoints); SerializeVector()(serialize, "short path", m_ShortPath.m_Waypoints); - - SerializeGoal()(serialize, "goal", m_FinalGoal); } virtual void Serialize(ISerializer& serialize) @@ -494,7 +488,11 @@ { CmpPtr cmpPosition(GetEntityHandle()); if (cmpPosition && cmpPosition->IsInWorld()) - FaceTowardsPointFromPos(cmpPosition->GetPosition2D(), m_FinalGoal.x, m_FinalGoal.z); + { + CFixedVector2D targetPos; + if (ComputeTargetPosition(targetPos)) + FaceTowardsPointFromPos(cmpPosition->GetPosition2D(), targetPos.X, targetPos.Y); + } } m_MoveRequest = MoveRequest(); @@ -577,8 +575,6 @@ m_CurSpeed = speed; } - bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange, entity_id_t target); - /** * Handle the result of an asynchronous path query. */ @@ -617,7 +613,11 @@ * Computes the current location of our target entity (plus offset). * Returns false if no target entity or no valid position. */ - bool ComputeTargetPosition(CFixedVector2D& out) const; + bool ComputeTargetPosition(CFixedVector2D& out, const MoveRequest& moveRequest) const; + bool ComputeTargetPosition(CFixedVector2D& out) const + { + return ComputeTargetPosition(out, m_MoveRequest); + } /** * Attempts to replace the current path with a straight line to the goal, @@ -632,15 +632,9 @@ bool TryGoingStraightToTargetEntity(const CFixedVector2D& from); /** - * Returns whether the target entity has moved more than minDelta since our - * last path computations, and we're close enough to it to care. + * Returns whether our target has moved enough that we need to recompute our path. */ - bool CheckTargetMovement(const CFixedVector2D& from, entity_pos_t minDelta); - - /** - * Update goal position if moving target - */ - void UpdateFinalGoal(); + bool CheckTargetMovement(); /** * Returns whether we are close enough to the target to assume it's a good enough @@ -666,6 +660,12 @@ */ ControlGroupMovementObstructionFilter GetObstructionFilter(bool noTarget = false) const; + /** + * Create a PathGoal from a move request. + * @returns true if the goal was successfully created. + */ + bool ComputeGoal(PathGoal& out, const MoveRequest& moveRequest) const; + /** * Start moving to the given goal, from our current position 'from'. * Might go in a straight line immediately, or might start an asynchronous @@ -724,7 +724,11 @@ // (via the short pathfinder), so if we're stuck and the user clicks // close enough to the unit then we can probably get unstuck if (m_LongPath.m_Waypoints.empty()) - m_LongPath.m_Waypoints.emplace_back(Waypoint{ m_FinalGoal.x, m_FinalGoal.z }); + { + CFixedVector2D targetPos; + if (ComputeTargetPosition(targetPos)) + m_LongPath.m_Waypoints.emplace_back(Waypoint{ targetPos.X, targetPos.Y }); + } m_PathState = PATHSTATE_FOLLOWING; } @@ -763,8 +767,9 @@ return; } - UpdateFinalGoal(); - RequestLongPath(pos, m_FinalGoal); + PathGoal goal; + ComputeGoal(goal, m_MoveRequest); + RequestLongPath(pos, goal); m_PathState = PATHSTATE_WAITING_REQUESTING_LONG; return; } @@ -832,10 +837,13 @@ { // 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. - if (IsFormationMember()) - CheckTargetMovement(pos, CHECK_TARGET_MOVEMENT_MIN_DELTA_FORMATION); - else - CheckTargetMovement(pos, CHECK_TARGET_MOVEMENT_MIN_DELTA); + if (CheckTargetMovement()) + { + PathGoal goal; + ComputeGoal(goal, m_MoveRequest); + RequestLongPath(pos, goal); + m_PathState = PATHSTATE_FOLLOWING_REQUESTING_LONG; + } } } @@ -988,69 +996,40 @@ } // If we still have long waypoints, try and compute a short path - // This will get us around units, amongst others. - // 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. - // I am not sure why this happens but the following code seems to work. if (!m_LongPath.m_Waypoints.empty()) { - CmpPtr cmpObstructionManager(GetSystemEntity()); - if (cmpObstructionManager) - { - // create a fake obstruction to represent our waypoint. - ICmpObstructionManager::ObstructionSquare square; - square.hh = m_Clearance; - square.hw = m_Clearance; - 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.x = m_LongPath.m_Waypoints.back().x; - square.z = m_LongPath.m_Waypoints.back().z; - std::vector unitOnGoal; - // don't ignore moving units as those might be units like us, ie not really moving. - cmpObstructionManager->GetUnitsOnObstruction(square, unitOnGoal, GetObstructionFilter()); - if (!unitOnGoal.empty()) - m_LongPath.m_Waypoints.pop_back(); - } - if (!m_LongPath.m_Waypoints.empty()) - { - PathGoal goal; - 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 }; - else - { - UpdateFinalGoal(); - goal = m_FinalGoal; - m_LongPath.m_Waypoints.clear(); - CFixedVector2D target = goal.NearestPointOnGoal(pos); - m_LongPath.m_Waypoints.emplace_back(Waypoint{ target.X, target.Y }); - } - RequestShortPath(pos, goal, true); - m_PathState = PATHSTATE_WAITING_REQUESTING_SHORT; - return true; - } + PathGoal goal = { PathGoal::POINT, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z }; + RequestShortPath(pos, goal, true); + m_PathState = PATHSTATE_WAITING_REQUESTING_SHORT; + return true; } + // Else, just entirely recompute - UpdateFinalGoal(); - BeginPathing(pos, m_FinalGoal); + PathGoal goal; + ComputeGoal(goal, m_MoveRequest); + BeginPathing(pos, goal); // potential TODO: We could switch the short-range pathfinder for something else entirely. return true; } -bool CCmpUnitMotion::ComputeTargetPosition(CFixedVector2D& out) const +bool CCmpUnitMotion::ComputeTargetPosition(CFixedVector2D& out, const MoveRequest& moveRequest) const { - if (m_MoveRequest.m_Entity == INVALID_ENTITY) - return false; + if (moveRequest.m_Type == MoveRequest::POINT) + { + out = moveRequest.m_Position; + return true; + } - CmpPtr cmpPosition(GetSimContext(), m_MoveRequest.m_Entity); + CmpPtr cmpPosition(GetSimContext(), moveRequest.m_Entity); if (!cmpPosition || !cmpPosition->IsInWorld()) return false; - if (m_MoveRequest.m_Type == MoveRequest::OFFSET) + if (moveRequest.m_Type == MoveRequest::OFFSET) { // There is an offset, so compute it relative to orientation entity_angle_t angle = cmpPosition->GetRotation().Y; - CFixedVector2D offset = m_MoveRequest.GetOffset().Rotate(angle); + CFixedVector2D offset = moveRequest.GetOffset().Rotate(angle); out = cmpPosition->GetPosition2D() + offset; } else @@ -1065,7 +1044,8 @@ return false; // Fail if the goal is too far away - CFixedVector2D goalPos(m_FinalGoal.x, m_FinalGoal.z); + CFixedVector2D goalPos; + ComputeTargetPosition(goalPos); if ((goalPos - from).CompareLength(DIRECT_PATH_RANGE) > 0) return false; @@ -1100,7 +1080,8 @@ return false; // Move the goal to match the target entity's new position - PathGoal goal = m_FinalGoal; + PathGoal goal; + ComputeGoal(goal, m_MoveRequest); goal.x = targetPos.X; goal.z = targetPos.Y; // (we ignore changes to the target's rotation, since only buildings are @@ -1114,7 +1095,6 @@ return false; // That route is okay, so update our path - m_FinalGoal = goal; m_LongPath.m_Waypoints.clear(); m_ShortPath.m_Waypoints.clear(); m_ShortPath.m_Waypoints.emplace_back(Waypoint{ goalPos.X, goalPos.Y }); @@ -1122,73 +1102,68 @@ return true; } -bool CCmpUnitMotion::CheckTargetMovement(const CFixedVector2D& from, entity_pos_t minDelta) +bool CCmpUnitMotion::CheckTargetMovement() { + if (m_MoveRequest.m_Type == MoveRequest::NONE) + return false; + CFixedVector2D targetPos; if (!ComputeTargetPosition(targetPos)) return false; - // Fail unless the target has moved enough - CFixedVector2D oldTargetPos(m_FinalGoal.x, m_FinalGoal.z); - if ((targetPos - oldTargetPos).CompareLength(minDelta) < 0) + if (PossiblyAtDestination()) return false; - CmpPtr cmpPosition(GetEntityHandle()); - if (!cmpPosition || !cmpPosition->IsInWorld()) - return false; - CFixedVector2D pos = cmpPosition->GetPosition2D(); - CFixedVector2D oldDir = (oldTargetPos - pos); - CFixedVector2D newDir = (targetPos - pos); - oldDir.Normalize(); - newDir.Normalize(); - - // Fail unless we're close enough to the target to care about its movement - // and the angle between the (straight-line) directions of the previous and new target positions is small - if (oldDir.Dot(newDir) > CHECK_TARGET_MOVEMENT_MIN_COS && !PathIsShort(m_LongPath, from, CHECK_TARGET_MOVEMENT_AT_MAX_DIST)) - return false; + // We are not at destination and have no waypoints: we need a path. + if (m_LongPath.m_Waypoints.empty() && m_ShortPath.m_Waypoints.empty()) + return true; - // Fail if the target is no longer visible to this entity's owner - // (in which case we'll continue moving to its last known location, - // unless it comes back into view before we reach that location) - CmpPtr cmpOwnership(GetEntityHandle()); - if (cmpOwnership) + // Get the obstruction shape and translate it where we estimate the target to be. + ICmpObstructionManager::ObstructionSquare estimatedTargetShape; + if (m_MoveRequest.m_Type == MoveRequest::POINT || m_MoveRequest.m_Type == MoveRequest::OFFSET) + { + estimatedTargetShape.x = targetPos.X; + estimatedTargetShape.z = targetPos.Y; + } + else if (m_MoveRequest.m_Type == MoveRequest::ENTITY) { - CmpPtr cmpRangeManager(GetSystemEntity()); - if (cmpRangeManager && cmpRangeManager->GetLosVisibility(m_MoveRequest.m_Entity, cmpOwnership->GetOwner()) == ICmpRangeManager::VIS_HIDDEN) - return false; + CmpPtr cmpTargetObstruction(GetSimContext(), m_MoveRequest.m_Entity); + if (cmpTargetObstruction) + cmpTargetObstruction->GetObstructionSquare(estimatedTargetShape); + estimatedTargetShape.x = targetPos.X; + estimatedTargetShape.z = targetPos.Y; + // TODO: scale it up by a certain factor based on distance to account for uncertainty? } - // The target moved and we need to update our current path; - // change the goal here and expect our caller to start the path request - m_FinalGoal.x = targetPos.X; - m_FinalGoal.z = targetPos.Y; - RequestLongPath(from, m_FinalGoal); - m_PathState = PATHSTATE_FOLLOWING_REQUESTING_LONG; + // Translate our own obstruction shape + CmpPtr cmpObstruction(GetEntityHandle()); + ICmpObstructionManager::ObstructionSquare shape; + if (cmpObstruction) + cmpObstruction->GetObstructionSquare(shape); + Waypoint lastWaypoint = m_ShortPath.m_Waypoints.empty() ? m_LongPath.m_Waypoints.front() : m_ShortPath.m_Waypoints.front(); + shape.x = lastWaypoint.x; + shape.z = lastWaypoint.z; + + CmpPtr cmpObstructionManager(GetSystemEntity()); + ENSURE(cmpObstructionManager); + entity_pos_t distance = cmpObstructionManager->DistanceBetweenShapes(shape, estimatedTargetShape); + + if (distance >= m_MoveRequest.m_MinRange && (distance <= m_MoveRequest.m_MaxRange || m_MoveRequest.m_MaxRange < fixed::Zero())) + return false; return true; } -void CCmpUnitMotion::UpdateFinalGoal() -{ - if (m_MoveRequest.m_Type != MoveRequest::ENTITY || m_MoveRequest.m_Type != MoveRequest::OFFSET) - return; - CmpPtr cmpUnitMotion(GetSimContext(), m_MoveRequest.m_Entity); - if (!cmpUnitMotion) - return; - if (IsFormationMember()) - return; - CFixedVector2D targetPos; - if (!ComputeTargetPosition(targetPos)) - return; - m_FinalGoal.x = targetPos.X; - m_FinalGoal.z = targetPos.Y; -} - bool CCmpUnitMotion::CloseEnoughFromDestinationToStop(const CFixedVector2D& from) { - if (m_MoveRequest.m_Type != MoveRequest::POINT || m_FinalGoal.DistanceToPoint(from) > SHORT_PATH_GOAL_RADIUS) + if (m_MoveRequest.m_Type != MoveRequest::POINT) return false; - return true; + + CFixedVector2D targetPos; + if (!ComputeTargetPosition(targetPos)) + return true; // We failed to compute a position so we'll stop anyways. + + return (from - targetPos).CompareLength(SHORT_PATH_GOAL_RADIUS) <= 0; } bool CCmpUnitMotion::PathIsShort(const WaypointPath& path, const CFixedVector2D& from, entity_pos_t minDistance) const @@ -1244,11 +1219,119 @@ return ControlGroupMovementObstructionFilter(ShouldAvoidMovingUnits(), group); } +bool CCmpUnitMotion::ComputeGoal(PathGoal& out, const MoveRequest& moveRequest) const +{ + if (moveRequest.m_Type == MoveRequest::NONE) + return false; + CmpPtr cmpPosition(GetEntityHandle()); + if (!cmpPosition || !cmpPosition->IsInWorld()) + return false; + + CFixedVector2D pos = cmpPosition->GetPosition2D(); + + CFixedVector2D targetPosition; + if (!ComputeTargetPosition(targetPosition, moveRequest)) + return false; + + ICmpObstructionManager::ObstructionSquare targetObstruction; + if (moveRequest.m_Type == MoveRequest::ENTITY) + { + CmpPtr cmpTargetObstruction(GetSimContext(), moveRequest.m_Entity); + if (cmpTargetObstruction) + cmpTargetObstruction->GetObstructionSquare(targetObstruction); + } + targetObstruction.x = targetPosition.X; + targetObstruction.z = targetPosition.Y; + + ICmpObstructionManager::ObstructionSquare obstruction; + CmpPtr cmpObstruction(GetEntityHandle()); + if (cmpObstruction) + cmpObstruction->GetObstructionSquare(obstruction); + else + { + obstruction.x = pos.X; + obstruction.z = pos.Y; + } + + CmpPtr cmpObstructionManager(GetSystemEntity()); + ENSURE(cmpObstructionManager); + + entity_pos_t distance = cmpObstructionManager->DistanceBetweenShapes(obstruction, targetObstruction); + + out.x = targetObstruction.x; + out.z = targetObstruction.z; + out.hw = targetObstruction.hw; + out.hh = targetObstruction.hh; + out.u = targetObstruction.u; + out.v = targetObstruction.v; + + if (targetObstruction.hw > fixed::Zero()) + out.type = PathGoal::SQUARE; + else + out.type = PathGoal::POINT; + + CFixedVector2D halfSize(targetObstruction.hw, targetObstruction.hh); + + // 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. + if (distance < moveRequest.m_MinRange) + { + entity_pos_t circleRadius = halfSize.Length(); + entity_pos_t goalDistance = moveRequest.m_MinRange; + + if (ShouldTreatTargetAsCircle(moveRequest.m_MinRange, circleRadius)) + { + out.type = PathGoal::INVERTED_CIRCLE; + out.hw = circleRadius + goalDistance; + } + else + { + out.type = PathGoal::INVERTED_SQUARE; + out.hw = targetObstruction.hw + goalDistance; + out.hh = targetObstruction.hh + goalDistance; + } + } + else if (moveRequest.m_MaxRange > fixed::Zero() && distance > moveRequest.m_MaxRange) + { + entity_pos_t circleRadius = halfSize.Length(); + + if (ShouldTreatTargetAsCircle(moveRequest.m_MaxRange, circleRadius)) + { + entity_pos_t goalDistance = moveRequest.m_MaxRange - Pathfinding::GOAL_DELTA; + + out.type = PathGoal::CIRCLE; + out.hw = circleRadius + goalDistance; + } + else + { + // The target is large relative to our range, so treat it as a square and + // get close enough that the diagonals come within range + + entity_pos_t goalDistance = moveRequest.m_MaxRange * 2 / 3; // multiply by slightly less than 1/sqrt(2) + + out.type = PathGoal::SQUARE; + entity_pos_t delta = std::max(goalDistance, m_Clearance + entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/16); // ensure it's far enough to not intersect the building itself + out.hw = targetObstruction.hw + delta; + out.hh = targetObstruction.hh + delta; + } + } + return true; +} + +bool CCmpUnitMotion::ShouldTreatTargetAsCircle(entity_pos_t range, entity_pos_t circleRadius) const +{ + // Given a square, plus a target range we should reach, the shape at that distance + // is a round-cornered square which we can approximate as either a circle or as a square. + // Previously, we used the shape that minimized the worst-case error. + // However that is unsage in some situations. So let's be less clever and + // just check if our range is at least three times bigger than the circleradius + return (range > circleRadius*3); +} void CCmpUnitMotion::BeginPathing(const CFixedVector2D& from, const PathGoal& goal) { - // reset our state for sanity. m_ExpectedPathTicket = 0; m_PathState = PATHSTATE_NONE; @@ -1293,7 +1376,7 @@ // where we are going if the short-range pathfinder returns // an aborted path. m_LongPath.m_Waypoints.clear(); - CFixedVector2D target = m_FinalGoal.NearestPointOnGoal(from); + CFixedVector2D target = goal.NearestPointOnGoal(from); m_LongPath.m_Waypoints.emplace_back(Waypoint{ target.X, target.Y }); m_PathState = PATHSTATE_WAITING_REQUESTING_SHORT; RequestShortPath(from, goal, true); @@ -1338,264 +1421,70 @@ } bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) -{ - return MoveToPointRange(x, z, minRange, maxRange, INVALID_ENTITY); -} - -bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange, entity_id_t target) { PROFILE("MoveToPointRange"); + MoveRequest moveRequest(CFixedVector2D(x, z), minRange, maxRange); + + PathGoal goal; + if (!ComputeGoal(goal, moveRequest)) + return false; + CmpPtr cmpPosition(GetEntityHandle()); if (!cmpPosition || !cmpPosition->IsInWorld()) return false; - CFixedVector2D pos = cmpPosition->GetPosition2D(); - - PathGoal goal; - goal.x = x; - goal.z = z; - - if (minRange.IsZero() && maxRange.IsZero()) - { - // Non-ranged movement: - - // Head directly for the goal - goal.type = PathGoal::POINT; - } - else - { - // Ranged movement: - - entity_pos_t distance = (pos - CFixedVector2D(x, z)).Length(); - - if (distance < minRange) - { - // Too close to target - move outwards to a circle - // that's slightly larger than the min range - goal.type = PathGoal::INVERTED_CIRCLE; - goal.hw = minRange + Pathfinding::GOAL_DELTA; - } - else if (maxRange >= entity_pos_t::Zero() && distance > maxRange) - { - // Too far from target - move inwards to a circle - // that's slightly smaller than the max range - goal.type = PathGoal::CIRCLE; - goal.hw = maxRange - Pathfinding::GOAL_DELTA; - - // If maxRange was abnormally small, - // collapse the circle into a point - if (goal.hw <= entity_pos_t::Zero()) - goal.type = PathGoal::POINT; - } - else - { - // We're already in range - no need to move anywhere - if (m_FacePointAfterMove) - FaceTowardsPointFromPos(pos, x, z); - return true; - } - } - - m_MoveRequest = MoveRequest(CFixedVector2D(x, z), minRange, maxRange); - m_FinalGoal = goal; + m_MoveRequest = moveRequest; m_Tries = 0; - BeginPathing(pos, goal); + BeginPathing(cmpPosition->GetPosition2D(), goal); return true; } -bool CCmpUnitMotion::ShouldTreatTargetAsCircle(entity_pos_t range, entity_pos_t circleRadius) const -{ - // Given a square, plus a target range we should reach, the shape at that distance - // is a round-cornered square which we can approximate as either a circle or as a square. - // Previously, we used the shape that minimized the worst-case error. - // However that is unsage in some situations. So let's be less clever and - // just check if our range is at least three times bigger than the circleradius - return (range > circleRadius*3); -} - bool CCmpUnitMotion::MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) { PROFILE("MoveToTargetRange"); + MoveRequest moveRequest(target, minRange, maxRange); + + PathGoal goal; + + if (!ComputeGoal(goal, moveRequest)) + return false; + CmpPtr cmpPosition(GetEntityHandle()); if (!cmpPosition || !cmpPosition->IsInWorld()) return false; - CFixedVector2D pos = cmpPosition->GetPosition2D(); - - CmpPtr cmpObstructionManager(GetSystemEntity()); - if (!cmpObstructionManager) - return false; - - bool hasObstruction = false; - ICmpObstructionManager::ObstructionSquare obstruction; - CmpPtr cmpObstruction(GetSimContext(), target); - if (cmpObstruction) - hasObstruction = cmpObstruction->GetObstructionSquare(obstruction); - - if (!hasObstruction) - { - // The target didn't have an obstruction or obstruction shape, so treat it as a point instead - - CmpPtr cmpTargetPosition(GetSimContext(), target); - if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld()) - return false; - - CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D(); - - return MoveToPointRange(targetPos.X, targetPos.Y, minRange, maxRange); - } - - /* - * If we're starting outside the maxRange, we need to move closer in. - * If we're starting inside the minRange, we need to move further out. - * These ranges are measured from the center of this entity to the edge of the target; - * we add the goal range onto the size of the target shape to get the goal shape. - * (Then we extend it outwards/inwards by a little bit to be sure we'll end up - * within the right range, in case of minor numerical inaccuracies.) - * - * There's a bit of a problem with large square targets: - * the pathfinder only lets us move to goals that are squares, but the points an equal - * distance from the target make a rounded square shape instead. - * - * When moving closer, we could shrink the goal radius to 1/sqrt(2) so the goal shape fits entirely - * within the desired rounded square, but that gives an unfair advantage to attackers who approach - * the target diagonally. - * - * If the target is small relative to the range (e.g. archers attacking anything), - * then we cheat and pretend the target is actually a circle. - * (TODO: that probably looks rubbish for things like walls?) - * - * If the target is large relative to the range (e.g. melee units attacking buildings), - * then we multiply maxRange by approx 1/sqrt(2) to guarantee they'll always aim close enough. - * (Those units should set minRange to 0 so they'll never be considered *too* close.) - */ - - CFixedVector2D halfSize(obstruction.hw, obstruction.hh); - PathGoal goal; - goal.x = obstruction.x; - goal.z = obstruction.z; - - entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize, true); - - // Compare with previous obstruction - ICmpObstructionManager::ObstructionSquare previousObstruction; - cmpObstruction->GetPreviousObstructionSquare(previousObstruction); - entity_pos_t previousDistance = Geometry::DistanceToSquare(pos - CFixedVector2D(previousObstruction.x, previousObstruction.z), obstruction.u, obstruction.v, halfSize, true); - - bool inside = distance.IsZero() && !Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize).IsZero(); - if ((distance < minRange && previousDistance < minRange) || inside) - { - // Too close to the square - need to move away - - // Circumscribe the square - entity_pos_t circleRadius = halfSize.Length(); - - entity_pos_t goalDistance = minRange + Pathfinding::GOAL_DELTA; - - if (ShouldTreatTargetAsCircle(minRange, circleRadius)) - { - // The target is small relative to our range, so pretend it's a circle - goal.type = PathGoal::INVERTED_CIRCLE; - goal.hw = circleRadius + goalDistance; - } - else - { - goal.type = PathGoal::INVERTED_SQUARE; - goal.u = obstruction.u; - goal.v = obstruction.v; - goal.hw = obstruction.hw + goalDistance; - goal.hh = obstruction.hh + goalDistance; - } - } - else if (maxRange < entity_pos_t::Zero() || distance < maxRange || previousDistance < maxRange) - { - // We're already in range - no need to move anywhere - FaceTowardsPointFromPos(pos, goal.x, goal.z); - return true; - } - else - { - // We might need to move closer: - - // Circumscribe the square - entity_pos_t circleRadius = halfSize.Length(); - - if (ShouldTreatTargetAsCircle(maxRange, circleRadius)) - { - // The target is small relative to our range, so pretend it's a circle - - // Note that the distance to the circle will always be less than - // the distance to the square, so the previous "distance < maxRange" - // check is still valid (though not sufficient) - entity_pos_t circleDistance = (pos - CFixedVector2D(obstruction.x, obstruction.z)).Length() - circleRadius; - entity_pos_t previousCircleDistance = (pos - CFixedVector2D(previousObstruction.x, previousObstruction.z)).Length() - circleRadius; - - if (circleDistance < maxRange || previousCircleDistance < maxRange) - { - // We're already in range - no need to move anywhere - if (m_FacePointAfterMove) - FaceTowardsPointFromPos(pos, goal.x, goal.z); - return false; - } - - entity_pos_t goalDistance = maxRange - Pathfinding::GOAL_DELTA; - - goal.type = PathGoal::CIRCLE; - goal.hw = circleRadius + goalDistance; - } - else - { - // The target is large relative to our range, so treat it as a square and - // get close enough that the diagonals come within range - - entity_pos_t goalDistance = (maxRange - Pathfinding::GOAL_DELTA)*2 / 3; // multiply by slightly less than 1/sqrt(2) - - goal.type = PathGoal::SQUARE; - goal.u = obstruction.u; - goal.v = obstruction.v; - entity_pos_t delta = std::max(goalDistance, m_Clearance + entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/16); // ensure it's far enough to not intersect the building itself - goal.hw = obstruction.hw + delta; - goal.hh = obstruction.hh + delta; - } - } - - m_MoveRequest = MoveRequest(target, minRange, maxRange); - m_FinalGoal = goal; + m_MoveRequest = moveRequest; m_Tries = 0; - BeginPathing(pos, goal); + BeginPathing(cmpPosition->GetPosition2D(), goal); return true; } void CCmpUnitMotion::MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z) { + MoveRequest moveRequest(target, CFixedVector2D(x, z)); + + PathGoal goal; + if (!ComputeGoal(goal, moveRequest)) + return; + CmpPtr cmpPosition(GetSimContext(), target); if (!cmpPosition || !cmpPosition->IsInWorld()) return; - CFixedVector2D pos = cmpPosition->GetPosition2D(); - - PathGoal goal; - goal.type = PathGoal::POINT; - goal.x = pos.X; - goal.z = pos.Y; - - m_MoveRequest = MoveRequest(target, CFixedVector2D(x, z)); - m_FinalGoal = goal; + m_MoveRequest = moveRequest; m_Tries = 0; - BeginPathing(pos, goal); + BeginPathing(cmpPosition->GetPosition2D(), goal); } - - void CCmpUnitMotion::RenderPath(const WaypointPath& path, std::vector& lines, CColor color) { bool floating = false;