2012-02-08 03:46:15 +01:00
/* Copyright (C) 2012 Wildfire Games.
2010-01-09 20:20:14 +01:00
* This file is part of 0 A . D .
*
* 0 A . D . is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 2 of the License , or
* ( at your option ) any later version .
*
* 0 A . D . is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with 0 A . D . If not , see < http : //www.gnu.org/licenses/>.
*/
# include "precompiled.h"
# include "simulation2/system/Component.h"
# include "ICmpUnitMotion.h"
2011-08-06 10:11:05 +02:00
# include "simulation2/components/ICmpObstruction.h"
# include "simulation2/components/ICmpObstructionManager.h"
# include "simulation2/components/ICmpOwnership.h"
# include "simulation2/components/ICmpPosition.h"
# include "simulation2/components/ICmpPathfinder.h"
# include "simulation2/components/ICmpRangeManager.h"
2010-04-30 01:36:05 +02:00
# include "simulation2/helpers/Geometry.h"
# include "simulation2/helpers/Render.h"
2011-08-06 10:11:05 +02:00
# include "simulation2/MessageTypes.h"
2010-09-17 19:53:26 +02:00
# include "simulation2/serialization/SerializeTemplates.h"
2010-04-30 01:36:05 +02:00
# include "graphics/Overlay.h"
# include "graphics/Terrain.h"
# include "maths/FixedVector2D.h"
2010-09-03 11:55:14 +02:00
# include "ps/CLogger.h"
2010-04-30 01:36:05 +02:00
# include "ps/Profile.h"
# include "renderer/Scene.h"
2010-11-30 13:31:54 +01:00
/**
* When advancing along the long path , and picking a new waypoint to move
* towards , we ' ll pick one that ' s up to this far from the unit ' s current
* position ( to minimise the effects of grid - constrained movement )
*/
2012-01-12 13:51:10 +01:00
static const entity_pos_t WAYPOINT_ADVANCE_MAX = entity_pos_t : : FromInt ( TERRAIN_TILE_SIZE * 8 ) ;
2010-11-30 13:31:54 +01:00
2010-11-30 15:48:04 +01:00
/**
* When advancing along the long path , we ' ll pick a new waypoint to move
* towards if we expect to reach the end of our current short path within
* this many turns ( assuming constant speed and turn length ) .
* ( This could typically be 1 , but we need some tolerance in case speeds
* or turn lengths change . )
*/
static const int WAYPOINT_ADVANCE_LOOKAHEAD_TURNS = 4 ;
2010-11-30 13:31:54 +01:00
/**
* Maximum range to restrict short path queries to . ( Larger ranges are slower ,
* smaller ranges might miss some legitimate routes around large obstacles . )
*/
2012-01-12 13:51:10 +01:00
static const entity_pos_t SHORT_PATH_SEARCH_RANGE = entity_pos_t : : FromInt ( TERRAIN_TILE_SIZE * 10 ) ;
2010-11-30 13:31:54 +01:00
/**
* When short - pathing to an intermediate waypoint , we aim for a circle of this radius
* around the waypoint rather than expecting to reach precisely the waypoint itself
* ( since it might be inside an obstacle ) .
*/
2012-01-12 13:51:10 +01:00
static const entity_pos_t SHORT_PATH_GOAL_RADIUS = entity_pos_t : : FromInt ( TERRAIN_TILE_SIZE * 3 / 2 ) ;
2010-04-30 01:36:05 +02:00
2010-11-30 13:31:54 +01:00
/**
* If we are this close to our target entity / point , then think about heading
* for it in a straight line instead of pathfinding .
*/
2012-01-12 13:51:10 +01:00
static const entity_pos_t DIRECT_PATH_RANGE = entity_pos_t : : FromInt ( TERRAIN_TILE_SIZE * 4 ) ;
2010-11-30 13:31:54 +01:00
/**
* If we ' re following a target entity ,
* we will recompute our path if the target has moved
* more than this distance from where we last pathed to .
*/
2012-01-12 13:51:10 +01:00
static const entity_pos_t CHECK_TARGET_MOVEMENT_MIN_DELTA = entity_pos_t : : FromInt ( TERRAIN_TILE_SIZE * 4 ) ;
2010-11-30 13:31:54 +01:00
/**
* If we ' re following as part of a formation ,
* but can ' t move to our assigned target point in a straight line ,
* we will recompute our path if the target has moved
* more than this distance from where we last pathed to .
*/
2012-01-12 13:51:10 +01:00
static const entity_pos_t CHECK_TARGET_MOVEMENT_MIN_DELTA_FORMATION = entity_pos_t : : FromInt ( TERRAIN_TILE_SIZE * 1 ) ;
2010-11-30 13:31:54 +01:00
/**
* If we ' re following something but it ' s more than this distance away along
* our path , then don ' t bother trying to repath regardless of how much it has
* moved , until we get this close to the end of our old path .
*/
2012-01-12 13:51:10 +01:00
static const entity_pos_t CHECK_TARGET_MOVEMENT_AT_MAX_DIST = entity_pos_t : : FromInt ( TERRAIN_TILE_SIZE * 16 ) ;
2010-11-30 13:31:54 +01:00
static const CColor OVERLAY_COLOUR_LONG_PATH ( 1 , 1 , 1 , 1 ) ;
2010-04-30 01:36:05 +02:00
static const CColor OVERLAY_COLOUR_SHORT_PATH ( 1 , 0 , 0 , 1 ) ;
2010-11-30 13:31:54 +01:00
2012-01-12 13:51:10 +01:00
static const entity_pos_t g_GoalDelta = entity_pos_t : : FromInt ( TERRAIN_TILE_SIZE ) / 4 ; // for extending the goal outwards/inwards a little bit
2010-01-09 20:20:14 +01:00
class CCmpUnitMotion : public ICmpUnitMotion
{
public :
static void ClassInit ( CComponentManager & componentManager )
{
2010-09-03 11:55:14 +02:00
componentManager . SubscribeToMessageType ( MT_Update_MotionFormation ) ;
componentManager . SubscribeToMessageType ( MT_Update_MotionUnit ) ;
2010-04-30 01:36:05 +02:00
componentManager . SubscribeToMessageType ( MT_RenderSubmit ) ; // for debug overlays
2010-09-03 11:55:14 +02:00
componentManager . SubscribeToMessageType ( MT_PathResult ) ;
2010-01-09 20:20:14 +01:00
}
DEFAULT_COMPONENT_ALLOCATOR ( UnitMotion )
2010-04-30 01:36:05 +02:00
bool m_DebugOverlayEnabled ;
2010-11-30 13:31:54 +01:00
std : : vector < SOverlayLine > m_DebugOverlayLongPathLines ;
2010-04-30 01:36:05 +02:00
std : : vector < SOverlayLine > m_DebugOverlayShortPathLines ;
2010-01-09 20:20:14 +01:00
// Template state:
2010-02-10 20:28:46 +01:00
2010-09-03 11:55:14 +02:00
bool m_FormationController ;
2010-07-18 17:19:49 +02:00
fixed m_WalkSpeed ; // in metres per second
2010-06-05 02:49:14 +02:00
fixed m_RunSpeed ;
2011-08-06 10:11:05 +02:00
ICmpPathfinder : : pass_class_t m_PassClass ;
ICmpPathfinder : : cost_class_t m_CostClass ;
2010-01-09 20:20:14 +01:00
// Dynamic state:
2010-02-10 20:28:46 +01:00
2010-09-03 11:55:14 +02:00
entity_pos_t m_Radius ;
2012-09-04 05:57:22 +02:00
bool m_Moving ;
2010-01-09 20:20:14 +01:00
2010-09-17 19:53:26 +02:00
enum State
2010-02-10 20:28:46 +01:00
{
2010-11-30 13:31:54 +01:00
/*
* Not moving at all .
*/
STATE_IDLE ,
/*
* Not moving at all . Will go to IDLE next turn .
* ( This one - turn delay is a hack to fix animation timings . )
*/
STATE_STOPPING ,
/*
* Member of a formation .
* Pathing to the target ( depending on m_PathState ) .
* Target is m_TargetEntity plus m_TargetOffset .
*/
STATE_FORMATIONMEMBER_PATH ,
/*
* Individual unit or formation controller .
* Pathing to the target ( depending on m_PathState ) .
* Target is m_TargetPos , m_TargetMinRange , m_TargetMaxRange ;
* if m_TargetEntity is not INVALID_ENTITY then m_TargetPos is tracking it .
*/
STATE_INDIVIDUAL_PATH ,
2010-09-17 19:53:26 +02:00
STATE_MAX
2010-02-10 20:28:46 +01:00
} ;
2010-11-30 13:31:54 +01:00
u8 m_State ;
enum PathState
{
/*
* There is no path .
* ( This should only happen in IDLE and STOPPING . )
*/
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 ,
/*
* We are following our path , and have an outstanding short path request
* to append to our current path .
* ( This is because we got near the end of our short path and need
* to extend it to continue along the long path ) .
* m_LongPath and m_ShortPath are valid .
*/
PATHSTATE_FOLLOWING_REQUESTING_SHORT_APPEND ,
PATHSTATE_MAX
} ;
u8 m_PathState ;
2010-02-10 20:28:46 +01:00
2010-09-03 11:55:14 +02:00
u32 m_ExpectedPathTicket ; // asynchronous request ID we're waiting for, or 0 if none
entity_id_t m_TargetEntity ;
2010-11-30 13:31:54 +01:00
CFixedVector2D m_TargetPos ;
CFixedVector2D m_TargetOffset ;
entity_pos_t m_TargetMinRange ;
entity_pos_t m_TargetMaxRange ;
2010-09-03 11:55:14 +02:00
fixed m_Speed ;
2012-12-06 20:46:13 +01:00
// Current mean speed (over the last turn).
fixed m_CurSpeed ;
2010-11-30 13:31:54 +01:00
// Currently active paths (storing waypoints in reverse order).
// The last item in each path is the point we're currently heading towards.
2010-09-03 11:55:14 +02:00
ICmpPathfinder : : Path m_LongPath ;
ICmpPathfinder : : Path m_ShortPath ;
2010-11-30 13:31:54 +01:00
2010-09-03 11:55:14 +02:00
ICmpPathfinder : : Goal m_FinalGoal ;
2010-04-09 21:02:39 +02:00
static std : : string GetSchema ( )
{
return
2010-04-23 18:09:03 +02:00
" <a:help>Provides the unit with the ability to move around the world by itself.</a:help> "
" <a:example> "
" <WalkSpeed>7.0</WalkSpeed> "
2010-05-28 01:31:03 +02:00
" <PassabilityClass>default</PassabilityClass> "
" <CostClass>infantry</CostClass> "
2010-04-23 18:09:03 +02:00
" </a:example> "
2010-09-03 11:55:14 +02:00
" <element name='FormationController'> "
" <data type='boolean'/> "
" </element> "
2010-04-23 18:09:03 +02:00
" <element name='WalkSpeed' a:help='Basic movement speed (in metres per second)'> "
2010-04-09 21:02:39 +02:00
" <ref name='positiveDecimal'/> "
2010-05-15 23:07:52 +02:00
" </element> "
" <optional> "
" <element name='Run'> "
" <interleave> "
" <element name='Speed'><ref name='positiveDecimal'/></element> "
" <element name='Range'><ref name='positiveDecimal'/></element> "
" <element name='RangeMin'><ref name='nonNegativeDecimal'/></element> "
" <element name='RegenTime'><ref name='positiveDecimal'/></element> "
" <element name='DecayTime'><ref name='positiveDecimal'/></element> "
" </interleave> "
" </element> "
2010-05-28 01:31:03 +02:00
" </optional> "
" <element name='PassabilityClass' a:help='Identifies the terrain passability class (values are defined in special/pathfinder.xml)'> "
" <text/> "
" </element> "
" <element name='CostClass' a:help='Identifies the movement speed/cost class (values are defined in special/pathfinder.xml)'> "
" <text/> "
" </element> " ;
2010-04-09 21:02:39 +02:00
}
2010-04-23 18:09:03 +02:00
2010-05-15 23:07:52 +02:00
/*
* TODO : the running / charging thing needs to be designed and implemented
*/
2011-01-16 15:08:38 +01:00
virtual void Init ( const CParamNode & paramNode )
2010-01-09 20:20:14 +01:00
{
2010-09-03 11:55:14 +02:00
m_FormationController = paramNode . GetChild ( " FormationController " ) . ToBool ( ) ;
2010-02-07 21:06:16 +01:00
2012-09-04 05:57:22 +02:00
m_Moving = false ;
2010-07-18 17:19:49 +02:00
m_WalkSpeed = paramNode . GetChild ( " WalkSpeed " ) . ToFixed ( ) ;
m_Speed = m_WalkSpeed ;
2012-12-06 20:46:13 +01:00
m_CurSpeed = fixed : : Zero ( ) ;
2010-02-10 20:28:46 +01:00
2010-06-05 02:49:14 +02:00
if ( paramNode . GetChild ( " Run " ) . IsOk ( ) )
{
m_RunSpeed = paramNode . GetChild ( " Run " ) . GetChild ( " Speed " ) . ToFixed ( ) ;
}
else
{
2010-07-18 17:19:49 +02:00
m_RunSpeed = m_WalkSpeed ;
2010-06-05 02:49:14 +02:00
}
2011-01-16 15:08:38 +01:00
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSimContext ( ) , SYSTEM_ENTITY ) ;
2012-02-08 03:46:15 +01:00
if ( cmpPathfinder )
2010-05-28 01:31:03 +02:00
{
2011-02-17 21:08:20 +01:00
m_PassClass = cmpPathfinder - > GetPassabilityClass ( paramNode . GetChild ( " PassabilityClass " ) . ToUTF8 ( ) ) ;
m_CostClass = cmpPathfinder - > GetCostClass ( paramNode . GetChild ( " CostClass " ) . ToUTF8 ( ) ) ;
2010-05-28 01:31:03 +02:00
}
2011-01-16 15:08:38 +01:00
CmpPtr < ICmpObstruction > cmpObstruction ( GetSimContext ( ) , GetEntityId ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( cmpObstruction )
2010-09-17 19:53:26 +02:00
m_Radius = cmpObstruction - > GetUnitRadius ( ) ;
2010-09-03 11:55:14 +02:00
m_State = STATE_IDLE ;
2010-11-30 13:31:54 +01:00
m_PathState = PATHSTATE_NONE ;
2010-09-03 11:55:14 +02:00
m_ExpectedPathTicket = 0 ;
2010-04-30 01:36:05 +02:00
2010-11-30 13:31:54 +01:00
m_TargetEntity = INVALID_ENTITY ;
2010-09-17 19:53:26 +02:00
m_FinalGoal . type = ICmpPathfinder : : Goal : : POINT ;
2010-04-30 01:36:05 +02:00
m_DebugOverlayEnabled = false ;
2010-01-09 20:20:14 +01:00
}
2011-01-16 15:08:38 +01:00
virtual void Deinit ( )
2010-01-09 20:20:14 +01:00
{
}
2010-11-30 13:31:54 +01:00
template < typename S >
void SerializeCommon ( S & serialize )
2010-01-09 20:20:14 +01:00
{
2010-09-17 19:53:26 +02:00
serialize . NumberFixed_Unbounded ( " radius " , m_Radius ) ;
serialize . NumberU8 ( " state " , m_State , 0 , STATE_MAX - 1 ) ;
2010-11-30 13:31:54 +01:00
serialize . NumberU8 ( " path state " , m_PathState , 0 , PATHSTATE_MAX - 1 ) ;
2010-09-17 19:53:26 +02:00
serialize . NumberU32_Unbounded ( " ticket " , m_ExpectedPathTicket ) ;
2010-02-10 20:28:46 +01:00
2010-09-17 19:53:26 +02:00
serialize . NumberU32_Unbounded ( " target entity " , m_TargetEntity ) ;
2010-11-30 13:31:54 +01:00
serialize . NumberFixed_Unbounded ( " target pos x " , m_TargetPos . X ) ;
serialize . NumberFixed_Unbounded ( " target pos y " , m_TargetPos . Y ) ;
serialize . NumberFixed_Unbounded ( " target offset x " , m_TargetOffset . X ) ;
serialize . NumberFixed_Unbounded ( " target offset y " , m_TargetOffset . Y ) ;
serialize . NumberFixed_Unbounded ( " target min range " , m_TargetMinRange ) ;
serialize . NumberFixed_Unbounded ( " target max range " , m_TargetMaxRange ) ;
2010-09-17 19:53:26 +02:00
serialize . NumberFixed_Unbounded ( " speed " , m_Speed ) ;
2012-12-04 17:59:08 +01:00
serialize . Bool ( " moving " , m_Moving ) ;
2010-09-17 19:53:26 +02:00
SerializeVector < SerializeWaypoint > ( ) ( serialize , " long path " , m_LongPath . m_Waypoints ) ;
SerializeVector < SerializeWaypoint > ( ) ( serialize , " short path " , m_ShortPath . m_Waypoints ) ;
2010-11-30 13:31:54 +01:00
2010-09-17 19:53:26 +02:00
SerializeGoal ( ) ( serialize , " goal " , m_FinalGoal ) ;
2010-01-09 20:20:14 +01:00
}
2010-11-30 13:31:54 +01:00
virtual void Serialize ( ISerializer & serialize )
{
SerializeCommon ( serialize ) ;
}
2011-01-16 15:08:38 +01:00
virtual void Deserialize ( const CParamNode & paramNode , IDeserializer & deserialize )
2010-01-09 20:20:14 +01:00
{
2011-01-16 15:08:38 +01:00
Init ( paramNode ) ;
2010-01-09 20:20:14 +01:00
2010-11-30 13:31:54 +01:00
SerializeCommon ( deserialize ) ;
2010-01-09 20:20:14 +01:00
}
2011-01-16 15:08:38 +01:00
virtual void HandleMessage ( const CMessage & msg , bool UNUSED ( global ) )
2010-01-09 20:20:14 +01:00
{
switch ( msg . GetType ( ) )
{
2010-09-03 11:55:14 +02:00
case MT_Update_MotionFormation :
2010-01-09 20:20:14 +01:00
{
2010-09-03 11:55:14 +02:00
if ( m_FormationController )
2010-02-10 20:28:46 +01:00
{
2010-09-03 11:55:14 +02:00
fixed dt = static_cast < const CMessageUpdate_MotionFormation & > ( msg ) . turnLength ;
Move ( dt ) ;
}
break ;
}
case MT_Update_MotionUnit :
{
if ( ! m_FormationController )
{
fixed dt = static_cast < const CMessageUpdate_MotionUnit & > ( msg ) . turnLength ;
Move ( dt ) ;
2010-02-10 20:28:46 +01:00
}
2010-01-09 20:20:14 +01:00
break ;
}
2010-04-30 01:36:05 +02:00
case MT_RenderSubmit :
{
const CMessageRenderSubmit & msgData = static_cast < const CMessageRenderSubmit & > ( msg ) ;
2010-05-01 11:48:39 +02:00
RenderSubmit ( msgData . collector ) ;
2010-04-30 01:36:05 +02:00
break ;
}
2010-09-03 11:55:14 +02:00
case MT_PathResult :
{
const CMessagePathResult & msgData = static_cast < const CMessagePathResult & > ( msg ) ;
PathResult ( msgData . ticket , msgData . path ) ;
break ;
}
2010-01-09 20:20:14 +01:00
}
}
2012-08-31 10:20:36 +02:00
virtual bool IsMoving ( )
{
2012-09-04 05:57:22 +02:00
return m_Moving ;
2012-08-31 10:20:36 +02:00
}
2010-07-18 17:19:49 +02:00
virtual fixed GetWalkSpeed ( )
2010-02-10 20:28:46 +01:00
{
2010-07-18 17:19:49 +02:00
return m_WalkSpeed ;
2010-04-30 01:36:05 +02:00
}
2010-02-10 20:28:46 +01:00
2010-06-05 02:49:14 +02:00
virtual fixed GetRunSpeed ( )
{
return m_RunSpeed ;
}
2011-08-06 10:11:05 +02:00
virtual ICmpPathfinder : : pass_class_t GetPassabilityClass ( )
{
return m_PassClass ;
}
2012-12-06 20:46:13 +01:00
virtual fixed GetCurrentSpeed ( )
{
return m_CurSpeed ;
}
2010-07-20 10:45:09 +02:00
virtual void SetSpeed ( fixed speed )
2010-07-18 17:19:49 +02:00
{
2010-07-20 10:45:09 +02:00
m_Speed = speed ;
2010-07-18 17:19:49 +02:00
}
2010-04-30 01:36:05 +02:00
virtual void SetDebugOverlay ( bool enabled )
{
m_DebugOverlayEnabled = enabled ;
}
2010-07-18 17:19:49 +02:00
virtual bool MoveToPointRange ( entity_pos_t x , entity_pos_t z , entity_pos_t minRange , entity_pos_t maxRange ) ;
2012-12-02 18:25:23 +01:00
virtual bool IsInPointRange ( entity_pos_t x , entity_pos_t z , entity_pos_t minRange , entity_pos_t maxRange ) ;
2010-11-30 13:31:54 +01:00
virtual bool MoveToTargetRange ( entity_id_t target , entity_pos_t minRange , entity_pos_t maxRange ) ;
virtual bool IsInTargetRange ( entity_id_t target , entity_pos_t minRange , entity_pos_t maxRange ) ;
2010-09-03 11:55:14 +02:00
virtual void MoveToFormationOffset ( entity_id_t target , entity_pos_t x , entity_pos_t z ) ;
2010-07-18 17:19:49 +02:00
2011-06-09 21:44:40 +02:00
virtual void FaceTowardsPoint ( entity_pos_t x , entity_pos_t z ) ;
2010-07-18 17:19:49 +02:00
virtual void StopMoving ( )
{
2012-09-04 05:57:22 +02:00
m_Moving = false ;
2010-10-07 00:17:34 +02:00
m_ExpectedPathTicket = 0 ;
2010-09-03 11:55:14 +02:00
m_State = STATE_STOPPING ;
2010-11-30 13:31:54 +01:00
m_PathState = PATHSTATE_NONE ;
m_LongPath . m_Waypoints . clear ( ) ;
m_ShortPath . m_Waypoints . clear ( ) ;
2010-09-03 11:55:14 +02:00
}
virtual void SetUnitRadius ( fixed radius )
{
m_Radius = radius ;
2010-07-18 17:19:49 +02:00
}
2010-04-30 01:36:05 +02:00
private :
2010-09-03 11:55:14 +02:00
bool ShouldAvoidMovingUnits ( )
{
return ! m_FormationController ;
}
2010-11-30 13:31:54 +01:00
bool IsFormationMember ( )
{
return m_State = = STATE_FORMATIONMEMBER_PATH ;
}
void StartFailed ( )
2010-09-03 11:55:14 +02:00
{
2010-11-30 13:31:54 +01:00
StopMoving ( ) ;
m_State = STATE_IDLE ; // don't go through the STOPPING state since we never even started
2010-09-03 11:55:14 +02:00
CmpPtr < ICmpObstruction > cmpObstruction ( GetSimContext ( ) , GetEntityId ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( cmpObstruction )
2010-09-03 11:55:14 +02:00
cmpObstruction - > SetMovingFlag ( false ) ;
CMessageMotionChanged msg ( true , true ) ;
GetSimContext ( ) . GetComponentManager ( ) . PostMessage ( GetEntityId ( ) , msg ) ;
}
2010-11-30 13:31:54 +01:00
void MoveFailed ( )
{
StopMoving ( ) ;
CmpPtr < ICmpObstruction > cmpObstruction ( GetSimContext ( ) , GetEntityId ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( cmpObstruction )
2010-11-30 13:31:54 +01:00
cmpObstruction - > SetMovingFlag ( false ) ;
CMessageMotionChanged msg ( false , true ) ;
GetSimContext ( ) . GetComponentManager ( ) . PostMessage ( GetEntityId ( ) , msg ) ;
}
void StartSucceeded ( )
2010-09-03 11:55:14 +02:00
{
CMessageMotionChanged msg ( true , false ) ;
GetSimContext ( ) . GetComponentManager ( ) . PostMessage ( GetEntityId ( ) , msg ) ;
}
2010-11-30 13:31:54 +01:00
void MoveSucceeded ( )
2010-09-03 11:55:14 +02:00
{
2012-09-04 05:57:22 +02:00
m_Moving = false ;
2010-09-03 11:55:14 +02:00
CmpPtr < ICmpObstruction > cmpObstruction ( GetSimContext ( ) , GetEntityId ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( cmpObstruction )
2010-09-03 11:55:14 +02:00
cmpObstruction - > SetMovingFlag ( false ) ;
2012-12-06 20:46:13 +01:00
// No longer moving, so speed is 0.
m_CurSpeed = fixed : : Zero ( ) ;
2010-09-03 11:55:14 +02:00
CMessageMotionChanged msg ( false , false ) ;
GetSimContext ( ) . GetComponentManager ( ) . PostMessage ( GetEntityId ( ) , msg ) ;
}
2010-04-30 01:36:05 +02:00
/**
2010-11-30 13:31:54 +01:00
* Handle the result of an asynchronous path query .
*/
void PathResult ( u32 ticket , const ICmpPathfinder : : Path & path ) ;
/**
* Do the per - turn movement and other updates .
2010-04-30 01:36:05 +02:00
*/
2010-05-02 22:32:37 +02:00
void Move ( fixed dt ) ;
2010-04-30 01:36:05 +02:00
2010-11-30 13:31:54 +01:00
/**
* Decide whether to approximate the given range from a square target as a circle ,
* rather than as a square .
*/
bool ShouldTreatTargetAsCircle ( entity_pos_t range , entity_pos_t hw , entity_pos_t hh , entity_pos_t circleRadius ) ;
/**
* Computes the current location of our target entity ( plus offset ) .
* Returns false if no target entity or no valid position .
*/
bool ComputeTargetPosition ( CFixedVector2D & out ) ;
/**
* Attempts to replace the current path with a straight line to the target
* entity , if it ' s close enough and the route is not obstructed .
*/
bool TryGoingStraightToTargetEntity ( 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 .
*/
bool CheckTargetMovement ( CFixedVector2D from , entity_pos_t minDelta ) ;
/**
* Returns whether the length of the given path , plus the distance from
* ' from ' to the first waypoints , it shorter than minDistance .
*/
bool PathIsShort ( const ICmpPathfinder : : Path & path , CFixedVector2D from , entity_pos_t minDistance ) ;
2010-04-30 01:36:05 +02:00
/**
* Rotate to face towards the target point , given the current pos
*/
2011-06-09 21:44:40 +02:00
void FaceTowardsPointFromPos ( CFixedVector2D pos , entity_pos_t x , entity_pos_t z ) ;
2010-04-30 01:36:05 +02:00
/**
2010-11-30 13:31:54 +01:00
* Returns an appropriate obstruction filter for use with path requests .
2010-04-30 01:36:05 +02:00
*/
2011-02-10 17:06:28 +01:00
ControlGroupMovementObstructionFilter GetObstructionFilter ( bool forceAvoidMovingUnits = false ) ;
2010-09-03 11:55:14 +02:00
2010-11-30 13:31:54 +01:00
/**
* Start moving to the given goal , from our current position ' from ' .
* Might go in a straight line immediately , or might start an asynchronous
* path request .
*/
void BeginPathing ( CFixedVector2D from , const ICmpPathfinder : : Goal & goal ) ;
2010-04-30 01:36:05 +02:00
/**
2010-11-30 13:31:54 +01:00
* Start an asynchronous long path query .
2010-04-30 01:36:05 +02:00
*/
2010-11-30 13:31:54 +01:00
void RequestLongPath ( CFixedVector2D from , const ICmpPathfinder : : Goal & goal ) ;
2010-04-30 01:36:05 +02:00
/**
2010-11-30 13:31:54 +01:00
* Start an asynchronous short path query .
2010-04-30 01:36:05 +02:00
*/
2010-11-30 13:31:54 +01:00
void RequestShortPath ( CFixedVector2D from , const ICmpPathfinder : : Goal & goal , bool avoidMovingUnits ) ;
2010-04-30 01:36:05 +02:00
/**
2010-11-30 13:31:54 +01:00
* Select a next long waypoint , given the current unit position .
* Also recomputes the short path to use that waypoint .
2010-04-30 01:36:05 +02:00
* Returns false on error , or if there is no waypoint to pick .
*/
2010-11-30 13:31:54 +01:00
bool PickNextLongWaypoint ( const CFixedVector2D & pos , bool avoidMovingUnits ) ;
2010-04-30 01:36:05 +02:00
/**
* Convert a path into a renderable list of lines
*/
2010-05-01 11:48:39 +02:00
void RenderPath ( const ICmpPathfinder : : Path & path , std : : vector < SOverlayLine > & lines , CColor color ) ;
2010-04-30 01:36:05 +02:00
2010-05-01 11:48:39 +02:00
void RenderSubmit ( SceneCollector & collector ) ;
2010-04-30 01:36:05 +02:00
} ;
REGISTER_COMPONENT_TYPE ( UnitMotion )
2010-02-10 20:28:46 +01:00
2010-09-03 11:55:14 +02:00
void CCmpUnitMotion : : PathResult ( u32 ticket , const ICmpPathfinder : : Path & path )
2010-04-30 01:36:05 +02:00
{
2010-09-03 11:55:14 +02:00
// Ignore obsolete path requests
if ( ticket ! = m_ExpectedPathTicket )
return ;
2010-04-30 01:36:05 +02:00
2010-09-03 11:55:14 +02:00
m_ExpectedPathTicket = 0 ; // we don't expect to get this result again
2010-11-30 13:31:54 +01:00
if ( m_PathState = = PATHSTATE_WAITING_REQUESTING_LONG )
2010-04-30 01:36:05 +02:00
{
2010-11-30 13:31:54 +01:00
m_LongPath = path ;
m_ShortPath . m_Waypoints . clear ( ) ;
2010-09-03 11:55:14 +02:00
2011-05-06 23:52:15 +02:00
// 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
// (via the short pathfinder), so if we're stuck and the user clicks
// close enough to the unit then we can probably get unstuck
2010-11-30 13:31:54 +01:00
if ( m_LongPath . m_Waypoints . empty ( ) )
2010-02-10 20:28:46 +01:00
{
2011-05-06 23:52:15 +02:00
ICmpPathfinder : : Waypoint wp = { m_FinalGoal . x , m_FinalGoal . z } ;
m_LongPath . m_Waypoints . push_back ( wp ) ;
2010-02-10 20:28:46 +01:00
}
2010-09-03 11:55:14 +02:00
CmpPtr < ICmpPosition > cmpPosition ( GetSimContext ( ) , GetEntityId ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
2010-11-30 13:31:54 +01:00
{
StartFailed ( ) ;
2010-09-03 11:55:14 +02:00
return ;
2010-11-30 13:31:54 +01:00
}
2010-09-03 11:55:14 +02:00
CFixedVector2D pos = cmpPosition - > GetPosition2D ( ) ;
2010-11-30 13:31:54 +01:00
if ( ! PickNextLongWaypoint ( pos , ShouldAvoidMovingUnits ( ) ) )
{
StartFailed ( ) ;
return ;
}
// We started a short path request to the next long path waypoint
m_PathState = PATHSTATE_WAITING_REQUESTING_SHORT ;
}
else if ( m_PathState = = PATHSTATE_WAITING_REQUESTING_SHORT )
{
m_ShortPath = path ;
// If there's no waypoints then we couldn't get near the target
if ( m_ShortPath . m_Waypoints . empty ( ) )
{
2012-09-03 04:59:15 +02:00
if ( ! IsFormationMember ( ) )
{
StartFailed ( ) ;
return ;
}
else
{
2012-09-04 05:57:22 +02:00
m_Moving = false ;
2012-09-03 04:59:15 +02:00
CMessageMotionChanged msg ( true , true ) ;
GetSimContext ( ) . GetComponentManager ( ) . PostMessage ( GetEntityId ( ) , msg ) ;
}
2010-11-30 13:31:54 +01:00
}
CmpPtr < ICmpPosition > cmpPosition ( GetSimContext ( ) , GetEntityId ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
2010-11-30 13:31:54 +01:00
{
StartFailed ( ) ;
return ;
}
2010-09-03 11:55:14 +02:00
2010-11-30 13:31:54 +01:00
// Now we've got a short path that we can follow
m_PathState = PATHSTATE_FOLLOWING ;
2010-09-03 11:55:14 +02:00
2010-11-30 13:31:54 +01:00
StartSucceeded ( ) ;
2010-04-30 01:36:05 +02:00
}
2010-11-30 13:31:54 +01:00
else if ( m_PathState = = PATHSTATE_FOLLOWING_REQUESTING_LONG )
2010-09-03 11:55:14 +02:00
{
m_LongPath = path ;
2010-11-30 13:31:54 +01:00
// Leave the old m_ShortPath - we'll carry on following it until the
// new short path has been computed
2010-04-30 01:36:05 +02:00
2011-06-28 09:27:03 +02:00
// 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
// (via the short pathfinder), so if we're stuck and the user clicks
// close enough to the unit then we can probably get unstuck
2010-09-03 11:55:14 +02:00
if ( m_LongPath . m_Waypoints . empty ( ) )
{
2011-06-28 09:27:03 +02:00
ICmpPathfinder : : Waypoint wp = { m_FinalGoal . x , m_FinalGoal . z } ;
m_LongPath . m_Waypoints . push_back ( wp ) ;
2010-09-03 11:55:14 +02:00
}
2010-11-30 13:31:54 +01:00
CmpPtr < ICmpPosition > cmpPosition ( GetSimContext ( ) , GetEntityId ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
2010-09-03 11:55:14 +02:00
{
2010-11-30 13:31:54 +01:00
StopMoving ( ) ;
return ;
}
2010-09-03 11:55:14 +02:00
2010-11-30 13:31:54 +01:00
CFixedVector2D pos = cmpPosition - > GetPosition2D ( ) ;
2010-09-03 11:55:14 +02:00
2010-11-30 13:31:54 +01:00
if ( ! PickNextLongWaypoint ( pos , ShouldAvoidMovingUnits ( ) ) )
{
StopMoving ( ) ;
return ;
}
// We started a short path request to the next long path waypoint
m_PathState = PATHSTATE_FOLLOWING_REQUESTING_SHORT ;
// (TODO: is this entirely safe? We might continue moving along our
// old path while this request is active, so it'll be slightly incorrect
// by the time the request has completed)
}
else if ( m_PathState = = PATHSTATE_FOLLOWING_REQUESTING_SHORT )
{
// Replace the current path with the new one
m_ShortPath = path ;
// If there's no waypoints then we couldn't get near the target
if ( m_ShortPath . m_Waypoints . empty ( ) )
{
// We should stop moving (unless we're in a formation, in which
// case we should continue following it)
if ( ! IsFormationMember ( ) )
2010-09-03 11:55:14 +02:00
{
2010-11-30 13:31:54 +01:00
MoveFailed ( ) ;
return ;
2010-09-03 11:55:14 +02:00
}
2012-09-03 04:59:15 +02:00
else
{
2012-09-04 05:57:22 +02:00
m_Moving = false ;
2012-09-03 04:59:15 +02:00
CMessageMotionChanged msg ( false , true ) ;
GetSimContext ( ) . GetComponentManager ( ) . PostMessage ( GetEntityId ( ) , msg ) ;
}
2010-09-03 11:55:14 +02:00
}
2010-11-30 13:31:54 +01:00
m_PathState = PATHSTATE_FOLLOWING ;
}
else if ( m_PathState = = PATHSTATE_FOLLOWING_REQUESTING_SHORT_APPEND )
{
// Append the new path onto our current one
m_ShortPath . m_Waypoints . insert ( m_ShortPath . m_Waypoints . begin ( ) , path . m_Waypoints . begin ( ) , path . m_Waypoints . end ( ) ) ;
// If there's no waypoints then we couldn't get near the target
// from the last intermediate long-path waypoint. But we can still
// continue using the remainder of our current short path. So just
// discard the now-useless long path.
if ( path . m_Waypoints . empty ( ) )
m_LongPath . m_Waypoints . clear ( ) ;
m_PathState = PATHSTATE_FOLLOWING ;
2010-09-03 11:55:14 +02:00
}
else
{
2012-01-01 17:43:10 +01:00
LOGWARNING ( L " unexpected PathResult (%u %d %d) " , GetEntityId ( ) , m_State , m_PathState ) ;
2010-09-03 11:55:14 +02:00
}
2010-04-30 01:36:05 +02:00
}
2010-05-02 22:32:37 +02:00
void CCmpUnitMotion : : Move ( fixed dt )
2010-04-30 01:36:05 +02:00
{
PROFILE ( " Move " ) ;
2010-11-30 13:31:54 +01:00
if ( m_State = = STATE_STOPPING )
2010-09-03 11:55:14 +02:00
{
m_State = STATE_IDLE ;
2010-11-30 13:31:54 +01:00
MoveSucceeded ( ) ;
return ;
}
if ( m_State = = STATE_IDLE )
{
return ;
}
switch ( m_PathState )
{
case PATHSTATE_NONE :
{
// If we're not pathing, do nothing
2010-05-28 01:31:03 +02:00
return ;
2010-11-30 13:31:54 +01:00
}
2010-05-28 01:31:03 +02:00
2010-11-30 13:31:54 +01:00
case PATHSTATE_WAITING_REQUESTING_LONG :
case PATHSTATE_WAITING_REQUESTING_SHORT :
{
// If we're waiting for a path and don't have one yet, do nothing
2010-04-30 01:36:05 +02:00
return ;
2010-11-30 13:31:54 +01:00
}
2010-04-30 01:36:05 +02:00
2010-11-30 13:31:54 +01:00
case PATHSTATE_FOLLOWING :
case PATHSTATE_FOLLOWING_REQUESTING_SHORT :
case PATHSTATE_FOLLOWING_REQUESTING_SHORT_APPEND :
case PATHSTATE_FOLLOWING_REQUESTING_LONG :
2010-09-03 11:55:14 +02:00
{
2010-11-30 13:31:54 +01:00
// TODO: there's some asymmetry here when units look at other
// units' positions - the result will depend on the order of execution.
// Maybe we should split the updates into multiple phases to minimise
// that problem.
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSimContext ( ) , SYSTEM_ENTITY ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpPathfinder )
2010-09-03 11:55:14 +02:00
return ;
2010-04-30 01:36:05 +02:00
2010-09-03 11:55:14 +02:00
CmpPtr < ICmpPosition > cmpPosition ( GetSimContext ( ) , GetEntityId ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
2010-09-03 11:55:14 +02:00
return ;
2010-04-30 01:36:05 +02:00
2010-11-30 13:31:54 +01:00
CFixedVector2D initialPos = cmpPosition - > GetPosition2D ( ) ;
2010-04-30 01:36:05 +02:00
2010-11-30 13:31:54 +01:00
// 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
// to it, then throw away our current path and go straight to it
if ( m_PathState = = PATHSTATE_FOLLOWING )
TryGoingStraightToTargetEntity ( initialPos ) ;
2010-02-10 20:28:46 +01:00
2010-11-30 13:31:54 +01:00
// Keep track of the current unit's position during the update
CFixedVector2D pos = initialPos ;
2010-05-28 01:31:03 +02:00
2010-11-30 13:31:54 +01:00
// If in formation, run to keep up; otherwise just walk
// (TODO: support stamina, charging, etc)
fixed basicSpeed ;
if ( IsFormationMember ( ) )
basicSpeed = GetRunSpeed ( ) ;
else
basicSpeed = m_Speed ; // (typically but not always WalkSpeed)
2010-04-30 01:36:05 +02:00
2010-11-30 15:48:04 +01:00
// 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
// partially onto a much slower/faster tile)
fixed terrainSpeed = cmpPathfinder - > GetMovementSpeed ( pos . X , pos . Y , m_CostClass ) ;
fixed maxSpeed = basicSpeed . Multiply ( terrainSpeed ) ;
2010-09-03 11:55:14 +02:00
2010-11-30 13:31:54 +01:00
bool wasObstructed = false ;
2010-11-30 15:48:04 +01:00
// We want to move (at most) maxSpeed*dt units from pos towards the next waypoint
2010-11-30 13:31:54 +01:00
fixed timeLeft = dt ;
2010-09-03 11:55:14 +02:00
2010-11-30 13:31:54 +01:00
while ( timeLeft > fixed : : Zero ( ) )
{
// If we ran out of short path, we have to stop
if ( m_ShortPath . m_Waypoints . empty ( ) )
break ;
CFixedVector2D target ( m_ShortPath . m_Waypoints . back ( ) . x , m_ShortPath . m_Waypoints . back ( ) . z ) ;
2010-09-03 11:55:14 +02:00
CFixedVector2D offset = target - pos ;
2010-04-30 01:36:05 +02:00
2010-09-03 11:55:14 +02:00
// Face towards the target
if ( ! offset . IsZero ( ) )
{
entity_angle_t angle = atan2_approx ( offset . X , offset . Y ) ;
cmpPosition - > TurnTo ( angle ) ;
}
2010-11-30 13:31:54 +01:00
// Work out how far we can travel in timeLeft
fixed maxdist = maxSpeed . Multiply ( timeLeft ) ;
2010-09-03 11:55:14 +02:00
// If the target is close, we can move there directly
fixed offsetLength = offset . Length ( ) ;
if ( offsetLength < = maxdist )
{
2010-11-30 13:31:54 +01:00
if ( cmpPathfinder - > CheckMovement ( GetObstructionFilter ( ) , pos . X , pos . Y , target . X , target . Y , m_Radius , m_PassClass ) )
2010-09-03 11:55:14 +02:00
{
pos = target ;
2010-11-30 13:31:54 +01:00
// Spend the rest of the time heading towards the next waypoint
timeLeft = timeLeft - ( offsetLength / maxSpeed ) ;
2010-09-03 11:55:14 +02:00
2010-11-30 13:31:54 +01:00
m_ShortPath . m_Waypoints . pop_back ( ) ;
continue ;
}
else
{
// Error - path was obstructed
wasObstructed = true ;
break ;
2010-09-03 11:55:14 +02:00
}
}
else
{
// Not close enough, so just move in the right direction
offset . Normalize ( maxdist ) ;
target = pos + offset ;
2010-11-30 13:31:54 +01:00
if ( cmpPathfinder - > CheckMovement ( GetObstructionFilter ( ) , pos . X , pos . Y , target . X , target . Y , m_Radius , m_PassClass ) )
2010-09-03 11:55:14 +02:00
{
pos = target ;
2010-11-30 13:31:54 +01:00
break ;
}
else
{
// Error - path was obstructed
wasObstructed = true ;
break ;
2010-09-03 11:55:14 +02:00
}
}
2010-11-30 13:31:54 +01:00
}
2010-09-03 11:55:14 +02:00
2010-11-30 13:31:54 +01:00
// Update the Position component after our movement (if we actually moved anywhere)
if ( pos ! = initialPos )
cmpPosition - > MoveTo ( pos . X , pos . Y ) ;
2010-04-30 01:36:05 +02:00
2012-12-06 20:46:13 +01:00
// Calculate the mean speed over this past turn.
m_CurSpeed = cmpPosition - > GetDistanceTravelled ( ) / dt ;
2010-11-30 13:31:54 +01:00
if ( wasObstructed )
{
// Oops, we hit something (very likely another unit).
// Stop, and recompute the whole path.
// TODO: if the target has UnitMotion and is higher priority,
// we should wait a little bit.
2010-09-03 11:55:14 +02:00
2010-11-30 13:31:54 +01:00
RequestLongPath ( pos , m_FinalGoal ) ;
m_PathState = PATHSTATE_WAITING_REQUESTING_LONG ;
2010-04-30 01:36:05 +02:00
2010-02-10 20:28:46 +01:00
return ;
}
2010-09-03 11:55:14 +02:00
2010-11-30 13:31:54 +01:00
// We successfully moved along our path, until running out of
// waypoints or time.
2010-09-03 11:55:14 +02:00
2010-11-30 13:31:54 +01:00
if ( m_PathState = = PATHSTATE_FOLLOWING )
2010-02-10 20:28:46 +01:00
{
2010-11-30 13:31:54 +01:00
// If we're not currently computing any new paths:
2010-09-03 11:55:14 +02:00
2010-11-30 13:31:54 +01:00
// If we are close to reaching the end of the short path
// (or have reached it already), try to extend it
2010-09-03 11:55:14 +02:00
2010-11-30 15:48:04 +01:00
entity_pos_t minDistance = basicSpeed . Multiply ( dt ) * WAYPOINT_ADVANCE_LOOKAHEAD_TURNS ;
2010-11-30 13:31:54 +01:00
if ( PathIsShort ( m_ShortPath , pos , minDistance ) )
2010-09-03 11:55:14 +02:00
{
2010-11-30 13:31:54 +01:00
// Start the path extension from the end of this short path
// (or our current position if no short path)
CFixedVector2D from = pos ;
if ( ! m_ShortPath . m_Waypoints . empty ( ) )
from = CFixedVector2D ( m_ShortPath . m_Waypoints [ 0 ] . x , m_ShortPath . m_Waypoints [ 0 ] . z ) ;
2010-04-30 01:36:05 +02:00
2010-11-30 13:31:54 +01:00
if ( PickNextLongWaypoint ( from , ShouldAvoidMovingUnits ( ) ) )
{
m_PathState = PATHSTATE_FOLLOWING_REQUESTING_SHORT_APPEND ;
2010-09-03 11:55:14 +02:00
}
2010-11-30 13:31:54 +01:00
else
2010-09-03 11:55:14 +02:00
{
2010-11-30 13:31:54 +01:00
// Failed (there were no long waypoints left).
// If there's still some short path then continue following
// it, else we've finished moving.
if ( m_ShortPath . m_Waypoints . empty ( ) )
{
if ( IsFormationMember ( ) )
{
2012-08-31 10:20:36 +02:00
// We've reached our assigned position. If the controller
// is idle, send a notification in case it should disband,
// otherwise continue following the formation next turn.
CmpPtr < ICmpUnitMotion > cmpUnitMotion ( GetSimContext ( ) , m_TargetEntity ) ;
if ( cmpUnitMotion & & ! cmpUnitMotion - > IsMoving ( ) )
{
2012-09-04 05:57:22 +02:00
m_Moving = false ;
2012-08-31 10:20:36 +02:00
CMessageMotionChanged msg ( false , false ) ;
GetSimContext ( ) . GetComponentManager ( ) . PostMessage ( GetEntityId ( ) , msg ) ;
}
2010-11-30 13:31:54 +01:00
}
else
{
// Not in formation, so just finish moving
2010-09-03 11:55:14 +02:00
2010-11-30 13:31:54 +01:00
StopMoving ( ) ;
2010-09-03 11:55:14 +02:00
2011-06-09 21:44:40 +02:00
FaceTowardsPointFromPos ( pos , m_FinalGoal . x , m_FinalGoal . z ) ;
2010-11-30 13:31:54 +01:00
// TODO: if the goal was a square building, we ought to point towards the
// nearest point on the square, not towards its center
}
}
2010-09-03 11:55:14 +02:00
}
}
2010-11-30 13:31:54 +01:00
}
2010-09-03 11:55:14 +02:00
2010-11-30 13:31:54 +01:00
// 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
// whole path
if ( m_PathState = = PATHSTATE_FOLLOWING )
{
if ( IsFormationMember ( ) )
CheckTargetMovement ( pos , CHECK_TARGET_MOVEMENT_MIN_DELTA_FORMATION ) ;
else
CheckTargetMovement ( pos , CHECK_TARGET_MOVEMENT_MIN_DELTA ) ;
2010-02-10 20:28:46 +01:00
}
}
2010-09-03 11:55:14 +02:00
}
2010-04-30 01:36:05 +02:00
}
2010-11-30 13:31:54 +01:00
bool CCmpUnitMotion : : ComputeTargetPosition ( CFixedVector2D & out )
2010-04-30 01:36:05 +02:00
{
2010-11-30 13:31:54 +01:00
if ( m_TargetEntity = = INVALID_ENTITY )
return false ;
CmpPtr < ICmpPosition > cmpPosition ( GetSimContext ( ) , m_TargetEntity ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
2010-11-30 13:31:54 +01:00
return false ;
2010-04-30 01:36:05 +02:00
2010-11-30 13:31:54 +01:00
if ( m_TargetOffset . IsZero ( ) )
{
// No offset, just return the position directly
out = cmpPosition - > GetPosition2D ( ) ;
}
else
{
// There is an offset, so compute it relative to orientation
entity_angle_t angle = cmpPosition - > GetRotation ( ) . Y ;
CFixedVector2D offset = m_TargetOffset . Rotate ( angle ) ;
out = cmpPosition - > GetPosition2D ( ) + offset ;
}
return true ;
2010-04-30 01:36:05 +02:00
}
2010-02-10 20:28:46 +01:00
2010-11-30 13:31:54 +01:00
bool CCmpUnitMotion : : TryGoingStraightToTargetEntity ( CFixedVector2D from )
2010-04-30 01:36:05 +02:00
{
2010-11-30 13:31:54 +01:00
CFixedVector2D targetPos ;
if ( ! ComputeTargetPosition ( targetPos ) )
return false ;
// Fail if the target is too far away
if ( ( targetPos - from ) . CompareLength ( DIRECT_PATH_RANGE ) > 0 )
return false ;
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSimContext ( ) , SYSTEM_ENTITY ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpPathfinder )
2010-11-30 13:31:54 +01:00
return false ;
// Move the goal to match the target entity's new position
ICmpPathfinder : : Goal goal = m_FinalGoal ;
goal . x = targetPos . X ;
goal . z = targetPos . Y ;
// (we ignore changes to the target's rotation, since only buildings are
// square and buildings don't move)
// Find the point on the goal shape that we should head towards
CFixedVector2D goalPos = cmpPathfinder - > GetNearestPointOnGoal ( from , goal ) ;
// Check if there's any collisions on that route
if ( ! cmpPathfinder - > CheckMovement ( GetObstructionFilter ( ) , from . X , from . Y , goalPos . X , goalPos . Y , m_Radius , m_PassClass ) )
return false ;
// That route is okay, so update our path
m_FinalGoal = goal ;
m_LongPath . m_Waypoints . clear ( ) ;
m_ShortPath . m_Waypoints . clear ( ) ;
ICmpPathfinder : : Waypoint wp = { goalPos . X , goalPos . Y } ;
m_ShortPath . m_Waypoints . push_back ( wp ) ;
return true ;
}
bool CCmpUnitMotion : : CheckTargetMovement ( CFixedVector2D from , entity_pos_t minDelta )
{
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 )
return false ;
// Fail unless we're close enough to the target to care about its movement
if ( ! PathIsShort ( m_LongPath , from , CHECK_TARGET_MOVEMENT_AT_MAX_DIST ) )
return false ;
2011-06-24 14:35:15 +02:00
// 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 < ICmpOwnership > cmpOwnership ( GetSimContext ( ) , GetEntityId ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( cmpOwnership )
2011-06-24 14:35:15 +02:00
{
CmpPtr < ICmpRangeManager > cmpRangeManager ( GetSimContext ( ) , SYSTEM_ENTITY ) ;
2012-02-08 03:46:15 +01:00
if ( cmpRangeManager )
2011-06-24 14:35:15 +02:00
{
if ( cmpRangeManager - > GetLosVisibility ( m_TargetEntity , cmpOwnership - > GetOwner ( ) ) = = ICmpRangeManager : : VIS_HIDDEN )
return false ;
}
}
2010-11-30 13:31:54 +01:00
// 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 ;
return true ;
}
bool CCmpUnitMotion : : PathIsShort ( const ICmpPathfinder : : Path & path , CFixedVector2D from , entity_pos_t minDistance )
{
CFixedVector2D pos = from ;
entity_pos_t distLeft = minDistance ;
2011-08-16 13:18:32 +02:00
for ( ssize_t i = ( ssize_t ) path . m_Waypoints . size ( ) - 1 ; i > = 0 ; - - i )
2010-11-30 13:31:54 +01:00
{
// Check if the next path segment is longer than the requested minimum
CFixedVector2D waypoint ( path . m_Waypoints [ i ] . x , path . m_Waypoints [ i ] . z ) ;
CFixedVector2D delta = waypoint - pos ;
if ( delta . CompareLength ( distLeft ) > 0 )
return false ;
// Still short enough - prepare to check the next segment
distLeft - = delta . Length ( ) ;
pos = waypoint ;
}
// Reached the end of the path before exceeding minDistance
return true ;
}
2011-06-09 21:44:40 +02:00
void CCmpUnitMotion : : FaceTowardsPoint ( entity_pos_t x , entity_pos_t z )
{
CmpPtr < ICmpPosition > cmpPosition ( GetSimContext ( ) , GetEntityId ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
2011-06-09 21:44:40 +02:00
return ;
CFixedVector2D pos = cmpPosition - > GetPosition2D ( ) ;
FaceTowardsPointFromPos ( pos , x , z ) ;
}
void CCmpUnitMotion : : FaceTowardsPointFromPos ( CFixedVector2D pos , entity_pos_t x , entity_pos_t z )
2010-11-30 13:31:54 +01:00
{
CFixedVector2D target ( x , z ) ;
CFixedVector2D offset = target - pos ;
if ( ! offset . IsZero ( ) )
2010-01-09 20:20:14 +01:00
{
2010-04-30 01:36:05 +02:00
entity_angle_t angle = atan2_approx ( offset . X , offset . Y ) ;
2010-01-30 14:11:58 +01:00
2010-05-01 11:48:39 +02:00
CmpPtr < ICmpPosition > cmpPosition ( GetSimContext ( ) , GetEntityId ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpPosition )
2010-01-30 14:11:58 +01:00
return ;
2010-04-30 01:36:05 +02:00
cmpPosition - > TurnTo ( angle ) ;
}
}
2010-01-30 14:11:58 +01:00
2011-02-10 17:06:28 +01:00
ControlGroupMovementObstructionFilter CCmpUnitMotion : : GetObstructionFilter ( bool forceAvoidMovingUnits )
2010-04-30 01:36:05 +02:00
{
2010-11-30 13:31:54 +01:00
entity_id_t group ;
if ( IsFormationMember ( ) )
group = m_TargetEntity ;
else
group = GetEntityId ( ) ;
2011-02-10 17:06:28 +01:00
return ControlGroupMovementObstructionFilter ( forceAvoidMovingUnits | | ShouldAvoidMovingUnits ( ) , group ) ;
2010-11-30 13:31:54 +01:00
}
void CCmpUnitMotion : : BeginPathing ( CFixedVector2D from , const ICmpPathfinder : : Goal & goal )
{
// Cancel any pending path requests
m_ExpectedPathTicket = 0 ;
2012-09-04 05:57:22 +02:00
// Update the unit's movement status.
m_Moving = true ;
2010-11-30 13:31:54 +01:00
// Set our 'moving' flag, so other units pathfinding now will ignore us
CmpPtr < ICmpObstruction > cmpObstruction ( GetSimContext ( ) , GetEntityId ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( cmpObstruction )
2010-11-30 13:31:54 +01:00
cmpObstruction - > SetMovingFlag ( true ) ;
// If we're aiming at a target entity and it's close and we can reach
// it in a straight line, then we'll just go along the straight line
// instead of computing a path.
if ( TryGoingStraightToTargetEntity ( from ) )
{
m_PathState = PATHSTATE_FOLLOWING ;
return ;
}
// TODO: should go straight to non-entity points too
// Otherwise we need to compute a path.
// TODO: if it's close then just do a short path, not a long path
// (But if it's close on the opposite side of a river then we really
// need a long path, so we can't simply check linear distance)
m_PathState = PATHSTATE_WAITING_REQUESTING_LONG ;
RequestLongPath ( from , goal ) ;
}
void CCmpUnitMotion : : RequestLongPath ( CFixedVector2D from , const ICmpPathfinder : : Goal & goal )
{
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSimContext ( ) , SYSTEM_ENTITY ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpPathfinder )
2010-11-30 13:31:54 +01:00
return ;
cmpPathfinder - > SetDebugPath ( from . X , from . Y , goal , m_PassClass , m_CostClass ) ;
m_ExpectedPathTicket = cmpPathfinder - > ComputePathAsync ( from . X , from . Y , goal , m_PassClass , m_CostClass , GetEntityId ( ) ) ;
}
void CCmpUnitMotion : : RequestShortPath ( CFixedVector2D from , const ICmpPathfinder : : Goal & goal , bool avoidMovingUnits )
{
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSimContext ( ) , SYSTEM_ENTITY ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpPathfinder )
2010-11-30 13:31:54 +01:00
return ;
m_ExpectedPathTicket = cmpPathfinder - > ComputeShortPathAsync ( from . X , from . Y , m_Radius , SHORT_PATH_SEARCH_RANGE , goal , m_PassClass , avoidMovingUnits , m_TargetEntity , GetEntityId ( ) ) ;
}
bool CCmpUnitMotion : : PickNextLongWaypoint ( const CFixedVector2D & pos , bool avoidMovingUnits )
{
// If there's no long path, we can't pick the next waypoint from it
if ( m_LongPath . m_Waypoints . empty ( ) )
return false ;
// First try to get the immediate next waypoint
entity_pos_t targetX = m_LongPath . m_Waypoints . back ( ) . x ;
entity_pos_t targetZ = m_LongPath . m_Waypoints . back ( ) . z ;
m_LongPath . m_Waypoints . pop_back ( ) ;
// To smooth the motion and avoid grid-constrained movement and allow dynamic obstacle avoidance,
// try skipping some more waypoints if they're close enough
while ( ! m_LongPath . m_Waypoints . empty ( ) )
{
CFixedVector2D w ( m_LongPath . m_Waypoints . back ( ) . x , m_LongPath . m_Waypoints . back ( ) . z ) ;
if ( ( w - pos ) . CompareLength ( WAYPOINT_ADVANCE_MAX ) > 0 )
break ;
targetX = m_LongPath . m_Waypoints . back ( ) . x ;
targetZ = m_LongPath . m_Waypoints . back ( ) . z ;
m_LongPath . m_Waypoints . pop_back ( ) ;
}
// Now we need to recompute a short path to the waypoint
ICmpPathfinder : : Goal goal ;
if ( m_LongPath . m_Waypoints . empty ( ) )
{
// This was the last waypoint - head for the exact goal
goal = m_FinalGoal ;
}
else
{
// Head for somewhere near the waypoint (but allow some leeway in case it's obstructed)
goal . type = ICmpPathfinder : : Goal : : CIRCLE ;
goal . hw = SHORT_PATH_GOAL_RADIUS ;
goal . x = targetX ;
goal . z = targetZ ;
}
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSimContext ( ) , SYSTEM_ENTITY ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpPathfinder )
2010-11-30 13:31:54 +01:00
return false ;
m_ExpectedPathTicket = cmpPathfinder - > ComputeShortPathAsync ( pos . X , pos . Y , m_Radius , SHORT_PATH_SEARCH_RANGE , goal , m_PassClass , avoidMovingUnits , GetEntityId ( ) , GetEntityId ( ) ) ;
return true ;
}
bool CCmpUnitMotion : : MoveToPointRange ( entity_pos_t x , entity_pos_t z , entity_pos_t minRange , entity_pos_t maxRange )
{
PROFILE ( " MoveToPointRange " ) ;
2010-01-09 20:20:14 +01:00
2010-05-01 11:48:39 +02:00
CmpPtr < ICmpPosition > cmpPosition ( GetSimContext ( ) , GetEntityId ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
2010-04-30 01:36:05 +02:00
return false ;
2010-07-29 22:39:23 +02:00
CFixedVector2D pos = cmpPosition - > GetPosition2D ( ) ;
2010-11-30 13:31:54 +01:00
2010-04-30 01:36:05 +02:00
ICmpPathfinder : : Goal goal ;
2010-11-30 13:31:54 +01:00
if ( minRange . IsZero ( ) & & maxRange . IsZero ( ) )
{
// Handle the non-ranged mode:
2010-04-30 01:36:05 +02:00
2010-11-30 13:31:54 +01:00
// Check whether this point is in an obstruction
2010-07-31 23:22:39 +02:00
2010-11-30 13:31:54 +01:00
CmpPtr < ICmpObstructionManager > cmpObstructionManager ( GetSimContext ( ) , SYSTEM_ENTITY ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpObstructionManager )
2010-11-30 13:31:54 +01:00
return false ;
2010-04-30 01:36:05 +02:00
2010-11-30 13:31:54 +01:00
ICmpObstructionManager : : ObstructionSquare obstruction ;
if ( cmpObstructionManager - > FindMostImportantObstruction ( GetObstructionFilter ( true ) , x , z , m_Radius , obstruction ) )
{
// If we're aiming inside a building, then aim for the outline of the building instead
// TODO: if we're aiming at a unit then maybe a circle would look nicer?
goal . type = ICmpPathfinder : : Goal : : SQUARE ;
goal . x = obstruction . x ;
goal . z = obstruction . z ;
goal . u = obstruction . u ;
goal . v = obstruction . v ;
goal . hw = obstruction . hw + m_Radius + g_GoalDelta ; // nudge the goal outwards so it doesn't intersect the building itself
goal . hh = obstruction . hh + m_Radius + g_GoalDelta ;
}
else
{
// Unobstructed - head directly for the goal
goal . type = ICmpPathfinder : : Goal : : POINT ;
goal . x = x ;
goal . z = z ;
}
2010-04-30 01:36:05 +02:00
}
else
{
2010-11-30 13:31:54 +01:00
entity_pos_t distance = ( pos - CFixedVector2D ( x , z ) ) . Length ( ) ;
entity_pos_t goalDistance ;
if ( distance < minRange )
{
goalDistance = minRange + g_GoalDelta ;
}
2011-03-04 15:36:41 +01:00
else if ( maxRange > = entity_pos_t : : Zero ( ) & & distance > maxRange )
2010-11-30 13:31:54 +01:00
{
goalDistance = maxRange - g_GoalDelta ;
}
else
{
// We're already in range - no need to move anywhere
2011-06-09 21:44:40 +02:00
FaceTowardsPointFromPos ( pos , x , z ) ;
2010-11-30 13:31:54 +01:00
return false ;
}
// TODO: what happens if goalDistance < 0? (i.e. we probably can never get close enough to the target)
goal . type = ICmpPathfinder : : Goal : : CIRCLE ;
2010-04-30 01:36:05 +02:00
goal . x = x ;
goal . z = z ;
2012-08-22 08:13:23 +02:00
// Formerly added m_Radius, but it seems better to go by the mid-point.
goal . hw = goalDistance ;
2010-04-30 01:36:05 +02:00
}
2010-11-30 13:31:54 +01:00
m_State = STATE_INDIVIDUAL_PATH ;
m_TargetEntity = INVALID_ENTITY ;
m_TargetOffset = CFixedVector2D ( ) ;
m_TargetMinRange = minRange ;
m_TargetMaxRange = maxRange ;
2010-04-30 01:36:05 +02:00
m_FinalGoal = goal ;
2010-11-30 13:31:54 +01:00
BeginPathing ( pos , goal ) ;
2010-04-30 01:36:05 +02:00
return true ;
}
2012-12-02 18:25:23 +01:00
bool CCmpUnitMotion : : IsInPointRange ( entity_pos_t x , entity_pos_t z , entity_pos_t minRange , entity_pos_t maxRange )
{
CmpPtr < ICmpPosition > cmpPosition ( GetSimContext ( ) , GetEntityId ( ) ) ;
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
return false ;
CFixedVector2D pos = cmpPosition - > GetPosition2D ( ) ;
bool hasObstruction = false ;
CmpPtr < ICmpObstructionManager > cmpObstructionManager ( GetSimContext ( ) , SYSTEM_ENTITY ) ;
ICmpObstructionManager : : ObstructionSquare obstruction ;
if ( cmpObstructionManager )
hasObstruction = cmpObstructionManager - > FindMostImportantObstruction ( GetObstructionFilter ( true ) , x , z , m_Radius , obstruction ) ;
if ( minRange . IsZero ( ) & & maxRange . IsZero ( ) & & hasObstruction )
{
// Handle the non-ranged mode:
CFixedVector2D halfSize ( obstruction . hw , obstruction . hh ) ;
entity_pos_t distance = Geometry : : DistanceToSquare ( pos - CFixedVector2D ( obstruction . x , obstruction . z ) , obstruction . u , obstruction . v , halfSize ) ;
// See if we're too close to the target square
if ( distance < minRange )
return false ;
// See if we're close enough to the target square
if ( maxRange < entity_pos_t : : Zero ( ) | | distance < = maxRange )
return true ;
return false ;
}
else
{
entity_pos_t distance = ( pos - CFixedVector2D ( x , z ) ) . Length ( ) ;
if ( distance < minRange )
{
return false ;
}
else if ( maxRange > = entity_pos_t : : Zero ( ) & & distance > maxRange )
{
return false ;
}
else
{
return true ;
}
}
}
2010-04-30 01:36:05 +02:00
bool CCmpUnitMotion : : ShouldTreatTargetAsCircle ( entity_pos_t range , entity_pos_t hw , entity_pos_t hh , entity_pos_t circleRadius )
2010-01-09 20:20:14 +01:00
{
2010-04-30 01:36:05 +02:00
// 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.
// Choose the shape that will minimise the worst-case error:
2010-01-09 20:20:14 +01:00
2010-04-30 01:36:05 +02:00
// For a square, error is (sqrt(2)-1) * range at the corners
entity_pos_t errSquare = ( entity_pos_t : : FromInt ( 4142 ) / 10000 ) . Multiply ( range ) ;
2010-01-09 20:20:14 +01:00
2010-04-30 01:36:05 +02:00
// For a circle, error is radius-hw at the sides and radius-hh at the top/bottom
entity_pos_t errCircle = circleRadius - std : : min ( hw , hh ) ;
2010-01-09 20:20:14 +01:00
2010-04-30 01:36:05 +02:00
return ( errCircle < errSquare ) ;
}
2010-01-09 20:20:14 +01:00
2010-11-30 13:31:54 +01:00
bool CCmpUnitMotion : : MoveToTargetRange ( entity_id_t target , entity_pos_t minRange , entity_pos_t maxRange )
2010-04-30 01:36:05 +02:00
{
2010-11-30 13:31:54 +01:00
PROFILE ( " MoveToTargetRange " ) ;
2010-04-30 01:36:05 +02:00
2010-05-01 11:48:39 +02:00
CmpPtr < ICmpPosition > cmpPosition ( GetSimContext ( ) , GetEntityId ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
2010-04-30 01:36:05 +02:00
return false ;
2010-07-29 22:39:23 +02:00
CFixedVector2D pos = cmpPosition - > GetPosition2D ( ) ;
2010-04-30 01:36:05 +02:00
2010-05-01 11:48:39 +02:00
CmpPtr < ICmpObstructionManager > cmpObstructionManager ( GetSimContext ( ) , SYSTEM_ENTITY ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpObstructionManager )
2010-04-30 01:36:05 +02:00
return false ;
2011-02-10 17:06:28 +01:00
bool hasObstruction = false ;
ICmpObstructionManager : : ObstructionSquare obstruction ;
2010-05-01 11:48:39 +02:00
CmpPtr < ICmpObstruction > cmpObstruction ( GetSimContext ( ) , target ) ;
2012-02-08 03:46:15 +01:00
if ( cmpObstruction )
2011-02-10 17:06:28 +01:00
hasObstruction = cmpObstruction - > GetObstructionSquare ( obstruction ) ;
2010-04-30 01:36:05 +02:00
/*
* 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 . )
*/
2011-02-10 17:06:28 +01:00
if ( hasObstruction )
2010-01-30 14:11:58 +01:00
{
2010-04-30 01:36:05 +02:00
CFixedVector2D halfSize ( obstruction . hw , obstruction . hh ) ;
2010-07-18 17:19:49 +02:00
ICmpPathfinder : : Goal goal ;
2010-04-30 01:36:05 +02:00
goal . x = obstruction . x ;
goal . z = obstruction . z ;
2010-02-10 20:28:46 +01:00
2010-04-30 01:36:05 +02:00
entity_pos_t distance = Geometry : : DistanceToSquare ( pos - CFixedVector2D ( obstruction . x , obstruction . z ) , obstruction . u , obstruction . v , halfSize ) ;
2010-01-09 20:20:14 +01:00
2010-04-30 01:36:05 +02:00
if ( distance < minRange )
{
// Too close to the square - need to move away
2010-11-30 13:31:54 +01:00
// TODO: maybe we should do the ShouldTreatTargetAsCircle thing here?
2010-07-18 17:19:49 +02:00
entity_pos_t goalDistance = minRange + g_GoalDelta ;
2010-04-30 01:36:05 +02:00
goal . type = ICmpPathfinder : : Goal : : SQUARE ;
goal . u = obstruction . u ;
goal . v = obstruction . v ;
2012-01-12 13:51:10 +01:00
entity_pos_t delta = std : : max ( goalDistance , m_Radius + entity_pos_t : : FromInt ( TERRAIN_TILE_SIZE ) / 16 ) ; // ensure it's far enough to not intersect the building itself
2010-04-30 01:36:05 +02:00
goal . hw = obstruction . hw + delta ;
goal . hh = obstruction . hh + delta ;
}
2011-03-04 15:36:41 +01:00
else if ( maxRange < entity_pos_t : : Zero ( ) | | distance < maxRange )
2010-04-30 01:36:05 +02:00
{
// We're already in range - no need to move anywhere
2011-06-09 21:44:40 +02:00
FaceTowardsPointFromPos ( pos , goal . x , goal . z ) ;
2010-04-30 01:36:05 +02:00
return false ;
}
else
2010-01-30 14:11:58 +01:00
{
2010-04-30 01:36:05 +02:00
// We might need to move closer:
// Circumscribe the square
entity_pos_t circleRadius = halfSize . Length ( ) ;
if ( ShouldTreatTargetAsCircle ( maxRange , obstruction . hw , obstruction . hh , circleRadius ) )
2010-01-30 14:11:58 +01:00
{
2010-04-30 01:36:05 +02:00
// The target is small relative to our range, so pretend it's a circle
2010-02-12 23:46:53 +01:00
2010-04-30 01:36:05 +02:00
// 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 ;
if ( circleDistance < maxRange )
2010-02-12 23:46:53 +01:00
{
2010-04-30 01:36:05 +02:00
// We're already in range - no need to move anywhere
2011-06-09 21:44:40 +02:00
FaceTowardsPointFromPos ( pos , goal . x , goal . z ) ;
2010-04-30 01:36:05 +02:00
return false ;
2010-02-12 23:46:53 +01:00
}
2010-07-18 17:19:49 +02:00
entity_pos_t goalDistance = maxRange - g_GoalDelta ;
2010-04-30 01:36:05 +02:00
goal . type = ICmpPathfinder : : Goal : : CIRCLE ;
goal . hw = circleRadius + goalDistance ;
2010-01-30 14:11:58 +01:00
}
2010-04-30 01:36:05 +02:00
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
2010-01-30 14:11:58 +01:00
2010-07-18 17:19:49 +02:00
entity_pos_t goalDistance = ( maxRange - g_GoalDelta ) * 2 / 3 ; // multiply by slightly less than 1/sqrt(2)
2010-04-30 01:36:05 +02:00
goal . type = ICmpPathfinder : : Goal : : SQUARE ;
goal . u = obstruction . u ;
goal . v = obstruction . v ;
2012-01-12 13:51:10 +01:00
entity_pos_t delta = std : : max ( goalDistance , m_Radius + entity_pos_t : : FromInt ( TERRAIN_TILE_SIZE ) / 16 ) ; // ensure it's far enough to not intersect the building itself
2010-04-30 01:36:05 +02:00
goal . hw = obstruction . hw + delta ;
goal . hh = obstruction . hh + delta ;
}
}
2010-07-18 17:19:49 +02:00
2010-11-30 13:31:54 +01:00
m_State = STATE_INDIVIDUAL_PATH ;
m_TargetEntity = target ;
m_TargetOffset = CFixedVector2D ( ) ;
m_TargetMinRange = minRange ;
m_TargetMaxRange = maxRange ;
2010-07-18 17:19:49 +02:00
m_FinalGoal = goal ;
2010-11-30 13:31:54 +01:00
BeginPathing ( pos , goal ) ;
2010-07-18 17:19:49 +02:00
return true ;
2010-04-30 01:36:05 +02:00
}
else
{
// The target didn't have an obstruction or obstruction shape, so treat it as a point instead
2010-05-01 11:48:39 +02:00
CmpPtr < ICmpPosition > cmpTargetPosition ( GetSimContext ( ) , target ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpTargetPosition | | ! cmpTargetPosition - > IsInWorld ( ) )
2010-04-30 01:36:05 +02:00
return false ;
2010-07-29 22:39:23 +02:00
CFixedVector2D targetPos = cmpTargetPosition - > GetPosition2D ( ) ;
2010-04-30 01:36:05 +02:00
2010-07-29 22:39:23 +02:00
return MoveToPointRange ( targetPos . X , targetPos . Y , minRange , maxRange ) ;
2010-07-18 17:19:49 +02:00
}
}
2010-04-30 01:36:05 +02:00
2010-11-30 13:31:54 +01:00
bool CCmpUnitMotion : : IsInTargetRange ( entity_id_t target , entity_pos_t minRange , entity_pos_t maxRange )
2010-07-18 17:19:49 +02:00
{
2010-11-30 13:31:54 +01:00
// This function closely mirrors MoveToTargetRange - it needs to return true
2010-04-30 01:36:05 +02:00
// after that Move has completed
2010-01-30 14:11:58 +01:00
2010-05-01 11:48:39 +02:00
CmpPtr < ICmpPosition > cmpPosition ( GetSimContext ( ) , GetEntityId ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
2010-04-30 01:36:05 +02:00
return false ;
2010-07-29 22:39:23 +02:00
CFixedVector2D pos = cmpPosition - > GetPosition2D ( ) ;
2010-04-30 01:36:05 +02:00
2010-05-01 11:48:39 +02:00
CmpPtr < ICmpObstructionManager > cmpObstructionManager ( GetSimContext ( ) , SYSTEM_ENTITY ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpObstructionManager )
2010-04-30 01:36:05 +02:00
return false ;
2011-02-10 17:06:28 +01:00
bool hasObstruction = false ;
ICmpObstructionManager : : ObstructionSquare obstruction ;
2010-05-01 11:48:39 +02:00
CmpPtr < ICmpObstruction > cmpObstruction ( GetSimContext ( ) , target ) ;
2012-02-08 03:46:15 +01:00
if ( cmpObstruction )
2011-02-10 17:06:28 +01:00
hasObstruction = cmpObstruction - > GetObstructionSquare ( obstruction ) ;
2010-04-30 01:36:05 +02:00
2011-02-10 17:06:28 +01:00
if ( hasObstruction )
2010-04-30 01:36:05 +02:00
{
CFixedVector2D halfSize ( obstruction . hw , obstruction . hh ) ;
entity_pos_t distance = Geometry : : DistanceToSquare ( pos - CFixedVector2D ( obstruction . x , obstruction . z ) , obstruction . u , obstruction . v , halfSize ) ;
// See if we're too close to the target square
if ( distance < minRange )
return false ;
// See if we're close enough to the target square
2011-03-04 15:36:41 +01:00
if ( maxRange < entity_pos_t : : Zero ( ) | | distance < = maxRange )
2010-04-30 01:36:05 +02:00
return true ;
entity_pos_t circleRadius = halfSize . Length ( ) ;
if ( ShouldTreatTargetAsCircle ( maxRange , obstruction . hw , obstruction . hh , circleRadius ) )
{
// The target is small relative to our range, so pretend it's a circle
// and see if we're close enough to that
entity_pos_t circleDistance = ( pos - CFixedVector2D ( obstruction . x , obstruction . z ) ) . Length ( ) - circleRadius ;
if ( circleDistance < = maxRange )
return true ;
}
2010-01-30 14:11:58 +01:00
2010-04-30 01:36:05 +02:00
return false ;
}
else
{
2010-05-01 11:48:39 +02:00
CmpPtr < ICmpPosition > cmpTargetPosition ( GetSimContext ( ) , target ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpTargetPosition | | ! cmpTargetPosition - > IsInWorld ( ) )
2010-04-30 01:36:05 +02:00
return false ;
2010-07-29 22:39:23 +02:00
CFixedVector2D targetPos = cmpTargetPosition - > GetPosition2D ( ) ;
2010-04-30 01:36:05 +02:00
2010-07-29 22:39:23 +02:00
entity_pos_t distance = ( pos - targetPos ) . Length ( ) ;
2010-04-30 01:36:05 +02:00
2011-03-04 15:36:41 +01:00
if ( minRange < = distance & & ( maxRange < entity_pos_t : : Zero ( ) | | distance < = maxRange ) )
2010-04-30 01:36:05 +02:00
return true ;
return false ;
}
}
2010-09-03 11:55:14 +02:00
void CCmpUnitMotion : : MoveToFormationOffset ( entity_id_t target , entity_pos_t x , entity_pos_t z )
2010-04-30 01:36:05 +02:00
{
2012-08-31 10:20:36 +02:00
CmpPtr < ICmpPosition > cmpPosition ( GetSimContext ( ) , target ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
2010-01-30 14:11:58 +01:00
return ;
2010-01-29 22:13:18 +01:00
2010-11-30 13:31:54 +01:00
CFixedVector2D pos = cmpPosition - > GetPosition2D ( ) ;
2010-04-30 01:36:05 +02:00
ICmpPathfinder : : Goal goal ;
2010-11-30 13:31:54 +01:00
goal . type = ICmpPathfinder : : Goal : : POINT ;
goal . x = pos . X ;
goal . z = pos . Y ;
2010-04-30 01:36:05 +02:00
2010-11-30 13:31:54 +01:00
m_State = STATE_FORMATIONMEMBER_PATH ;
m_TargetEntity = target ;
m_TargetOffset = CFixedVector2D ( x , z ) ;
m_TargetMinRange = entity_pos_t : : Zero ( ) ;
m_TargetMaxRange = entity_pos_t : : Zero ( ) ;
m_FinalGoal = goal ;
2010-04-30 01:36:05 +02:00
2010-11-30 13:31:54 +01:00
BeginPathing ( pos , goal ) ;
2010-04-30 01:36:05 +02:00
}
2010-11-30 13:31:54 +01:00
2010-04-30 01:36:05 +02:00
2010-05-01 11:48:39 +02:00
void CCmpUnitMotion : : RenderPath ( const ICmpPathfinder : : Path & path , std : : vector < SOverlayLine > & lines , CColor color )
2010-04-30 01:36:05 +02:00
{
2010-05-28 01:23:53 +02:00
bool floating = false ;
CmpPtr < ICmpPosition > cmpPosition ( GetSimContext ( ) , GetEntityId ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( cmpPosition )
2010-05-28 01:23:53 +02:00
floating = cmpPosition - > IsFloating ( ) ;
2010-04-30 01:36:05 +02:00
lines . clear ( ) ;
std : : vector < float > waypointCoords ;
for ( size_t i = 0 ; i < path . m_Waypoints . size ( ) ; + + i )
{
float x = path . m_Waypoints [ i ] . x . ToFloat ( ) ;
float z = path . m_Waypoints [ i ] . z . ToFloat ( ) ;
waypointCoords . push_back ( x ) ;
waypointCoords . push_back ( z ) ;
lines . push_back ( SOverlayLine ( ) ) ;
lines . back ( ) . m_Color = color ;
2010-05-28 01:23:53 +02:00
SimRender : : ConstructSquareOnGround ( GetSimContext ( ) , x , z , 1.0f , 1.0f , 0.0f , lines . back ( ) , floating ) ;
2010-04-30 01:36:05 +02:00
}
lines . push_back ( SOverlayLine ( ) ) ;
lines . back ( ) . m_Color = color ;
2010-05-28 01:23:53 +02:00
SimRender : : ConstructLineOnGround ( GetSimContext ( ) , waypointCoords , lines . back ( ) , floating ) ;
2010-04-30 01:36:05 +02:00
}
2010-05-01 11:48:39 +02:00
void CCmpUnitMotion : : RenderSubmit ( SceneCollector & collector )
2010-04-30 01:36:05 +02:00
{
if ( ! m_DebugOverlayEnabled )
return ;
2010-11-30 13:31:54 +01:00
RenderPath ( m_LongPath , m_DebugOverlayLongPathLines , OVERLAY_COLOUR_LONG_PATH ) ;
RenderPath ( m_ShortPath , m_DebugOverlayShortPathLines , OVERLAY_COLOUR_SHORT_PATH ) ;
for ( size_t i = 0 ; i < m_DebugOverlayLongPathLines . size ( ) ; + + i )
collector . Submit ( & m_DebugOverlayLongPathLines [ i ] ) ;
2010-04-30 01:36:05 +02:00
for ( size_t i = 0 ; i < m_DebugOverlayShortPathLines . size ( ) ; + + i )
collector . Submit ( & m_DebugOverlayShortPathLines [ i ] ) ;
2010-01-09 20:20:14 +01:00
}