Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/source/simulation2/components/CCmpUnitMotion.h
Show First 20 Lines • Show All 147 Lines • ▼ Show 20 Lines | public: | ||||
bool m_DebugOverlayEnabled; | bool m_DebugOverlayEnabled; | ||||
std::vector<SOverlayLine> m_DebugOverlayLongPathLines; | std::vector<SOverlayLine> m_DebugOverlayLongPathLines; | ||||
std::vector<SOverlayLine> m_DebugOverlayShortPathLines; | std::vector<SOverlayLine> m_DebugOverlayShortPathLines; | ||||
// Template state: | // Template state: | ||||
bool m_IsFormationController; | bool m_IsFormationController; | ||||
fixed m_TemplateWalkSpeed, m_TemplateRunMultiplier; | fixed m_TemplateWalkSpeed, m_TemplateRunMultiplier, m_TemplateAcceleration; | ||||
pass_class_t m_PassClass; | pass_class_t m_PassClass; | ||||
std::string m_PassClassName; | std::string m_PassClassName; | ||||
// Dynamic state: | // Dynamic state: | ||||
entity_pos_t m_Clearance; | entity_pos_t m_Clearance; | ||||
// cached for efficiency | // cached for efficiency | ||||
▲ Show 20 Lines • Show All 55 Lines • ▼ Show 20 Lines | public: | ||||
// If this is not INVALID_ENTITY, the unit is a formation member. | // If this is not INVALID_ENTITY, the unit is a formation member. | ||||
entity_id_t m_FormationController = INVALID_ENTITY; | entity_id_t m_FormationController = INVALID_ENTITY; | ||||
// If the entity moves, it will do so at m_WalkSpeed * m_SpeedMultiplier. | // If the entity moves, it will do so at m_WalkSpeed * m_SpeedMultiplier. | ||||
fixed m_SpeedMultiplier; | fixed m_SpeedMultiplier; | ||||
// This caches the resulting speed from m_WalkSpeed * m_SpeedMultiplier for convenience. | // This caches the resulting speed from m_WalkSpeed * m_SpeedMultiplier for convenience. | ||||
fixed m_Speed; | fixed m_Speed; | ||||
// Current mean speed (over the last turn). | // The speed achieved at the end of the current turn. | ||||
fixed m_CurSpeed; | fixed m_CurrentSpeed; | ||||
fixed m_InstantTurnAngle; | |||||
fixed m_Acceleration; | |||||
// Currently active paths (storing waypoints in reverse order). | // Currently active paths (storing waypoints in reverse order). | ||||
// The last item in each path is the point we're currently heading towards. | // The last item in each path is the point we're currently heading towards. | ||||
WaypointPath m_LongPath; | WaypointPath m_LongPath; | ||||
WaypointPath m_ShortPath; | WaypointPath m_ShortPath; | ||||
static std::string GetSchema() | static std::string GetSchema() | ||||
{ | { | ||||
return | return | ||||
"<a:help>Provides the unit with the ability to move around the world by itself.</a:help>" | "<a:help>Provides the unit with the ability to move around the world by itself.</a:help>" | ||||
"<a:example>" | "<a:example>" | ||||
"<WalkSpeed>7.0</WalkSpeed>" | "<WalkSpeed>7.0</WalkSpeed>" | ||||
"<PassabilityClass>default</PassabilityClass>" | "<PassabilityClass>default</PassabilityClass>" | ||||
"</a:example>" | "</a:example>" | ||||
"<element name='FormationController'>" | "<element name='FormationController'>" | ||||
"<data type='boolean'/>" | "<data type='boolean'/>" | ||||
"</element>" | "</element>" | ||||
"<element name='WalkSpeed' a:help='Basic movement speed (in metres per second)'>" | "<element name='WalkSpeed' a:help='Basic movement speed (in metres per second).'>" | ||||
"<ref name='positiveDecimal'/>" | "<ref name='positiveDecimal'/>" | ||||
"</element>" | "</element>" | ||||
"<optional>" | "<optional>" | ||||
"<element name='RunMultiplier' a:help='How much faster the unit goes when running (as a multiple of walk speed)'>" | "<element name='RunMultiplier' a:help='How much faster the unit goes when running (as a multiple of walk speed).'>" | ||||
"<ref name='positiveDecimal'/>" | "<ref name='positiveDecimal'/>" | ||||
"</element>" | "</element>" | ||||
"</optional>" | "</optional>" | ||||
"<element name='PassabilityClass' a:help='Identifies the terrain passability class (values are defined in special/pathfinder.xml)'>" | "<element name='InstantTurnAngle' a:help='Angle we can turn instantly. Any value greater than pi will disable turning times. Avoid zero since it stops the entity every turn.'>" | ||||
"<ref name='positiveDecimal'/>" | |||||
"</element>" | |||||
"<element name='Acceleration' a:help='Acceleration (in metres per second^2).'>" | |||||
"<ref name='positiveDecimal'/>" | |||||
"</element>" | |||||
"<element name='PassabilityClass' a:help='Identifies the terrain passability class (values are defined in special/pathfinder.xml).'>" | |||||
"<text/>" | "<text/>" | ||||
"</element>" | "</element>" | ||||
"<optional>" | "<optional>" | ||||
"<element name='DisablePushing'>" | "<element name='DisablePushing'>" | ||||
"<data type='boolean'/>" | "<data type='boolean'/>" | ||||
"</element>" | "</element>" | ||||
"</optional>"; | "</optional>"; | ||||
} | } | ||||
virtual void Init(const CParamNode& paramNode) | virtual void Init(const CParamNode& paramNode) | ||||
{ | { | ||||
m_IsFormationController = paramNode.GetChild("FormationController").ToBool(); | m_IsFormationController = paramNode.GetChild("FormationController").ToBool(); | ||||
m_FacePointAfterMove = true; | m_FacePointAfterMove = true; | ||||
m_WalkSpeed = m_TemplateWalkSpeed = m_Speed = paramNode.GetChild("WalkSpeed").ToFixed(); | m_WalkSpeed = m_TemplateWalkSpeed = m_Speed = paramNode.GetChild("WalkSpeed").ToFixed(); | ||||
m_SpeedMultiplier = fixed::FromInt(1); | m_SpeedMultiplier = fixed::FromInt(1); | ||||
m_CurSpeed = fixed::Zero(); | m_CurrentSpeed = fixed::Zero(); | ||||
m_RunMultiplier = m_TemplateRunMultiplier = fixed::FromInt(1); | m_RunMultiplier = m_TemplateRunMultiplier = fixed::FromInt(1); | ||||
if (paramNode.GetChild("RunMultiplier").IsOk()) | if (paramNode.GetChild("RunMultiplier").IsOk()) | ||||
m_RunMultiplier = m_TemplateRunMultiplier = paramNode.GetChild("RunMultiplier").ToFixed(); | m_RunMultiplier = m_TemplateRunMultiplier = paramNode.GetChild("RunMultiplier").ToFixed(); | ||||
m_InstantTurnAngle = paramNode.GetChild("InstantTurnAngle").ToFixed(); | |||||
m_Acceleration = m_TemplateAcceleration = paramNode.GetChild("Acceleration").ToFixed(); | |||||
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | ||||
if (cmpPathfinder) | if (cmpPathfinder) | ||||
{ | { | ||||
m_PassClassName = paramNode.GetChild("PassabilityClass").ToString(); | m_PassClassName = paramNode.GetChild("PassabilityClass").ToString(); | ||||
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()); | ||||
Show All 30 Lines | void SerializeCommon(S& serialize) | ||||
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); | ||||
serialize.NumberU32_Unbounded("formation controller", m_FormationController); | serialize.NumberU32_Unbounded("formation controller", m_FormationController); | ||||
serialize.NumberFixed_Unbounded("speed multiplier", m_SpeedMultiplier); | serialize.NumberFixed_Unbounded("speed multiplier", m_SpeedMultiplier); | ||||
serialize.NumberFixed_Unbounded("current speed", m_CurSpeed); | serialize.NumberFixed_Unbounded("current speed", m_CurrentSpeed); | ||||
serialize.NumberFixed_Unbounded("instant turn angle", m_InstantTurnAngle); | |||||
serialize.NumberFixed_Unbounded("acceleration", m_Acceleration); | |||||
serialize.Bool("facePointAfterMove", m_FacePointAfterMove); | serialize.Bool("facePointAfterMove", m_FacePointAfterMove); | ||||
serialize.Bool("pushing", m_Pushing); | serialize.Bool("pushing", m_Pushing); | ||||
Serializer(serialize, "long path", m_LongPath.m_Waypoints); | Serializer(serialize, "long path", m_LongPath.m_Waypoints); | ||||
Serializer(serialize, "short path", m_ShortPath.m_Waypoints); | Serializer(serialize, "short path", m_ShortPath.m_Waypoints); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 117 Lines • ▼ Show 20 Lines | virtual CFixedVector2D EstimateFuturePosition(const fixed dt) const | ||||
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | ||||
if (!cmpPosition || !cmpPosition->IsInWorld()) | if (!cmpPosition || !cmpPosition->IsInWorld()) | ||||
return CFixedVector2D(); | return CFixedVector2D(); | ||||
// TODO: formation members should perhaps try to use the controller's position. | // TODO: formation members should perhaps try to use the controller's position. | ||||
CFixedVector2D pos = cmpPosition->GetPosition2D(); | CFixedVector2D pos = cmpPosition->GetPosition2D(); | ||||
entity_angle_t angle = cmpPosition->GetRotation().Y; | entity_angle_t angle = cmpPosition->GetRotation().Y; | ||||
fixed speed = m_CurrentSpeed; | |||||
// Copy the path so we don't change it. | // Copy the path so we don't change it. | ||||
WaypointPath shortPath = m_ShortPath; | WaypointPath shortPath = m_ShortPath; | ||||
WaypointPath longPath = m_LongPath; | WaypointPath longPath = m_LongPath; | ||||
PerformMove(dt, cmpPosition->GetTurnRate(), shortPath, longPath, pos, angle); | PerformMove(dt, cmpPosition->GetTurnRate(), shortPath, longPath, pos, speed, angle); | ||||
return pos; | return pos; | ||||
} | } | ||||
virtual fixed GetAcceleration() const | |||||
{ | |||||
return m_Acceleration; | |||||
} | |||||
virtual void SetAcceleration(fixed acceleration) | |||||
{ | |||||
m_Acceleration = acceleration; | |||||
} | |||||
virtual pass_class_t GetPassabilityClass() const | virtual pass_class_t GetPassabilityClass() const | ||||
{ | { | ||||
return m_PassClass; | return m_PassClass; | ||||
} | } | ||||
virtual std::string GetPassabilityClassName() const | virtual std::string GetPassabilityClassName() const | ||||
{ | { | ||||
return m_PassClassName; | return m_PassClassName; | ||||
} | } | ||||
virtual void SetPassabilityClassName(const std::string& passClassName) | virtual void SetPassabilityClassName(const std::string& passClassName) | ||||
{ | { | ||||
m_PassClassName = passClassName; | m_PassClassName = passClassName; | ||||
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | ||||
if (cmpPathfinder) | if (cmpPathfinder) | ||||
m_PassClass = cmpPathfinder->GetPassabilityClass(passClassName); | m_PassClass = cmpPathfinder->GetPassabilityClass(passClassName); | ||||
} | } | ||||
virtual fixed GetCurrentSpeed() const | virtual fixed GetCurrentSpeed() const | ||||
{ | { | ||||
return m_CurSpeed; | return m_CurrentSpeed; | ||||
} | } | ||||
virtual void SetFacePointAfterMove(bool facePointAfterMove) | virtual void SetFacePointAfterMove(bool facePointAfterMove) | ||||
{ | { | ||||
m_FacePointAfterMove = facePointAfterMove; | m_FacePointAfterMove = facePointAfterMove; | ||||
} | } | ||||
virtual bool GetFacePointAfterMove() const | virtual bool GetFacePointAfterMove() const | ||||
▲ Show 20 Lines • Show All 219 Lines • ▼ Show 20 Lines | private: | ||||
*/ | */ | ||||
bool PossiblyAtDestination() const; | bool PossiblyAtDestination() const; | ||||
/** | /** | ||||
* Process the move the unit will do this turn. | * Process the move the unit will do this turn. | ||||
* This does not send actually change the position. | * This does not send actually change the position. | ||||
* @returns true if the move was obstructed. | * @returns true if the move was obstructed. | ||||
*/ | */ | ||||
bool PerformMove(fixed dt, const fixed& turnRate, WaypointPath& shortPath, WaypointPath& longPath, CFixedVector2D& pos, entity_angle_t& angle) const; | bool PerformMove(fixed dt, const fixed& turnRate, WaypointPath& shortPath, WaypointPath& longPath, CFixedVector2D& pos, fixed& speed, entity_angle_t& angle) const; | ||||
/** | /** | ||||
* Update other components on our speed. | * Update other components on our speed. | ||||
* (For performance, this should try to avoid sending messages). | * (For performance, this should try to avoid sending messages). | ||||
*/ | */ | ||||
void UpdateMovementState(entity_pos_t speed); | void UpdateMovementState(entity_pos_t speed, entity_pos_t meanSpeed); | ||||
/** | /** | ||||
* React if our move was obstructed. | * React if our move was obstructed. | ||||
* @param moved - true if the unit still managed to move. | * @param moved - true if the unit still managed to move. | ||||
* @returns true if the obstruction required handling, false otherwise. | * @returns true if the obstruction required handling, false otherwise. | ||||
*/ | */ | ||||
bool HandleObstructedMove(bool moved); | bool HandleObstructedMove(bool moved); | ||||
▲ Show 20 Lines • Show All 249 Lines • ▼ Show 20 Lines | |||||
{ | { | ||||
state.ignore = !m_Pushing || !m_BlockMovement; | state.ignore = !m_Pushing || !m_BlockMovement; | ||||
state.wasObstructed = false; | state.wasObstructed = false; | ||||
state.wentStraight = false; | state.wentStraight = false; | ||||
// If we were idle and will still be, no need for an update. | // If we were idle and will still be, no need for an update. | ||||
state.needUpdate = state.cmpPosition->IsInWorld() && | state.needUpdate = state.cmpPosition->IsInWorld() && | ||||
(m_CurSpeed != fixed::Zero() || m_MoveRequest.m_Type != MoveRequest::NONE); | (m_CurrentSpeed != fixed::Zero() || m_MoveRequest.m_Type != MoveRequest::NONE); | ||||
if (!m_BlockMovement) | if (!m_BlockMovement) | ||||
return; | return; | ||||
state.controlGroup = IsFormationMember() ? m_FormationController : INVALID_ENTITY; | state.controlGroup = IsFormationMember() ? m_FormationController : INVALID_ENTITY; | ||||
// Update moving flag, this is an internal construct used for pushing, | // Update moving flag, this is an internal construct used for pushing, | ||||
// so it does not really reflect whether the unit is actually moving or not. | // so it does not really reflect whether the unit is actually moving or not. | ||||
state.isMoving = m_Pushing && m_MoveRequest.m_Type != MoveRequest::NONE; | state.isMoving = m_Pushing && m_MoveRequest.m_Type != MoveRequest::NONE; | ||||
CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle()); | CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle()); | ||||
if (cmpObstruction) | if (cmpObstruction) | ||||
cmpObstruction->SetMovingFlag(state.isMoving); | cmpObstruction->SetMovingFlag(state.isMoving); | ||||
} | } | ||||
void CCmpUnitMotion::Move(CCmpUnitMotionManager::MotionState& state, fixed dt) | void CCmpUnitMotion::Move(CCmpUnitMotionManager::MotionState& state, fixed dt) | ||||
{ | { | ||||
PROFILE("Move"); | PROFILE("Move"); | ||||
// 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. | ||||
state.wentStraight = TryGoingStraightToTarget(state.initialPos, true); | state.wentStraight = TryGoingStraightToTarget(state.initialPos, true); | ||||
state.wasObstructed = PerformMove(dt, state.cmpPosition->GetTurnRate(), m_ShortPath, m_LongPath, state.pos, state.angle); | state.wasObstructed = PerformMove(dt, state.cmpPosition->GetTurnRate(), m_ShortPath, m_LongPath, state.pos, state.speed, state.angle); | ||||
} | } | ||||
void CCmpUnitMotion::PostMove(CCmpUnitMotionManager::MotionState& state, fixed dt) | void CCmpUnitMotion::PostMove(CCmpUnitMotionManager::MotionState& state, fixed dt) | ||||
{ | { | ||||
// 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 (state.pos == state.initialPos) | if (state.pos == state.initialPos) | ||||
{ | { | ||||
if (state.angle != state.initialAngle) | if (state.angle != state.initialAngle) | ||||
state.cmpPosition->TurnTo(state.angle); | state.cmpPosition->TurnTo(state.angle); | ||||
UpdateMovementState(fixed::Zero()); | UpdateMovementState(fixed::Zero(), 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 = state.pos - state.initialPos; | CFixedVector2D offset = state.pos - state.initialPos; | ||||
// When moving always set the angle in the direction of the movement, | // When moving always set the angle in the direction of the movement, | ||||
// if we are not trying to move, assume this is pushing-related movement, | // if we are not trying to move, assume this is pushing-related movement, | ||||
// and maintain the current angle instead. | // and maintain the current angle instead. | ||||
if (IsMoveRequested()) | if (IsMoveRequested()) | ||||
state.angle = atan2_approx(offset.X, offset.Y); | state.angle = atan2_approx(offset.X, offset.Y); | ||||
state.cmpPosition->MoveAndTurnTo(state.pos.X, state.pos.Y, state.angle); | state.cmpPosition->MoveAndTurnTo(state.pos.X, state.pos.Y, state.angle); | ||||
// Calculate the mean speed over this past turn. | // Calculate the mean speed over this past turn. | ||||
UpdateMovementState(offset.Length() / dt); | UpdateMovementState(state.speed, offset.Length() / dt); | ||||
} | } | ||||
if (state.wasObstructed && HandleObstructedMove(state.pos != state.initialPos)) | if (state.wasObstructed && HandleObstructedMove(state.pos != state.initialPos)) | ||||
return; | return; | ||||
else if (!state.wasObstructed && state.pos != state.initialPos) | else if (!state.wasObstructed && state.pos != state.initialPos) | ||||
m_FailedMovements = 0; | m_FailedMovements = 0; | ||||
// If we moved straight, and didn't quite finish the path, reset - we'll update it next turn if still OK. | // If we moved straight, and didn't quite finish the path, reset - we'll update it next turn if still OK. | ||||
Show All 36 Lines | if (m_MoveRequest.m_Type == MoveRequest::OFFSET) | ||||
CFixedVector2D targetPos; | CFixedVector2D targetPos; | ||||
ComputeTargetPosition(targetPos); | ComputeTargetPosition(targetPos); | ||||
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | ||||
return (targetPos-cmpPosition->GetPosition2D()).CompareLength(fixed::Zero()) <= 0; | return (targetPos-cmpPosition->GetPosition2D()).CompareLength(fixed::Zero()) <= 0; | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
bool CCmpUnitMotion::PerformMove(fixed dt, const fixed& turnRate, WaypointPath& shortPath, WaypointPath& longPath, CFixedVector2D& pos, entity_angle_t& angle) const | bool CCmpUnitMotion::PerformMove(fixed dt, const fixed& turnRate, WaypointPath& shortPath, WaypointPath& longPath, CFixedVector2D& pos, fixed& speed, entity_angle_t& angle) const | ||||
{ | { | ||||
// If there are no waypoint, behave as though we were obstructed and let HandleObstructedMove handle it. | // If there are no waypoint, behave as though we were obstructed and let HandleObstructedMove handle it. | ||||
if (shortPath.m_Waypoints.empty() && longPath.m_Waypoints.empty()) | if (shortPath.m_Waypoints.empty() && longPath.m_Waypoints.empty()) | ||||
return true; | return true; | ||||
// Wrap the angle to (-Pi, Pi]. | // Wrap the angle to (-Pi, Pi]. | ||||
while (angle > entity_angle_t::Pi()) | while (angle > entity_angle_t::Pi()) | ||||
angle -= entity_angle_t::Pi() * 2; | angle -= entity_angle_t::Pi() * 2; | ||||
Show All 40 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 && !offset.IsZero()) | |||||
{ | |||||
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) | |||||
{ | |||||
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; | ||||
// We only rotate to the instantTurnAngle angle. The rest we rotate during movement. | |||||
if (absoluteAngleDiff > m_InstantTurnAngle) | |||||
{ | |||||
// Stop moving when rotating this far. | |||||
speed = zero; | |||||
real_tabasco_sauce: might be cool to set speed as a function of the absoluteAngleDiff
this way larger turns slow a… | |||||
if (turnRate > zero && !offset.IsZero()) | |||||
{ | |||||
fixed maxRotation = turnRate.Multiply(timeLeft); | |||||
// Figure out whether rotating will increase or decrease the angle, and how far we need to rotate in that direction. | // Figure out whether rotating will increase or decrease the angle, and how far we need to rotate in that direction. | ||||
int direction = (entity_angle_t::Zero() < angleDiff && angleDiff <= entity_angle_t::Pi()) || angleDiff < -entity_angle_t::Pi() ? -1 : 1; | int direction = (entity_angle_t::Zero() < angleDiff && angleDiff <= entity_angle_t::Pi()) || angleDiff < -entity_angle_t::Pi() ? -1 : 1; | ||||
// Can't rotate far enough, just rotate in the correct direction. | // Can't rotate far enough, just rotate in the correct direction. | ||||
if (absoluteAngleDiff > maxRotation) | if (absoluteAngleDiff - m_InstantTurnAngle > maxRotation) | ||||
{ | { | ||||
angle += maxRotation * direction; | angle += maxRotation * direction; | ||||
if (angle * direction > entity_angle_t::Pi()) | if (angle * direction > entity_angle_t::Pi()) | ||||
angle -= entity_angle_t::Pi() * 2 * direction; | angle -= entity_angle_t::Pi() * 2 * direction; | ||||
break; | break; | ||||
} | } | ||||
// Rotate towards the next waypoint and continue moving. | // Rotate towards the next waypoint and continue moving. | ||||
angle = atan2_approx(offset.X, offset.Y); | angle = atan2_approx(offset.X, offset.Y); | ||||
// Give some 'free' rotation for angles below 0.5 radians. | timeLeft = std::min(maxRotation, maxRotation - absoluteAngleDiff + m_InstantTurnAngle) / turnRate; | ||||
timeLeft = (std::min(maxRotation, maxRotation - absoluteAngleDiff + fixed::FromInt(1)/2)) / turnRate; | |||||
} | } | ||||
} | } | ||||
else | |||||
{ | |||||
// Modify the speed depending on the angle difference. | |||||
fixed sin, cos; | |||||
sincos_approx(angleDiff, sin, cos); | |||||
speed = speed.Multiply(cos); | |||||
} | |||||
// Work out how far we can travel in timeLeft. | // Work out how far we can travel in timeLeft. | ||||
fixed maxdist = maxSpeed.Multiply(timeLeft); | fixed accelTime = std::min(timeLeft, (maxSpeed - speed) / m_Acceleration); | ||||
fixed accelDist = speed.Multiply(accelTime) + accelTime.Square().Multiply(m_Acceleration) / 2; | |||||
fixed maxdist = accelDist + maxSpeed.Multiply(timeLeft - accelTime); | |||||
// If the target is close, we can move there directly. | // If the target is close, we can move there directly. | ||||
fixed offsetLength = offset.Length(); | fixed offsetLength = offset.Length(); | ||||
if (offsetLength <= maxdist) | if (offsetLength <= maxdist) | ||||
{ | { | ||||
if (cmpPathfinder->CheckMovement(GetObstructionFilter(specificIgnore), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass)) | if (cmpPathfinder->CheckMovement(GetObstructionFilter(specificIgnore), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass)) | ||||
{ | { | ||||
pos = target; | pos = target; | ||||
// Spend the rest of the time heading towards the next waypoint. | // Spend the rest of the time heading towards the next waypoint. | ||||
timeLeft = (maxdist - offsetLength) / maxSpeed; | // Either we still need to accelerate after, or we have reached maxSpeed. | ||||
// The former is much less likely than the latter: usually we can reach | |||||
// maxSpeed within one waypoint. So the Sqrt is not too bad. | |||||
if (offsetLength <= accelDist) | |||||
{ | |||||
fixed requiredTime = (-speed + (speed.Square() + offsetLength.Multiply(m_Acceleration).Multiply(fixed::FromInt(2))).Sqrt()) / m_Acceleration; | |||||
timeLeft -= requiredTime; | |||||
speed += m_Acceleration.Multiply(requiredTime); | |||||
} | |||||
else | |||||
{ | |||||
timeLeft -= accelTime + (offsetLength - accelDist) / maxSpeed; | |||||
speed = maxSpeed; | |||||
} | |||||
if (shortPath.m_Waypoints.empty()) | if (shortPath.m_Waypoints.empty()) | ||||
longPath.m_Waypoints.pop_back(); | longPath.m_Waypoints.pop_back(); | ||||
else | else | ||||
shortPath.m_Waypoints.pop_back(); | shortPath.m_Waypoints.pop_back(); | ||||
continue; | continue; | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
// Error - path was obstructed. | // Error - path was obstructed. | ||||
return true; | return true; | ||||
} | } | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
// Not close enough, so just move in the right direction. | // Not close enough, so just move in the right direction. | ||||
offset.Normalize(maxdist); | offset.Normalize(maxdist); | ||||
target = pos + offset; | target = pos + offset; | ||||
speed = std::min(maxSpeed, speed + m_Acceleration.Multiply(timeLeft)); | |||||
if (cmpPathfinder->CheckMovement(GetObstructionFilter(specificIgnore), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass)) | if (cmpPathfinder->CheckMovement(GetObstructionFilter(specificIgnore), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass)) | ||||
pos = target; | pos = target; | ||||
else | else | ||||
return true; | return true; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
void CCmpUnitMotion::UpdateMovementState(entity_pos_t speed) | void CCmpUnitMotion::UpdateMovementState(entity_pos_t speed, entity_pos_t meanSpeed) | ||||
{ | { | ||||
CmpPtr<ICmpVisual> cmpVisual(GetEntityHandle()); | CmpPtr<ICmpVisual> cmpVisual(GetEntityHandle()); | ||||
if (cmpVisual) | if (cmpVisual) | ||||
{ | { | ||||
if (speed == fixed::Zero()) | if (meanSpeed == fixed::Zero()) | ||||
cmpVisual->SelectMovementAnimation("idle", fixed::FromInt(1)); | cmpVisual->SelectMovementAnimation("idle", fixed::FromInt(1)); | ||||
else | else | ||||
cmpVisual->SelectMovementAnimation(speed > (m_WalkSpeed / 2).Multiply(m_RunMultiplier + fixed::FromInt(1)) ? "run" : "walk", speed); | cmpVisual->SelectMovementAnimation(meanSpeed > (m_WalkSpeed / 2).Multiply(m_RunMultiplier + fixed::FromInt(1)) ? "run" : "walk", meanSpeed); | ||||
} | } | ||||
m_CurSpeed = speed; | m_CurrentSpeed = speed; | ||||
} | } | ||||
bool CCmpUnitMotion::HandleObstructedMove(bool moved) | bool CCmpUnitMotion::HandleObstructedMove(bool moved) | ||||
{ | { | ||||
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | ||||
if (!cmpPosition || !cmpPosition->IsInWorld()) | if (!cmpPosition || !cmpPosition->IsInWorld()) | ||||
return false; | return false; | ||||
▲ Show 20 Lines • Show All 576 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator
might be cool to set speed as a function of the absoluteAngleDiff
this way larger turns slow a unit down more than small ones.