Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp
Show First 20 Lines • Show All 113 Lines • ▼ Show 20 Lines | |||||
static const CColor OVERLAY_COLOR_LONG_PATH(1, 1, 1, 1); | static const CColor OVERLAY_COLOR_LONG_PATH(1, 1, 1, 1); | ||||
static const CColor OVERLAY_COLOR_SHORT_PATH(1, 0, 0, 1); | static const CColor OVERLAY_COLOR_SHORT_PATH(1, 0, 0, 1); | ||||
class CCmpUnitMotion : public ICmpUnitMotion | class CCmpUnitMotion : public ICmpUnitMotion | ||||
{ | { | ||||
public: | public: | ||||
static void ClassInit(CComponentManager& componentManager) | static void ClassInit(CComponentManager& componentManager) | ||||
{ | { | ||||
componentManager.SubscribeToMessageType(MT_TurnStart); | componentManager.SubscribeToMessageType(MT_Create); | ||||
componentManager.SubscribeToMessageType(MT_Update_MotionFormation); | componentManager.SubscribeToMessageType(MT_Destroy); | ||||
componentManager.SubscribeToMessageType(MT_Update_MotionUnit); | |||||
componentManager.SubscribeToMessageType(MT_PathResult); | componentManager.SubscribeToMessageType(MT_PathResult); | ||||
componentManager.SubscribeToMessageType(MT_OwnershipChanged); | componentManager.SubscribeToMessageType(MT_OwnershipChanged); | ||||
componentManager.SubscribeToMessageType(MT_ValueModification); | componentManager.SubscribeToMessageType(MT_ValueModification); | ||||
componentManager.SubscribeToMessageType(MT_Deserialized); | componentManager.SubscribeToMessageType(MT_Deserialized); | ||||
} | } | ||||
DEFAULT_COMPONENT_ALLOCATOR(UnitMotion) | DEFAULT_COMPONENT_ALLOCATOR(UnitMotion) | ||||
▲ Show 20 Lines • Show All 69 Lines • ▼ Show 20 Lines | public: | ||||
// Current mean speed (over the last turn). | // Current mean speed (over the last turn). | ||||
fixed m_CurSpeed; | fixed m_CurSpeed; | ||||
// 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; | ||||
// Hack - units move one-at-a-time, so they may need to interplate their target position. | |||||
// However, some computations are not doing during the motion messages, and those shouldn't (e.g. turn start). | |||||
// This is true if and only if the calls take place during handling of the entity's MT_Motion* messages. | |||||
// NB: this won't be true if we end up in UnitMotion because of another entity's motion messages, | |||||
// but I think it fixes the issue of interpolating target position OK for current needs, | |||||
// without having to add parameters everywhere. | |||||
// No need for serialisation, it's just a transient boolean. | |||||
bool m_InMotionMessage = false; | |||||
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>" | ||||
▲ Show 20 Lines • Show All 89 Lines • ▼ Show 20 Lines | virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) | ||||
if (cmpPathfinder) | if (cmpPathfinder) | ||||
m_PassClass = cmpPathfinder->GetPassabilityClass(m_PassClassName); | m_PassClass = cmpPathfinder->GetPassabilityClass(m_PassClassName); | ||||
} | } | ||||
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) | virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) | ||||
{ | { | ||||
switch (msg.GetType()) | switch (msg.GetType()) | ||||
{ | { | ||||
case MT_TurnStart: | |||||
{ | |||||
TurnStart(); | |||||
break; | |||||
} | |||||
case MT_Update_MotionFormation: | |||||
{ | |||||
if (m_FormationController) | |||||
{ | |||||
m_InMotionMessage = true; | |||||
fixed dt = static_cast<const CMessageUpdate_MotionFormation&> (msg).turnLength; | |||||
Move(dt); | |||||
m_InMotionMessage = false; | |||||
} | |||||
break; | |||||
} | |||||
case MT_Update_MotionUnit: | |||||
{ | |||||
if (!m_FormationController) | |||||
{ | |||||
m_InMotionMessage = true; | |||||
fixed dt = static_cast<const CMessageUpdate_MotionUnit&> (msg).turnLength; | |||||
Move(dt); | |||||
m_InMotionMessage = false; | |||||
} | |||||
break; | |||||
} | |||||
case MT_RenderSubmit: | case MT_RenderSubmit: | ||||
{ | { | ||||
PROFILE("UnitMotion::RenderSubmit"); | PROFILE("UnitMotion::RenderSubmit"); | ||||
const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg); | const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg); | ||||
RenderSubmit(msgData.collector); | RenderSubmit(msgData.collector); | ||||
break; | break; | ||||
} | } | ||||
case MT_PathResult: | case MT_PathResult: | ||||
{ | { | ||||
const CMessagePathResult& msgData = static_cast<const CMessagePathResult&> (msg); | const CMessagePathResult& msgData = static_cast<const CMessagePathResult&> (msg); | ||||
PathResult(msgData.ticket, msgData.path); | PathResult(msgData.ticket, msgData.path); | ||||
break; | break; | ||||
} | } | ||||
case MT_Create: | |||||
{ | |||||
if (!ENTITY_IS_LOCAL(GetEntityId())) | |||||
CmpPtr<ICmpUnitMotionManager>(GetSystemEntity())->Register(GetEntityId(), m_FormationController); | |||||
break; | |||||
} | |||||
case MT_Destroy: | |||||
{ | |||||
if (!ENTITY_IS_LOCAL(GetEntityId())) | |||||
CmpPtr<ICmpUnitMotionManager>(GetSystemEntity())->Unregister(GetEntityId()); | |||||
break; | |||||
} | |||||
case MT_ValueModification: | case MT_ValueModification: | ||||
{ | { | ||||
const CMessageValueModification& msgData = static_cast<const CMessageValueModification&> (msg); | const CMessageValueModification& msgData = static_cast<const CMessageValueModification&> (msg); | ||||
if (msgData.component != L"UnitMotion") | if (msgData.component != L"UnitMotion") | ||||
break; | break; | ||||
FALLTHROUGH; | FALLTHROUGH; | ||||
} | } | ||||
case MT_OwnershipChanged: | case MT_OwnershipChanged: | ||||
case MT_Deserialized: | |||||
{ | { | ||||
CmpPtr<ICmpValueModificationManager> cmpValueModificationManager(GetSystemEntity()); | OnValueModification(); | ||||
if (!cmpValueModificationManager) | |||||
break; | break; | ||||
} | |||||
m_WalkSpeed = cmpValueModificationManager->ApplyModifications(L"UnitMotion/WalkSpeed", m_TemplateWalkSpeed, GetEntityId()); | case MT_Deserialized: | ||||
m_RunMultiplier = cmpValueModificationManager->ApplyModifications(L"UnitMotion/RunMultiplier", m_TemplateRunMultiplier, GetEntityId()); | { | ||||
OnValueModification(); | |||||
// For MT_Deserialize compute m_Speed from the serialized m_SpeedMultiplier. | if (!ENTITY_IS_LOCAL(GetEntityId())) | ||||
// For MT_ValueModification and MT_OwnershipChanged, adjust m_SpeedMultiplier if needed | CmpPtr<ICmpUnitMotionManager>(GetSystemEntity())->Register(GetEntityId(), m_FormationController); | ||||
// (in case then new m_RunMultiplier value is lower than the old). | |||||
SetSpeedMultiplier(m_SpeedMultiplier); | |||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
void UpdateMessageSubscriptions() | void UpdateMessageSubscriptions() | ||||
{ | { | ||||
bool needRender = m_DebugOverlayEnabled; | bool needRender = m_DebugOverlayEnabled; | ||||
▲ Show 20 Lines • Show All 252 Lines • ▼ Show 20 Lines | entity_pos_t ShortPathSearchRange() const | ||||
return searchRange; | return searchRange; | ||||
} | } | ||||
/** | /** | ||||
* Handle the result of an asynchronous path query. | * Handle the result of an asynchronous path query. | ||||
*/ | */ | ||||
void PathResult(u32 ticket, const WaypointPath& path); | void PathResult(u32 ticket, const WaypointPath& path); | ||||
void OnValueModification() | |||||
{ | |||||
CmpPtr<ICmpValueModificationManager> cmpValueModificationManager(GetSystemEntity()); | |||||
if (!cmpValueModificationManager) | |||||
return; | |||||
m_WalkSpeed = cmpValueModificationManager->ApplyModifications(L"UnitMotion/WalkSpeed", m_TemplateWalkSpeed, GetEntityId()); | |||||
m_RunMultiplier = cmpValueModificationManager->ApplyModifications(L"UnitMotion/RunMultiplier", m_TemplateRunMultiplier, GetEntityId()); | |||||
// For MT_Deserialize compute m_Speed from the serialized m_SpeedMultiplier. | |||||
// For MT_ValueModification and MT_OwnershipChanged, adjust m_SpeedMultiplier if needed | |||||
// (in case then new m_RunMultiplier value is lower than the old). | |||||
SetSpeedMultiplier(m_SpeedMultiplier); | |||||
} | |||||
/** | /** | ||||
* Check if we are at destination early in the turn, this both lets units react faster | * Check if we are at destination early in the turn, this both lets units react faster | ||||
* and ensure that distance comparisons are done while units are not being moved | * and ensure that distance comparisons are done while units are not being moved | ||||
* (otherwise they won't be commutative). | * (otherwise they won't be commutative). | ||||
*/ | */ | ||||
void TurnStart(); | virtual void OnTurnStart(); | ||||
/** | virtual void PreMove(ICmpUnitMotionManager::MotionState& state); | ||||
* Do the per-turn movement and other updates. | |||||
*/ | virtual void Move(ICmpUnitMotionManager::MotionState& state, fixed dt); | ||||
void Move(fixed dt); | |||||
virtual void PostMove(ICmpUnitMotionManager::MotionState& state, fixed dt); | |||||
/** | /** | ||||
* Returns true if we are possibly at our destination. | * Returns true if we are possibly at our destination. | ||||
* Since the concept of being at destination is dependent on why the move was requested, | * Since the concept of being at destination is dependent on why the move was requested, | ||||
* UnitMotion can only ever hint about this, hence the conditional tone. | * UnitMotion can only ever hint about this, hence the conditional tone. | ||||
*/ | */ | ||||
bool PossiblyAtDestination() const; | bool PossiblyAtDestination() const; | ||||
▲ Show 20 Lines • Show All 230 Lines • ▼ Show 20 Lines | if (PathingUpdateNeeded(pos)) | ||||
if (!IncrementFailedMovementsAndMaybeNotify()) | if (!IncrementFailedMovementsAndMaybeNotify()) | ||||
MoveObstructed(); | MoveObstructed(); | ||||
// We'll automatically recompute a path when this reaches 0, as a way to improve behaviour. | // We'll automatically recompute a path when this reaches 0, as a way to improve behaviour. | ||||
// (See D665 - this is needed because the target may be moving, and we should adjust to that). | // (See D665 - this is needed because the target may be moving, and we should adjust to that). | ||||
m_FollowKnownImperfectPathCountdown = KNOWN_IMPERFECT_PATH_RESET_COUNTDOWN; | m_FollowKnownImperfectPathCountdown = KNOWN_IMPERFECT_PATH_RESET_COUNTDOWN; | ||||
} | } | ||||
} | } | ||||
void CCmpUnitMotion::TurnStart() | void CCmpUnitMotion::OnTurnStart() | ||||
{ | { | ||||
if (PossiblyAtDestination()) | if (PossiblyAtDestination()) | ||||
MoveSucceeded(); | MoveSucceeded(); | ||||
else if (!TargetHasValidPosition()) | else if (!TargetHasValidPosition()) | ||||
{ | { | ||||
// Scrap waypoints - we don't know where to go. | // Scrap waypoints - we don't know where to go. | ||||
// If the move request remains unchanged and the target again has a valid position later on, | // If the move request remains unchanged and the target again has a valid position later on, | ||||
// moving will be resumed. | // moving will be resumed. | ||||
// Units may want to move to move to the target's last known position, | // Units may want to move to move to the target's last known position, | ||||
// but that should be decided by UnitAI (handling MoveFailed), not UnitMotion. | // but that should be decided by UnitAI (handling MoveFailed), not UnitMotion. | ||||
m_LongPath.m_Waypoints.clear(); | m_LongPath.m_Waypoints.clear(); | ||||
m_ShortPath.m_Waypoints.clear(); | m_ShortPath.m_Waypoints.clear(); | ||||
MoveFailed(); | MoveFailed(); | ||||
} | } | ||||
} | } | ||||
void CCmpUnitMotion::Move(fixed dt) | void CCmpUnitMotion::PreMove(ICmpUnitMotionManager::MotionState& state) | ||||
{ | { | ||||
PROFILE("Move"); | // If we were idle and will still be, no need for an update. | ||||
state.needUpdate = m_CurSpeed != fixed::Zero() || m_MoveRequest.m_Type != MoveRequest::NONE; | |||||
// If we were idle and will still be, we can return. | } | ||||
// TODO: this will need to be removed if pushing is implemented. | |||||
if (m_CurSpeed == fixed::Zero() && m_MoveRequest.m_Type == MoveRequest::NONE) | |||||
return; | |||||
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | |||||
if (!cmpPosition || !cmpPosition->IsInWorld()) | |||||
return; | |||||
CFixedVector2D initialPos = cmpPosition->GetPosition2D(); | |||||
entity_angle_t initialAngle = cmpPosition->GetRotation().Y; | |||||
// Keep track of the current unit's position and rotation during the update. | void CCmpUnitMotion::Move(ICmpUnitMotionManager::MotionState& state, fixed dt) | ||||
CFixedVector2D pos = initialPos; | { | ||||
entity_angle_t angle = initialAngle; | 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. | ||||
bool wentStraight = TryGoingStraightToTarget(initialPos); | state.wentStraight = TryGoingStraightToTarget(state.initialPos); | ||||
bool wasObstructed = PerformMove(dt, cmpPosition->GetTurnRate(), m_ShortPath, m_LongPath, pos, angle); | state.wasObstructed = PerformMove(dt, state.cmpPosition->GetTurnRate(), m_ShortPath, m_LongPath, state.pos, state.angle); | ||||
} | |||||
void CCmpUnitMotion::PostMove(ICmpUnitMotionManager::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 (pos == initialPos) | if (state.pos == state.initialPos) | ||||
{ | { | ||||
if (angle != initialAngle) | if (state.angle != state.initialAngle) | ||||
cmpPosition->TurnTo(angle); | state.cmpPosition->TurnTo(state.angle); | ||||
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) | ||||
// When moving always set the angle in the direction of the movement. | // When moving always set the angle in the direction of the movement. | ||||
CFixedVector2D offset = pos - initialPos; | CFixedVector2D offset = state.pos - state.initialPos; | ||||
angle = atan2_approx(offset.X, offset.Y); | state.angle = atan2_approx(offset.X, offset.Y); | ||||
cmpPosition->MoveAndTurnTo(pos.X, pos.Y, 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(offset.Length() / dt); | ||||
} | } | ||||
if (wasObstructed && HandleObstructedMove(pos != initialPos)) | if (state.wasObstructed && HandleObstructedMove(state.pos != state.initialPos)) | ||||
return; | return; | ||||
else if (!wasObstructed && pos != initialPos) | else if (!state.wasObstructed && state.pos != state.initialPos) | ||||
m_FailedMovements = 0; | m_FailedMovements = 0; | ||||
// 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 (!wentStraight && PathingUpdateNeeded(pos)) | if (!state.wentStraight && PathingUpdateNeeded(state.pos)) | ||||
{ | { | ||||
PathGoal goal; | PathGoal goal; | ||||
if (ComputeGoal(goal, m_MoveRequest)) | if (ComputeGoal(goal, m_MoveRequest)) | ||||
ComputePathToGoal(pos, goal); | ComputePathToGoal(state.pos, goal); | ||||
} | } | ||||
else if (m_FollowKnownImperfectPathCountdown > 0) | else if (m_FollowKnownImperfectPathCountdown > 0) | ||||
--m_FollowKnownImperfectPathCountdown; | --m_FollowKnownImperfectPathCountdown; | ||||
} | } | ||||
bool CCmpUnitMotion::PossiblyAtDestination() const | bool CCmpUnitMotion::PossiblyAtDestination() const | ||||
{ | { | ||||
if (m_MoveRequest.m_Type == MoveRequest::NONE) | if (m_MoveRequest.m_Type == MoveRequest::NONE) | ||||
▲ Show 20 Lines • Show All 276 Lines • ▼ Show 20 Lines | bool CCmpUnitMotion::ComputeTargetPosition(CFixedVector2D& out, const MoveRequest& moveRequest) const | ||||
{ | { | ||||
out = cmpTargetPosition->GetPosition2D(); | out = cmpTargetPosition->GetPosition2D(); | ||||
// Because units move one-at-a-time and pathing is asynchronous, we need to account for target movement, | // Because units move one-at-a-time and pathing is asynchronous, we need to account for target movement, | ||||
// if we are computing this during the MT_Motion* part of the turn. | // if we are computing this during the MT_Motion* part of the turn. | ||||
// If our entity ID is lower, we move first, and so we need to add a predicted movement to compute a path for next turn. | // If our entity ID is lower, we move first, and so we need to add a predicted movement to compute a path for next turn. | ||||
// If our entity ID is higher, the target has already moved, so we can just use the position directly. | // If our entity ID is higher, the target has already moved, so we can just use the position directly. | ||||
// TODO: This does not really aim many turns in advance, with orthogonal trajectories it probably should. | // TODO: This does not really aim many turns in advance, with orthogonal trajectories it probably should. | ||||
CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), moveRequest.m_Entity); | CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), moveRequest.m_Entity); | ||||
bool needInterpolation = cmpUnitMotion && cmpUnitMotion->IsMoveRequested() && m_InMotionMessage; | CmpPtr<ICmpUnitMotionManager> cmpUnitMotionManager(GetSystemEntity()); | ||||
bool needInterpolation = cmpUnitMotion && cmpUnitMotion->IsMoveRequested() && cmpUnitMotionManager->ComputingMotion(); | |||||
if (needInterpolation && GetEntityId() < moveRequest.m_Entity) | if (needInterpolation && GetEntityId() < moveRequest.m_Entity) | ||||
{ | { | ||||
// Add predicted movement. | // Add predicted movement. | ||||
CFixedVector2D tempPos = out + (out - cmpTargetPosition->GetPreviousPosition2D()); | CFixedVector2D tempPos = out + (out - cmpTargetPosition->GetPreviousPosition2D()); | ||||
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | ||||
if (!cmpPosition || !cmpPosition->IsInWorld()) | if (!cmpPosition || !cmpPosition->IsInWorld()) | ||||
return true; // Still return true since we don't need a position for the target to have one. | return true; // Still return true since we don't need a position for the target to have one. | ||||
▲ Show 20 Lines • Show All 453 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator