Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
CCmpUnitMotion.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2012 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "precompiled.h"
19 
21 #include "ICmpUnitMotion.h"
22 
33 
34 #include "graphics/Overlay.h"
35 #include "graphics/Terrain.h"
36 #include "maths/FixedVector2D.h"
37 #include "ps/CLogger.h"
38 #include "ps/Profile.h"
39 #include "renderer/Scene.h"
40 
41 /**
42  * When advancing along the long path, and picking a new waypoint to move
43  * towards, we'll pick one that's up to this far from the unit's current
44  * position (to minimise the effects of grid-constrained movement)
45  */
47 
48 /**
49  * When advancing along the long path, we'll pick a new waypoint to move
50  * towards if we expect to reach the end of our current short path within
51  * this many turns (assuming constant speed and turn length).
52  * (This could typically be 1, but we need some tolerance in case speeds
53  * or turn lengths change.)
54  */
55 static const int WAYPOINT_ADVANCE_LOOKAHEAD_TURNS = 4;
56 
57 /**
58  * Maximum range to restrict short path queries to. (Larger ranges are slower,
59  * smaller ranges might miss some legitimate routes around large obstacles.)
60  */
62 
63 /**
64  * When short-pathing to an intermediate waypoint, we aim for a circle of this radius
65  * around the waypoint rather than expecting to reach precisely the waypoint itself
66  * (since it might be inside an obstacle).
67  */
69 
70 /**
71  * If we are this close to our target entity/point, then think about heading
72  * for it in a straight line instead of pathfinding.
73  */
75 
76 /**
77  * If we're following a target entity,
78  * we will recompute our path if the target has moved
79  * more than this distance from where we last pathed to.
80  */
82 
83 /**
84  * If we're following as part of a formation,
85  * but can't move to our assigned target point in a straight line,
86  * we will recompute our path if the target has moved
87  * more than this distance from where we last pathed to.
88  */
90 
91 /**
92  * If we're following something but it's more than this distance away along
93  * our path, then don't bother trying to repath regardless of how much it has
94  * moved, until we get this close to the end of our old path.
95  */
97 
98 static const CColor OVERLAY_COLOUR_LONG_PATH(1, 1, 1, 1);
99 static const CColor OVERLAY_COLOUR_SHORT_PATH(1, 0, 0, 1);
100 
101 static const entity_pos_t g_GoalDelta = entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/4; // for extending the goal outwards/inwards a little bit
102 
104 {
105 public:
106  static void ClassInit(CComponentManager& componentManager)
107  {
110  componentManager.SubscribeToMessageType(MT_RenderSubmit); // for debug overlays
111  componentManager.SubscribeToMessageType(MT_PathResult);
112  }
113 
115 
119 
120  // Template state:
121 
123  fixed m_WalkSpeed; // in metres per second
127 
128  // Dynamic state:
129 
131  bool m_Moving;
133 
134  enum State
135  {
136  /*
137  * Not moving at all.
138  */
140 
141  /*
142  * Not moving at all. Will go to IDLE next turn.
143  * (This one-turn delay is a hack to fix animation timings.)
144  */
146 
147  /*
148  * Member of a formation.
149  * Pathing to the target (depending on m_PathState).
150  * Target is m_TargetEntity plus m_TargetOffset.
151  */
153 
154  /*
155  * Individual unit or formation controller.
156  * Pathing to the target (depending on m_PathState).
157  * Target is m_TargetPos, m_TargetMinRange, m_TargetMaxRange;
158  * if m_TargetEntity is not INVALID_ENTITY then m_TargetPos is tracking it.
159  */
161 
163  };
165 
167  {
168  /*
169  * There is no path.
170  * (This should only happen in IDLE and STOPPING.)
171  */
173 
174  /*
175  * We have an outstanding long path request.
176  * No paths are usable yet, so we can't move anywhere.
177  */
179 
180  /*
181  * We have an outstanding short path request.
182  * m_LongPath is valid.
183  * m_ShortPath is not yet valid, so we can't move anywhere.
184  */
186 
187  /*
188  * We are following our path, and have no path requests.
189  * m_LongPath and m_ShortPath are valid.
190  */
192 
193  /*
194  * We are following our path, and have an outstanding long path request.
195  * (This is because our target moved a long way and we need to recompute
196  * the whole path).
197  * m_LongPath and m_ShortPath are valid.
198  */
200 
201  /*
202  * We are following our path, and have an outstanding short path request.
203  * (This is because our target moved and we've got a new long path
204  * which we need to follow).
205  * m_LongPath is valid; m_ShortPath is valid but obsolete.
206  */
208 
209  /*
210  * We are following our path, and have an outstanding short path request
211  * to append to our current path.
212  * (This is because we got near the end of our short path and need
213  * to extend it to continue along the long path).
214  * m_LongPath and m_ShortPath are valid.
215  */
217 
219  };
221 
222  u32 m_ExpectedPathTicket; // asynchronous request ID we're waiting for, or 0 if none
223 
229 
231 
232  // Current mean speed (over the last turn).
234 
235  // Currently active paths (storing waypoints in reverse order).
236  // The last item in each path is the point we're currently heading towards.
239 
241 
242  static std::string GetSchema()
243  {
244  return
245  "<a:help>Provides the unit with the ability to move around the world by itself.</a:help>"
246  "<a:example>"
247  "<WalkSpeed>7.0</WalkSpeed>"
248  "<PassabilityClass>default</PassabilityClass>"
249  "<CostClass>infantry</CostClass>"
250  "</a:example>"
251  "<element name='FormationController'>"
252  "<data type='boolean'/>"
253  "</element>"
254  "<element name='WalkSpeed' a:help='Basic movement speed (in metres per second)'>"
255  "<ref name='positiveDecimal'/>"
256  "</element>"
257  "<optional>"
258  "<element name='Run'>"
259  "<interleave>"
260  "<element name='Speed'><ref name='positiveDecimal'/></element>"
261  "<element name='Range'><ref name='positiveDecimal'/></element>"
262  "<element name='RangeMin'><ref name='nonNegativeDecimal'/></element>"
263  "<element name='RegenTime'><ref name='positiveDecimal'/></element>"
264  "<element name='DecayTime'><ref name='positiveDecimal'/></element>"
265  "</interleave>"
266  "</element>"
267  "</optional>"
268  "<element name='PassabilityClass' a:help='Identifies the terrain passability class (values are defined in special/pathfinder.xml)'>"
269  "<text/>"
270  "</element>"
271  "<element name='CostClass' a:help='Identifies the movement speed/cost class (values are defined in special/pathfinder.xml)'>"
272  "<text/>"
273  "</element>";
274  }
275 
276  /*
277  * TODO: the running/charging thing needs to be designed and implemented
278  */
279 
280  virtual void Init(const CParamNode& paramNode)
281  {
282  m_FormationController = paramNode.GetChild("FormationController").ToBool();
283 
284  m_Moving = false;
285  m_FacePointAfterMove = true;
286 
287  m_WalkSpeed = paramNode.GetChild("WalkSpeed").ToFixed();
290 
291  if (paramNode.GetChild("Run").IsOk())
292  {
293  m_RunSpeed = paramNode.GetChild("Run").GetChild("Speed").ToFixed();
294  }
295  else
296  {
298  }
299 
300  CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
301  if (cmpPathfinder)
302  {
303  m_PassClass = cmpPathfinder->GetPassabilityClass(paramNode.GetChild("PassabilityClass").ToUTF8());
304  m_CostClass = cmpPathfinder->GetCostClass(paramNode.GetChild("CostClass").ToUTF8());
305  }
306 
307  CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
308  if (cmpObstruction)
309  m_Radius = cmpObstruction->GetUnitRadius();
310 
313 
315 
317 
319 
320  m_DebugOverlayEnabled = false;
321  }
322 
323  virtual void Deinit()
324  {
325  }
326 
327  template<typename S>
328  void SerializeCommon(S& serialize)
329  {
330  serialize.NumberFixed_Unbounded("radius", m_Radius);
331 
332  serialize.NumberU8("state", m_State, 0, STATE_MAX-1);
333  serialize.NumberU8("path state", m_PathState, 0, PATHSTATE_MAX-1);
334 
335  serialize.NumberU32_Unbounded("ticket", m_ExpectedPathTicket);
336 
337  serialize.NumberU32_Unbounded("target entity", m_TargetEntity);
338  serialize.NumberFixed_Unbounded("target pos x", m_TargetPos.X);
339  serialize.NumberFixed_Unbounded("target pos y", m_TargetPos.Y);
340  serialize.NumberFixed_Unbounded("target offset x", m_TargetOffset.X);
341  serialize.NumberFixed_Unbounded("target offset y", m_TargetOffset.Y);
342  serialize.NumberFixed_Unbounded("target min range", m_TargetMinRange);
343  serialize.NumberFixed_Unbounded("target max range", m_TargetMaxRange);
344 
345  serialize.NumberFixed_Unbounded("speed", m_Speed);
346 
347  serialize.Bool("moving", m_Moving);
348  serialize.Bool("facePointAfterMove", m_FacePointAfterMove);
349 
350  SerializeVector<SerializeWaypoint>()(serialize, "long path", m_LongPath.m_Waypoints);
351  SerializeVector<SerializeWaypoint>()(serialize, "short path", m_ShortPath.m_Waypoints);
352 
353  SerializeGoal()(serialize, "goal", m_FinalGoal);
354  }
355 
356  virtual void Serialize(ISerializer& serialize)
357  {
358  SerializeCommon(serialize);
359  }
360 
361  virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
362  {
363  Init(paramNode);
364 
365  SerializeCommon(deserialize);
366  }
367 
368  virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
369  {
370  switch (msg.GetType())
371  {
373  {
375  {
376  fixed dt = static_cast<const CMessageUpdate_MotionFormation&> (msg).turnLength;
377  Move(dt);
378  }
379  break;
380  }
382  {
384  {
385  fixed dt = static_cast<const CMessageUpdate_MotionUnit&> (msg).turnLength;
386  Move(dt);
387  }
388  break;
389  }
390  case MT_RenderSubmit:
391  {
392  const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
393  RenderSubmit(msgData.collector);
394  break;
395  }
396  case MT_PathResult:
397  {
398  const CMessagePathResult& msgData = static_cast<const CMessagePathResult&> (msg);
399  PathResult(msgData.ticket, msgData.path);
400  break;
401  }
402  }
403  }
404 
405  virtual bool IsMoving()
406  {
407  return m_Moving;
408  }
409 
410  virtual fixed GetWalkSpeed()
411  {
412  return m_WalkSpeed;
413  }
414 
415  virtual fixed GetRunSpeed()
416  {
417  return m_RunSpeed;
418  }
419 
421  {
422  return m_PassClass;
423  }
424 
426  {
427  return m_CurSpeed;
428  }
429 
430  virtual void SetSpeed(fixed speed)
431  {
432  m_Speed = speed;
433  }
434 
435  virtual void SetFacePointAfterMove(bool facePointAfterMove)
436  {
437  m_FacePointAfterMove = facePointAfterMove;
438  }
439 
440  virtual void SetDebugOverlay(bool enabled)
441  {
442  m_DebugOverlayEnabled = enabled;
443  }
444 
445  virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange);
446  virtual bool IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange);
447  virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
448  virtual bool IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
449  virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z);
450 
451  virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z);
452 
453  virtual void StopMoving()
454  {
455  m_Moving = false;
459  m_LongPath.m_Waypoints.clear();
460  m_ShortPath.m_Waypoints.clear();
461  }
462 
463  virtual void SetUnitRadius(fixed radius)
464  {
465  m_Radius = radius;
466  }
467 
468 private:
470  {
471  return !m_FormationController;
472  }
473 
475  {
477  }
478 
479  void StartFailed()
480  {
481  StopMoving();
482  m_State = STATE_IDLE; // don't go through the STOPPING state since we never even started
483 
484  CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
485  if (cmpObstruction)
486  cmpObstruction->SetMovingFlag(false);
487 
488  CMessageMotionChanged msg(true, true);
490  }
491 
492  void MoveFailed()
493  {
494  StopMoving();
495 
496  CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
497  if (cmpObstruction)
498  cmpObstruction->SetMovingFlag(false);
499 
500  CMessageMotionChanged msg(false, true);
502  }
503 
505  {
506  CMessageMotionChanged msg(true, false);
508  }
509 
511  {
512  m_Moving = false;
513 
514  CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
515  if (cmpObstruction)
516  cmpObstruction->SetMovingFlag(false);
517 
518  // No longer moving, so speed is 0.
520 
521  CMessageMotionChanged msg(false, false);
523  }
524 
525  /**
526  * Handle the result of an asynchronous path query.
527  */
528  void PathResult(u32 ticket, const ICmpPathfinder::Path& path);
529 
530  /**
531  * Do the per-turn movement and other updates.
532  */
533  void Move(fixed dt);
534 
535  /**
536  * Decide whether to approximate the given range from a square target as a circle,
537  * rather than as a square.
538  */
540 
541  /**
542  * Computes the current location of our target entity (plus offset).
543  * Returns false if no target entity or no valid position.
544  */
546 
547  /**
548  * Attempts to replace the current path with a straight line to the target
549  * entity, if it's close enough and the route is not obstructed.
550  */
552 
553  /**
554  * Returns whether the target entity has moved more than minDelta since our
555  * last path computations, and we're close enough to it to care.
556  */
557  bool CheckTargetMovement(CFixedVector2D from, entity_pos_t minDelta);
558 
559  /**
560  * Returns whether the length of the given path, plus the distance from
561  * 'from' to the first waypoints, it shorter than minDistance.
562  */
563  bool PathIsShort(const ICmpPathfinder::Path& path, CFixedVector2D from, entity_pos_t minDistance);
564 
565  /**
566  * Rotate to face towards the target point, given the current pos
567  */
569 
570  /**
571  * Returns an appropriate obstruction filter for use with path requests.
572  */
573  ControlGroupMovementObstructionFilter GetObstructionFilter(bool forceAvoidMovingUnits = false);
574 
575  /**
576  * Start moving to the given goal, from our current position 'from'.
577  * Might go in a straight line immediately, or might start an asynchronous
578  * path request.
579  */
580  void BeginPathing(CFixedVector2D from, const ICmpPathfinder::Goal& goal);
581 
582  /**
583  * Start an asynchronous long path query.
584  */
585  void RequestLongPath(CFixedVector2D from, const ICmpPathfinder::Goal& goal);
586 
587  /**
588  * Start an asynchronous short path query.
589  */
590  void RequestShortPath(CFixedVector2D from, const ICmpPathfinder::Goal& goal, bool avoidMovingUnits);
591 
592  /**
593  * Select a next long waypoint, given the current unit position.
594  * Also recomputes the short path to use that waypoint.
595  * Returns false on error, or if there is no waypoint to pick.
596  */
597  bool PickNextLongWaypoint(const CFixedVector2D& pos, bool avoidMovingUnits);
598 
599  /**
600  * Convert a path into a renderable list of lines
601  */
602  void RenderPath(const ICmpPathfinder::Path& path, std::vector<SOverlayLine>& lines, CColor color);
603 
604  void RenderSubmit(SceneCollector& collector);
605 };
606 
607 REGISTER_COMPONENT_TYPE(UnitMotion)
608 
609 void CCmpUnitMotion::PathResult(u32 ticket, const ICmpPathfinder::Path& path)
610 {
611  // Ignore obsolete path requests
612  if (ticket != m_ExpectedPathTicket)
613  return;
614 
615  m_ExpectedPathTicket = 0; // we don't expect to get this result again
616 
617  if (m_PathState == PATHSTATE_WAITING_REQUESTING_LONG)
618  {
619  m_LongPath = path;
620  m_ShortPath.m_Waypoints.clear();
621 
622  // If there's no waypoints then we couldn't get near the target.
623  // Sort of hack: Just try going directly to the goal point instead
624  // (via the short pathfinder), so if we're stuck and the user clicks
625  // close enough to the unit then we can probably get unstuck
626  if (m_LongPath.m_Waypoints.empty())
627  {
628  ICmpPathfinder::Waypoint wp = { m_FinalGoal.x, m_FinalGoal.z };
629  m_LongPath.m_Waypoints.push_back(wp);
630  }
631 
632  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
633  if (!cmpPosition || !cmpPosition->IsInWorld())
634  {
635  StartFailed();
636  return;
637  }
638 
639  CFixedVector2D pos = cmpPosition->GetPosition2D();
640 
641  if (!PickNextLongWaypoint(pos, ShouldAvoidMovingUnits()))
642  {
643  StartFailed();
644  return;
645  }
646 
647  // We started a short path request to the next long path waypoint
648  m_PathState = PATHSTATE_WAITING_REQUESTING_SHORT;
649  }
650  else if (m_PathState == PATHSTATE_WAITING_REQUESTING_SHORT)
651  {
652  m_ShortPath = path;
653 
654  // If there's no waypoints then we couldn't get near the target
655  if (m_ShortPath.m_Waypoints.empty())
656  {
657  if (!IsFormationMember())
658  {
659  StartFailed();
660  return;
661  }
662  else
663  {
664  m_Moving = false;
665  CMessageMotionChanged msg(true, true);
666  GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
667  }
668  }
669 
670  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
671  if (!cmpPosition || !cmpPosition->IsInWorld())
672  {
673  StartFailed();
674  return;
675  }
676 
677  // Now we've got a short path that we can follow
678  m_PathState = PATHSTATE_FOLLOWING;
679 
680  StartSucceeded();
681  }
682  else if (m_PathState == PATHSTATE_FOLLOWING_REQUESTING_LONG)
683  {
684  m_LongPath = path;
685  // Leave the old m_ShortPath - we'll carry on following it until the
686  // new short path has been computed
687 
688  // If there's no waypoints then we couldn't get near the target.
689  // Sort of hack: Just try going directly to the goal point instead
690  // (via the short pathfinder), so if we're stuck and the user clicks
691  // close enough to the unit then we can probably get unstuck
692  if (m_LongPath.m_Waypoints.empty())
693  {
694  ICmpPathfinder::Waypoint wp = { m_FinalGoal.x, m_FinalGoal.z };
695  m_LongPath.m_Waypoints.push_back(wp);
696  }
697 
698  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
699  if (!cmpPosition || !cmpPosition->IsInWorld())
700  {
701  StopMoving();
702  return;
703  }
704 
705  CFixedVector2D pos = cmpPosition->GetPosition2D();
706 
707  if (!PickNextLongWaypoint(pos, ShouldAvoidMovingUnits()))
708  {
709  StopMoving();
710  return;
711  }
712 
713  // We started a short path request to the next long path waypoint
714  m_PathState = PATHSTATE_FOLLOWING_REQUESTING_SHORT;
715 
716  // (TODO: is this entirely safe? We might continue moving along our
717  // old path while this request is active, so it'll be slightly incorrect
718  // by the time the request has completed)
719  }
720  else if (m_PathState == PATHSTATE_FOLLOWING_REQUESTING_SHORT)
721  {
722  // Replace the current path with the new one
723  m_ShortPath = path;
724 
725  // If there's no waypoints then we couldn't get near the target
726  if (m_ShortPath.m_Waypoints.empty())
727  {
728  // We should stop moving (unless we're in a formation, in which
729  // case we should continue following it)
730  if (!IsFormationMember())
731  {
732  MoveFailed();
733  return;
734  }
735  else
736  {
737  m_Moving = false;
738  CMessageMotionChanged msg(false, true);
739  GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
740  }
741  }
742 
743  m_PathState = PATHSTATE_FOLLOWING;
744  }
745  else if (m_PathState == PATHSTATE_FOLLOWING_REQUESTING_SHORT_APPEND)
746  {
747  // Append the new path onto our current one
748  m_ShortPath.m_Waypoints.insert(m_ShortPath.m_Waypoints.begin(), path.m_Waypoints.begin(), path.m_Waypoints.end());
749 
750  // If there's no waypoints then we couldn't get near the target
751  // from the last intermediate long-path waypoint. But we can still
752  // continue using the remainder of our current short path. So just
753  // discard the now-useless long path.
754  if (path.m_Waypoints.empty())
755  m_LongPath.m_Waypoints.clear();
756 
757  m_PathState = PATHSTATE_FOLLOWING;
758  }
759  else
760  {
761  LOGWARNING(L"unexpected PathResult (%u %d %d)", GetEntityId(), m_State, m_PathState);
762  }
763 }
764 
766 {
767  PROFILE("Move");
768 
769  if (m_State == STATE_STOPPING)
770  {
772  MoveSucceeded();
773  return;
774  }
775 
776  if (m_State == STATE_IDLE)
777  {
778  return;
779  }
780 
781  switch (m_PathState)
782  {
783  case PATHSTATE_NONE:
784  {
785  // If we're not pathing, do nothing
786  return;
787  }
788 
791  {
792  // If we're waiting for a path and don't have one yet, do nothing
793  return;
794  }
795 
796  case PATHSTATE_FOLLOWING:
800  {
801  // TODO: there's some asymmetry here when units look at other
802  // units' positions - the result will depend on the order of execution.
803  // Maybe we should split the updates into multiple phases to minimise
804  // that problem.
805 
806  CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
807  if (!cmpPathfinder)
808  return;
809 
810  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
811  if (!cmpPosition || !cmpPosition->IsInWorld())
812  return;
813 
814  CFixedVector2D initialPos = cmpPosition->GetPosition2D();
815 
816  // If we're chasing a potentially-moving unit and are currently close
817  // enough to its current position, and we can head in a straight line
818  // to it, then throw away our current path and go straight to it
820  TryGoingStraightToTargetEntity(initialPos);
821 
822  // Keep track of the current unit's position during the update
823  CFixedVector2D pos = initialPos;
824 
825  // If in formation, run to keep up; otherwise just walk
826  // (TODO: support stamina, charging, etc)
827  fixed basicSpeed;
828  if (IsFormationMember())
829  basicSpeed = GetRunSpeed();
830  else
831  basicSpeed = m_Speed; // (typically but not always WalkSpeed)
832 
833  // Find the speed factor of the underlying terrain
834  // (We only care about the tile we start on - it doesn't matter if we're moving
835  // partially onto a much slower/faster tile)
836  fixed terrainSpeed = cmpPathfinder->GetMovementSpeed(pos.X, pos.Y, m_CostClass);
837 
838  fixed maxSpeed = basicSpeed.Multiply(terrainSpeed);
839 
840  bool wasObstructed = false;
841 
842  // We want to move (at most) maxSpeed*dt units from pos towards the next waypoint
843 
844  fixed timeLeft = dt;
845 
846  while (timeLeft > fixed::Zero())
847  {
848  // If we ran out of short path, we have to stop
849  if (m_ShortPath.m_Waypoints.empty())
850  break;
851 
852  CFixedVector2D target(m_ShortPath.m_Waypoints.back().x, m_ShortPath.m_Waypoints.back().z);
853  CFixedVector2D offset = target - pos;
854 
855  // Face towards the target
856  if (!offset.IsZero())
857  {
858  entity_angle_t angle = atan2_approx(offset.X, offset.Y);
859  cmpPosition->TurnTo(angle);
860  }
861 
862  // Work out how far we can travel in timeLeft
863  fixed maxdist = maxSpeed.Multiply(timeLeft);
864 
865  // If the target is close, we can move there directly
866  fixed offsetLength = offset.Length();
867  if (offsetLength <= maxdist)
868  {
869  if (cmpPathfinder->CheckMovement(GetObstructionFilter(), pos.X, pos.Y, target.X, target.Y, m_Radius, m_PassClass))
870  {
871  pos = target;
872 
873  // Spend the rest of the time heading towards the next waypoint
874  timeLeft = timeLeft - (offsetLength / maxSpeed);
875 
876  m_ShortPath.m_Waypoints.pop_back();
877  continue;
878  }
879  else
880  {
881  // Error - path was obstructed
882  wasObstructed = true;
883  break;
884  }
885  }
886  else
887  {
888  // Not close enough, so just move in the right direction
889  offset.Normalize(maxdist);
890  target = pos + offset;
891 
892  if (cmpPathfinder->CheckMovement(GetObstructionFilter(), pos.X, pos.Y, target.X, target.Y, m_Radius, m_PassClass))
893  {
894  pos = target;
895  break;
896  }
897  else
898  {
899  // Error - path was obstructed
900  wasObstructed = true;
901  break;
902  }
903  }
904  }
905 
906  // Update the Position component after our movement (if we actually moved anywhere)
907  if (pos != initialPos)
908  cmpPosition->MoveTo(pos.X, pos.Y);
909 
910  // Calculate the mean speed over this past turn.
911  m_CurSpeed = cmpPosition->GetDistanceTravelled() / dt;
912 
913  if (wasObstructed)
914  {
915  // Oops, we hit something (very likely another unit).
916  // Stop, and recompute the whole path.
917  // TODO: if the target has UnitMotion and is higher priority,
918  // we should wait a little bit.
919 
922 
923  return;
924  }
925 
926  // We successfully moved along our path, until running out of
927  // waypoints or time.
928 
930  {
931  // If we're not currently computing any new paths:
932 
933  // If we are close to reaching the end of the short path
934  // (or have reached it already), try to extend it
935 
936  entity_pos_t minDistance = basicSpeed.Multiply(dt) * WAYPOINT_ADVANCE_LOOKAHEAD_TURNS;
937  if (PathIsShort(m_ShortPath, pos, minDistance))
938  {
939  // Start the path extension from the end of this short path
940  // (or our current position if no short path)
941  CFixedVector2D from = pos;
942  if (!m_ShortPath.m_Waypoints.empty())
944 
946  {
948  }
949  else
950  {
951  // Failed (there were no long waypoints left).
952  // If there's still some short path then continue following
953  // it, else we've finished moving.
954  if (m_ShortPath.m_Waypoints.empty())
955  {
956  if (IsFormationMember())
957  {
958  // We've reached our assigned position. If the controller
959  // is idle, send a notification in case it should disband,
960  // otherwise continue following the formation next turn.
962  if (cmpUnitMotion && !cmpUnitMotion->IsMoving())
963  {
964  m_Moving = false;
965  CMessageMotionChanged msg(false, false);
967  }
968  }
969  else
970  {
971  // Not in formation, so just finish moving
972 
973  StopMoving();
974 
975 
978  // TODO: if the goal was a square building, we ought to point towards the
979  // nearest point on the square, not towards its center
980  }
981  }
982  }
983  }
984  }
985 
986  // If we have a target entity, and we're not miles away from the end of
987  // our current path, and the target moved enough, then recompute our
988  // whole path
990  {
991  if (IsFormationMember())
992  CheckTargetMovement(pos, CHECK_TARGET_MOVEMENT_MIN_DELTA_FORMATION);
993  else
994  CheckTargetMovement(pos, CHECK_TARGET_MOVEMENT_MIN_DELTA);
995  }
996  }
997  }
998 }
999 
1001 {
1003  return false;
1004 
1006  if (!cmpPosition || !cmpPosition->IsInWorld())
1007  return false;
1008 
1009  if (m_TargetOffset.IsZero())
1010  {
1011  // No offset, just return the position directly
1012  out = cmpPosition->GetPosition2D();
1013  }
1014  else
1015  {
1016  // There is an offset, so compute it relative to orientation
1017  entity_angle_t angle = cmpPosition->GetRotation().Y;
1018  CFixedVector2D offset = m_TargetOffset.Rotate(angle);
1019  out = cmpPosition->GetPosition2D() + offset;
1020  }
1021  return true;
1022 }
1023 
1025 {
1026  CFixedVector2D targetPos;
1027  if (!ComputeTargetPosition(targetPos))
1028  return false;
1029 
1030  // Fail if the target is too far away
1031  if ((targetPos - from).CompareLength(DIRECT_PATH_RANGE) > 0)
1032  return false;
1033 
1034  CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
1035  if (!cmpPathfinder)
1036  return false;
1037 
1038  // Move the goal to match the target entity's new position
1040  goal.x = targetPos.X;
1041  goal.z = targetPos.Y;
1042  // (we ignore changes to the target's rotation, since only buildings are
1043  // square and buildings don't move)
1044 
1045  // Find the point on the goal shape that we should head towards
1046  CFixedVector2D goalPos = cmpPathfinder->GetNearestPointOnGoal(from, goal);
1047 
1048  // Check if there's any collisions on that route
1049  if (!cmpPathfinder->CheckMovement(GetObstructionFilter(), from.X, from.Y, goalPos.X, goalPos.Y, m_Radius, m_PassClass))
1050  return false;
1051 
1052  // That route is okay, so update our path
1053  m_FinalGoal = goal;
1054  m_LongPath.m_Waypoints.clear();
1055  m_ShortPath.m_Waypoints.clear();
1056  ICmpPathfinder::Waypoint wp = { goalPos.X, goalPos.Y };
1057  m_ShortPath.m_Waypoints.push_back(wp);
1058 
1059  return true;
1060 }
1061 
1063 {
1064  CFixedVector2D targetPos;
1065  if (!ComputeTargetPosition(targetPos))
1066  return false;
1067 
1068  // Fail unless the target has moved enough
1069  CFixedVector2D oldTargetPos(m_FinalGoal.x, m_FinalGoal.z);
1070  if ((targetPos - oldTargetPos).CompareLength(minDelta) < 0)
1071  return false;
1072 
1073  // Fail unless we're close enough to the target to care about its movement
1074  if (!PathIsShort(m_LongPath, from, CHECK_TARGET_MOVEMENT_AT_MAX_DIST))
1075  return false;
1076 
1077  // Fail if the target is no longer visible to this entity's owner
1078  // (in which case we'll continue moving to its last known location,
1079  // unless it comes back into view before we reach that location)
1080  CmpPtr<ICmpOwnership> cmpOwnership(GetEntityHandle());
1081  if (cmpOwnership)
1082  {
1083  CmpPtr<ICmpRangeManager> cmpRangeManager(GetSystemEntity());
1084  if (cmpRangeManager)
1085  {
1086  if (cmpRangeManager->GetLosVisibility(m_TargetEntity, cmpOwnership->GetOwner()) == ICmpRangeManager::VIS_HIDDEN)
1087  return false;
1088  }
1089  }
1090 
1091  // The target moved and we need to update our current path;
1092  // change the goal here and expect our caller to start the path request
1093  m_FinalGoal.x = targetPos.X;
1094  m_FinalGoal.z = targetPos.Y;
1097 
1098  return true;
1099 }
1100 
1102 {
1103  CFixedVector2D pos = from;
1104  entity_pos_t distLeft = minDistance;
1105 
1106  for (ssize_t i = (ssize_t)path.m_Waypoints.size()-1; i >= 0; --i)
1107  {
1108  // Check if the next path segment is longer than the requested minimum
1109  CFixedVector2D waypoint(path.m_Waypoints[i].x, path.m_Waypoints[i].z);
1110  CFixedVector2D delta = waypoint - pos;
1111  if (delta.CompareLength(distLeft) > 0)
1112  return false;
1113 
1114  // Still short enough - prepare to check the next segment
1115  distLeft -= delta.Length();
1116  pos = waypoint;
1117  }
1118 
1119  // Reached the end of the path before exceeding minDistance
1120  return true;
1121 }
1122 
1124 {
1125  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
1126  if (!cmpPosition || !cmpPosition->IsInWorld())
1127  return;
1128 
1129  CFixedVector2D pos = cmpPosition->GetPosition2D();
1130  FaceTowardsPointFromPos(pos, x, z);
1131 }
1132 
1134 {
1135  CFixedVector2D target(x, z);
1136  CFixedVector2D offset = target - pos;
1137  if (!offset.IsZero())
1138  {
1139  entity_angle_t angle = atan2_approx(offset.X, offset.Y);
1140 
1141  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
1142  if (!cmpPosition)
1143  return;
1144  cmpPosition->TurnTo(angle);
1145  }
1146 }
1147 
1149 {
1150  entity_id_t group;
1151  if (IsFormationMember())
1152  group = m_TargetEntity;
1153  else
1154  group = GetEntityId();
1155 
1156  return ControlGroupMovementObstructionFilter(forceAvoidMovingUnits || ShouldAvoidMovingUnits(), group);
1157 }
1158 
1159 
1160 
1162 {
1163  // Cancel any pending path requests
1165 
1166  // Update the unit's movement status.
1167  m_Moving = true;
1168 
1169  // Set our 'moving' flag, so other units pathfinding now will ignore us
1170  CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
1171  if (cmpObstruction)
1172  cmpObstruction->SetMovingFlag(true);
1173 
1174  // If we're aiming at a target entity and it's close and we can reach
1175  // it in a straight line, then we'll just go along the straight line
1176  // instead of computing a path.
1178  {
1180  return;
1181  }
1182 
1183  // TODO: should go straight to non-entity points too
1184 
1185  // Otherwise we need to compute a path.
1186 
1187  // TODO: if it's close then just do a short path, not a long path
1188  // (But if it's close on the opposite side of a river then we really
1189  // need a long path, so we can't simply check linear distance)
1190 
1192  RequestLongPath(from, goal);
1193 }
1194 
1196 {
1197  CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
1198  if (!cmpPathfinder)
1199  return;
1200 
1201  cmpPathfinder->SetDebugPath(from.X, from.Y, goal, m_PassClass, m_CostClass);
1202 
1203  m_ExpectedPathTicket = cmpPathfinder->ComputePathAsync(from.X, from.Y, goal, m_PassClass, m_CostClass, GetEntityId());
1204 }
1205 
1206 void CCmpUnitMotion::RequestShortPath(CFixedVector2D from, const ICmpPathfinder::Goal& goal, bool avoidMovingUnits)
1207 {
1208  CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
1209  if (!cmpPathfinder)
1210  return;
1211 
1212  m_ExpectedPathTicket = cmpPathfinder->ComputeShortPathAsync(from.X, from.Y, m_Radius, SHORT_PATH_SEARCH_RANGE, goal, m_PassClass, avoidMovingUnits, m_TargetEntity, GetEntityId());
1213 }
1214 
1215 bool CCmpUnitMotion::PickNextLongWaypoint(const CFixedVector2D& pos, bool avoidMovingUnits)
1216 {
1217  // If there's no long path, we can't pick the next waypoint from it
1218  if (m_LongPath.m_Waypoints.empty())
1219  return false;
1220 
1221  // First try to get the immediate next waypoint
1222  entity_pos_t targetX = m_LongPath.m_Waypoints.back().x;
1223  entity_pos_t targetZ = m_LongPath.m_Waypoints.back().z;
1224  m_LongPath.m_Waypoints.pop_back();
1225 
1226  // To smooth the motion and avoid grid-constrained movement and allow dynamic obstacle avoidance,
1227  // try skipping some more waypoints if they're close enough
1228 
1229  while (!m_LongPath.m_Waypoints.empty())
1230  {
1232  if ((w - pos).CompareLength(WAYPOINT_ADVANCE_MAX) > 0)
1233  break;
1234  targetX = m_LongPath.m_Waypoints.back().x;
1235  targetZ = m_LongPath.m_Waypoints.back().z;
1236  m_LongPath.m_Waypoints.pop_back();
1237  }
1238 
1239  // Now we need to recompute a short path to the waypoint
1240 
1241  ICmpPathfinder::Goal goal;
1242  if (m_LongPath.m_Waypoints.empty())
1243  {
1244  // This was the last waypoint - head for the exact goal
1245  goal = m_FinalGoal;
1246  }
1247  else
1248  {
1249  // Head for somewhere near the waypoint (but allow some leeway in case it's obstructed)
1251  goal.hw = SHORT_PATH_GOAL_RADIUS;
1252  goal.x = targetX;
1253  goal.z = targetZ;
1254  }
1255 
1256  CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
1257  if (!cmpPathfinder)
1258  return false;
1259 
1260  m_ExpectedPathTicket = cmpPathfinder->ComputeShortPathAsync(pos.X, pos.Y, m_Radius, SHORT_PATH_SEARCH_RANGE, goal, m_PassClass, avoidMovingUnits, GetEntityId(), GetEntityId());
1261 
1262  return true;
1263 }
1264 
1265 
1267 {
1268  PROFILE("MoveToPointRange");
1269 
1270  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
1271  if (!cmpPosition || !cmpPosition->IsInWorld())
1272  return false;
1273 
1274  CFixedVector2D pos = cmpPosition->GetPosition2D();
1275 
1276  ICmpPathfinder::Goal goal;
1277 
1278  if (minRange.IsZero() && maxRange.IsZero())
1279  {
1280  // Handle the non-ranged mode:
1281 
1282  // Check whether this point is in an obstruction
1283 
1284  CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
1285  if (!cmpObstructionManager)
1286  return false;
1287 
1289  if (cmpObstructionManager->FindMostImportantObstruction(GetObstructionFilter(true), x, z, m_Radius, obstruction))
1290  {
1291  // If we're aiming inside a building, then aim for the outline of the building instead
1292  // TODO: if we're aiming at a unit then maybe a circle would look nicer?
1293 
1295  goal.x = obstruction.x;
1296  goal.z = obstruction.z;
1297  goal.u = obstruction.u;
1298  goal.v = obstruction.v;
1299  goal.hw = obstruction.hw + m_Radius + g_GoalDelta; // nudge the goal outwards so it doesn't intersect the building itself
1300  goal.hh = obstruction.hh + m_Radius + g_GoalDelta;
1301  }
1302  else
1303  {
1304  // Unobstructed - head directly for the goal
1306  goal.x = x;
1307  goal.z = z;
1308  }
1309  }
1310  else
1311  {
1312  entity_pos_t distance = (pos - CFixedVector2D(x, z)).Length();
1313 
1314  entity_pos_t goalDistance;
1315  if (distance < minRange)
1316  {
1317  goalDistance = minRange + g_GoalDelta;
1318  }
1319  else if (maxRange >= entity_pos_t::Zero() && distance > maxRange)
1320  {
1321  goalDistance = maxRange - g_GoalDelta;
1322  }
1323  else
1324  {
1325  // We're already in range - no need to move anywhere
1327  FaceTowardsPointFromPos(pos, x, z);
1328  return false;
1329  }
1330 
1331  // TODO: what happens if goalDistance < 0? (i.e. we probably can never get close enough to the target)
1332 
1334  goal.x = x;
1335  goal.z = z;
1336 
1337  // Formerly added m_Radius, but it seems better to go by the mid-point.
1338  goal.hw = goalDistance;
1339  }
1340 
1344  m_TargetMinRange = minRange;
1345  m_TargetMaxRange = maxRange;
1346  m_FinalGoal = goal;
1347 
1348  BeginPathing(pos, goal);
1349 
1350  return true;
1351 }
1352 
1354 {
1355  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
1356  if (!cmpPosition || !cmpPosition->IsInWorld())
1357  return false;
1358 
1359  CFixedVector2D pos = cmpPosition->GetPosition2D();
1360 
1361  bool hasObstruction = false;
1362  CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
1364  if (cmpObstructionManager)
1365  hasObstruction = cmpObstructionManager->FindMostImportantObstruction(GetObstructionFilter(true), x, z, m_Radius, obstruction);
1366 
1367  if (minRange.IsZero() && maxRange.IsZero() && hasObstruction)
1368  {
1369  // Handle the non-ranged mode:
1370  CFixedVector2D halfSize(obstruction.hw, obstruction.hh);
1371  entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize);
1372 
1373  // See if we're too close to the target square
1374  if (distance < minRange)
1375  return false;
1376 
1377  // See if we're close enough to the target square
1378  if (maxRange < entity_pos_t::Zero() || distance <= maxRange)
1379  return true;
1380 
1381  return false;
1382  }
1383  else
1384  {
1385  entity_pos_t distance = (pos - CFixedVector2D(x, z)).Length();
1386 
1387  if (distance < minRange)
1388  {
1389  return false;
1390  }
1391  else if (maxRange >= entity_pos_t::Zero() && distance > maxRange)
1392  {
1393  return false;
1394  }
1395  else
1396  {
1397  return true;
1398  }
1399  }
1400 }
1401 
1403 {
1404  // Given a square, plus a target range we should reach, the shape at that distance
1405  // is a round-cornered square which we can approximate as either a circle or as a square.
1406  // Choose the shape that will minimise the worst-case error:
1407 
1408  // For a square, error is (sqrt(2)-1) * range at the corners
1409  entity_pos_t errSquare = (entity_pos_t::FromInt(4142)/10000).Multiply(range);
1410 
1411  // For a circle, error is radius-hw at the sides and radius-hh at the top/bottom
1412  entity_pos_t errCircle = circleRadius - std::min(hw, hh);
1413 
1414  return (errCircle < errSquare);
1415 }
1416 
1418 {
1419  PROFILE("MoveToTargetRange");
1420 
1421  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
1422  if (!cmpPosition || !cmpPosition->IsInWorld())
1423  return false;
1424 
1425  CFixedVector2D pos = cmpPosition->GetPosition2D();
1426 
1427  CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
1428  if (!cmpObstructionManager)
1429  return false;
1430 
1431  bool hasObstruction = false;
1433  CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), target);
1434  if (cmpObstruction)
1435  hasObstruction = cmpObstruction->GetObstructionSquare(obstruction);
1436 
1437  /*
1438  * If we're starting outside the maxRange, we need to move closer in.
1439  * If we're starting inside the minRange, we need to move further out.
1440  * These ranges are measured from the center of this entity to the edge of the target;
1441  * we add the goal range onto the size of the target shape to get the goal shape.
1442  * (Then we extend it outwards/inwards by a little bit to be sure we'll end up
1443  * within the right range, in case of minor numerical inaccuracies.)
1444  *
1445  * There's a bit of a problem with large square targets:
1446  * the pathfinder only lets us move to goals that are squares, but the points an equal
1447  * distance from the target make a rounded square shape instead.
1448  *
1449  * When moving closer, we could shrink the goal radius to 1/sqrt(2) so the goal shape fits entirely
1450  * within the desired rounded square, but that gives an unfair advantage to attackers who approach
1451  * the target diagonally.
1452  *
1453  * If the target is small relative to the range (e.g. archers attacking anything),
1454  * then we cheat and pretend the target is actually a circle.
1455  * (TODO: that probably looks rubbish for things like walls?)
1456  *
1457  * If the target is large relative to the range (e.g. melee units attacking buildings),
1458  * then we multiply maxRange by approx 1/sqrt(2) to guarantee they'll always aim close enough.
1459  * (Those units should set minRange to 0 so they'll never be considered *too* close.)
1460  */
1461 
1462  if (hasObstruction)
1463  {
1464  CFixedVector2D halfSize(obstruction.hw, obstruction.hh);
1465  ICmpPathfinder::Goal goal;
1466  goal.x = obstruction.x;
1467  goal.z = obstruction.z;
1468 
1469  entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize);
1470 
1471  if (distance < minRange)
1472  {
1473  // Too close to the square - need to move away
1474 
1475  // TODO: maybe we should do the ShouldTreatTargetAsCircle thing here?
1476 
1477  entity_pos_t goalDistance = minRange + g_GoalDelta;
1478 
1480  goal.u = obstruction.u;
1481  goal.v = obstruction.v;
1482  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
1483  goal.hw = obstruction.hw + delta;
1484  goal.hh = obstruction.hh + delta;
1485  }
1486  else if (maxRange < entity_pos_t::Zero() || distance < maxRange)
1487  {
1488  // We're already in range - no need to move anywhere
1490  FaceTowardsPointFromPos(pos, goal.x, goal.z);
1491  return false;
1492  }
1493  else
1494  {
1495  // We might need to move closer:
1496 
1497  // Circumscribe the square
1498  entity_pos_t circleRadius = halfSize.Length();
1499 
1500  if (ShouldTreatTargetAsCircle(maxRange, obstruction.hw, obstruction.hh, circleRadius))
1501  {
1502  // The target is small relative to our range, so pretend it's a circle
1503 
1504  // Note that the distance to the circle will always be less than
1505  // the distance to the square, so the previous "distance < maxRange"
1506  // check is still valid (though not sufficient)
1507  entity_pos_t circleDistance = (pos - CFixedVector2D(obstruction.x, obstruction.z)).Length() - circleRadius;
1508 
1509  if (circleDistance < maxRange)
1510  {
1511  // We're already in range - no need to move anywhere
1513  FaceTowardsPointFromPos(pos, goal.x, goal.z);
1514  return false;
1515  }
1516 
1517  entity_pos_t goalDistance = maxRange - g_GoalDelta;
1518 
1520  goal.hw = circleRadius + goalDistance;
1521  }
1522  else
1523  {
1524  // The target is large relative to our range, so treat it as a square and
1525  // get close enough that the diagonals come within range
1526 
1527  entity_pos_t goalDistance = (maxRange - g_GoalDelta)*2 / 3; // multiply by slightly less than 1/sqrt(2)
1528 
1530  goal.u = obstruction.u;
1531  goal.v = obstruction.v;
1532  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
1533  goal.hw = obstruction.hw + delta;
1534  goal.hh = obstruction.hh + delta;
1535  }
1536  }
1537 
1539  m_TargetEntity = target;
1541  m_TargetMinRange = minRange;
1542  m_TargetMaxRange = maxRange;
1543  m_FinalGoal = goal;
1544 
1545  BeginPathing(pos, goal);
1546 
1547  return true;
1548  }
1549  else
1550  {
1551  // The target didn't have an obstruction or obstruction shape, so treat it as a point instead
1552 
1553  CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), target);
1554  if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
1555  return false;
1556 
1557  CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
1558 
1559  return MoveToPointRange(targetPos.X, targetPos.Y, minRange, maxRange);
1560  }
1561 }
1562 
1564 {
1565  // This function closely mirrors MoveToTargetRange - it needs to return true
1566  // after that Move has completed
1567 
1568  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
1569  if (!cmpPosition || !cmpPosition->IsInWorld())
1570  return false;
1571 
1572  CFixedVector2D pos = cmpPosition->GetPosition2D();
1573 
1574  CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
1575  if (!cmpObstructionManager)
1576  return false;
1577 
1578  bool hasObstruction = false;
1580  CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), target);
1581  if (cmpObstruction)
1582  hasObstruction = cmpObstruction->GetObstructionSquare(obstruction);
1583 
1584  if (hasObstruction)
1585  {
1586  CFixedVector2D halfSize(obstruction.hw, obstruction.hh);
1587  entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize);
1588 
1589  // See if we're too close to the target square
1590  if (distance < minRange)
1591  return false;
1592 
1593  // See if we're close enough to the target square
1594  if (maxRange < entity_pos_t::Zero() || distance <= maxRange)
1595  return true;
1596 
1597  entity_pos_t circleRadius = halfSize.Length();
1598 
1599  if (ShouldTreatTargetAsCircle(maxRange, obstruction.hw, obstruction.hh, circleRadius))
1600  {
1601  // The target is small relative to our range, so pretend it's a circle
1602  // and see if we're close enough to that
1603 
1604  entity_pos_t circleDistance = (pos - CFixedVector2D(obstruction.x, obstruction.z)).Length() - circleRadius;
1605 
1606  if (circleDistance <= maxRange)
1607  return true;
1608  }
1609 
1610  return false;
1611  }
1612  else
1613  {
1614  CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), target);
1615  if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
1616  return false;
1617 
1618  CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
1619 
1620  entity_pos_t distance = (pos - targetPos).Length();
1621 
1622  if (minRange <= distance && (maxRange < entity_pos_t::Zero() || distance <= maxRange))
1623  return true;
1624 
1625  return false;
1626  }
1627 }
1628 
1630 {
1631  CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), target);
1632  if (!cmpPosition || !cmpPosition->IsInWorld())
1633  return;
1634 
1635  CFixedVector2D pos = cmpPosition->GetPosition2D();
1636 
1637  ICmpPathfinder::Goal goal;
1639  goal.x = pos.X;
1640  goal.z = pos.Y;
1641 
1643  m_TargetEntity = target;
1647  m_FinalGoal = goal;
1648 
1649  BeginPathing(pos, goal);
1650 }
1651 
1652 
1653 
1654 
1655 
1656 void CCmpUnitMotion::RenderPath(const ICmpPathfinder::Path& path, std::vector<SOverlayLine>& lines, CColor color)
1657 {
1658  bool floating = false;
1659  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
1660  if (cmpPosition)
1661  floating = cmpPosition->IsFloating();
1662 
1663  lines.clear();
1664  std::vector<float> waypointCoords;
1665  for (size_t i = 0; i < path.m_Waypoints.size(); ++i)
1666  {
1667  float x = path.m_Waypoints[i].x.ToFloat();
1668  float z = path.m_Waypoints[i].z.ToFloat();
1669  waypointCoords.push_back(x);
1670  waypointCoords.push_back(z);
1671  lines.push_back(SOverlayLine());
1672  lines.back().m_Color = color;
1673  SimRender::ConstructSquareOnGround(GetSimContext(), x, z, 1.0f, 1.0f, 0.0f, lines.back(), floating);
1674  }
1675  lines.push_back(SOverlayLine());
1676  lines.back().m_Color = color;
1677  SimRender::ConstructLineOnGround(GetSimContext(), waypointCoords, lines.back(), floating);
1678 
1679 }
1680 
1682 {
1683  if (!m_DebugOverlayEnabled)
1684  return;
1685 
1688 
1689  for (size_t i = 0; i < m_DebugOverlayLongPathLines.size(); ++i)
1690  collector.Submit(&m_DebugOverlayLongPathLines[i]);
1691 
1692  for (size_t i = 0; i < m_DebugOverlayShortPathLines.size(); ++i)
1693  collector.Submit(&m_DebugOverlayShortPathLines[i]);
1694 }
int CompareLength(fixed cmp) const
Returns -1, 0, +1 depending on whether length is less/equal/greater than the argument.
An entity initialisation parameter node.
Definition: ParamNode.h:112
virtual pass_class_t GetPassabilityClass(const std::string &name)=0
Get the tag for a given passability class name.
void SubscribeToMessageType(MessageTypeId mtid)
Subscribe the current component type to the given message type.
Update phase for formation controller movement (must happen before individual units move to follow th...
Definition: MessageTypes.h:74
#define u8
Definition: types.h:39
A simple fixed-point number class.
Definition: Fixed.h:115
static const int WAYPOINT_ADVANCE_LOOKAHEAD_TURNS
When advancing along the long path, we&#39;ll pick a new waypoint to move towards if we expect to reach t...
void PathResult(u32 ticket, const ICmpPathfinder::Path &path)
Handle the result of an asynchronous path query.
bool ShouldAvoidMovingUnits()
#define REGISTER_COMPONENT_TYPE(cname)
Definition: Component.h:30
void RequestShortPath(CFixedVector2D from, const ICmpPathfinder::Goal &goal, bool avoidMovingUnits)
Start an asynchronous short path query.
Helper templates for serializing/deserializing common objects.
ICmpPathfinder::Path m_LongPath
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning. ...
Line-based overlay, with world-space coordinates, rendered in the world potentially behind other obje...
Definition: Overlay.h:36
ControlGroupMovementObstructionFilter GetObstructionFilter(bool forceAvoidMovingUnits=false)
Returns an appropriate obstruction filter for use with path requests.
bool ToBool() const
Parses the content of this node as a boolean (&quot;true&quot; == true, anything else == false) ...
Definition: ParamNode.cpp:236
CFixedVector2D v
virtual void Deserialize(const CParamNode &paramNode, IDeserializer &deserialize)
virtual u32 ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal &goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t group, entity_id_t notify)=0
Asynchronous version of ComputeShortPath (using ControlGroupObstructionFilter).
static CFixed Zero()
Definition: Fixed.h:127
const ssize_t TERRAIN_TILE_SIZE
metres [world space units] per tile in x and z
Definition: Terrain.h:40
void RequestLongPath(CFixedVector2D from, const ICmpPathfinder::Goal &goal)
Start an asynchronous long path query.
CFixedVector2D m_TargetOffset
void RenderSubmit(SceneCollector &collector)
const std::string ToUTF8() const
Returns the content of this node as an 8-bit string.
Definition: ParamNode.cpp:203
bool IsOk() const
Returns true if this is a valid CParamNode, false if it represents a non-existent node...
Definition: ParamNode.cpp:193
ICmpPathfinder::cost_class_t m_CostClass
virtual fixed GetMovementSpeed(entity_pos_t x0, entity_pos_t z0, cost_class_t costClass)=0
Find the speed factor (typically around 1.0) for a unit of the given cost class at the given position...
Definition: Overlay.h:34
bool IsZero() const
Serialization interface; see serialization overview.
Definition: ISerializer.h:120
Obstruction test filter that reject shapes in a given control group, and rejects shapes that don&#39;t bl...
Add renderable objects to the scene collector.
Definition: MessageTypes.h:145
Sent by CCmpUnitMotion during Update, whenever the motion status has changed since the previous updat...
Definition: MessageTypes.h:265
static std::string GetSchema()
static void out(const wchar_t *fmt,...)
Definition: wdbg_sym.cpp:419
std::vector< SOverlayLine > m_DebugOverlayShortPathLines
virtual CFixedVector3D GetRotation()=0
Returns the current rotation (relative to the upwards axis), as Euler angles with X=pitch...
virtual fixed GetCurrentSpeed()
Get the current movement speed.
virtual void SetUnitRadius(fixed radius)
Override the default obstruction radius, used for planning paths and checking for collisions...
Returned path.
std::vector< Waypoint > m_Waypoints
virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z)
Turn to look towards the given point.
virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare &out)=0
Gets the square corresponding to this obstruction shape.
virtual cost_class_t GetCostClass(const std::string &name)=0
Get the tag for a given movement cost class name.
virtual fixed GetWalkSpeed()
Get the default speed that this unit will have when walking, in metres per second.
Update phase for non-formation-controller unit movement.
Definition: MessageTypes.h:90
entity_pos_t m_TargetMinRange
void FaceTowardsPointFromPos(CFixedVector2D pos, entity_pos_t x, entity_pos_t z)
Rotate to face towards the target point, given the current pos.
virtual fixed GetRunSpeed()
Get the default speed that this unit will have when running, in metres per second.
static const entity_pos_t WAYPOINT_ADVANCE_MAX
When advancing along the long path, and picking a new waypoint to move towards, we&#39;ll pick one that&#39;s...
virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z)
Join a formation, and move towards a given offset relative to the formation controller entity...
CFixedVector2D Rotate(fixed angle)
Rotate the vector by the given angle (anticlockwise).
virtual void Serialize(ISerializer &serialize)
virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
Attempt to walk into range of a given target entity, or as close as possible.
virtual bool IsInWorld()=0
Returns true if the entity currently exists at a defined position in the world.
Pathfinder algorithms.
virtual ELosVisibility GetLosVisibility(CEntityHandle ent, player_id_t player, bool forceRetainInFog=false)=0
Returns the visibility status of the given entity, with respect to the given player.
This interface accepts renderable objects.
Definition: Scene.h:82
Motion interface for entities with complex movement capabilities.
#define LOGWARNING
Definition: CLogger.h:34
CEntityHandle GetEntityHandle() const
Definition: IComponent.h:45
fixed ToFixed() const
Parses the content of this node as a fixed-point number.
Definition: ParamNode.cpp:222
virtual void StopMoving()
Stop moving immediately.
bool PickNextLongWaypoint(const CFixedVector2D &pos, bool avoidMovingUnits)
Select a next long waypoint, given the current unit position.
virtual void HandleMessage(const CMessage &msg, bool global)
static const entity_pos_t CHECK_TARGET_MOVEMENT_AT_MAX_DIST
If we&#39;re following something but it&#39;s more than this distance away along our path, then don&#39;t bother trying to repath regardless of how much it has moved, until we get this close to the end of our old path.
virtual fixed GetDistanceTravelled()=0
Returns the distance that the unit will be interpolated over, i.e.
entity_id_t GetEntityId() const
Definition: IComponent.h:48
virtual void SetMovingFlag(bool enabled)=0
virtual bool IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange)
Determine wether the givven point is within the given range, using the same measurement as MoveToPoin...
entity_pos_t m_TargetMaxRange
virtual int GetType() const =0
bool ComputeTargetPosition(CFixedVector2D &out)
Computes the current location of our target entity (plus offset).
const CParamNode & GetChild(const char *name) const
Returns the (unique) child node with the given name, or a node with IsOk() == false if there is none...
Definition: ParamNode.cpp:185
virtual bool CheckMovement(const IObstructionTestFilter &filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, pass_class_t passClass)=0
Check whether the given movement line is valid and doesn&#39;t hit any obstructions or impassable terrain...
void ConstructLineOnGround(const CSimContext &context, const std::vector< float > &xz, SOverlayLine &overlay, bool floating, float heightOffset=0.25f)
Constructs overlay line from given points, conforming to terrain.
Definition: Render.cpp:35
virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange)
Attempt to walk into range of a to a given point, or as close as possible.
CFixedVector2D u
static void ClassInit(CComponentManager &componentManager)
Definition: path.h:75
entity_pos_t m_Radius
void Move(fixed dt)
Do the per-turn movement and other updates.
virtual void Init(const CParamNode &paramNode)
virtual void TurnTo(entity_angle_t y)=0
Rotate smoothly to the given angle around the upwards axis.
bool ShouldTreatTargetAsCircle(entity_pos_t range, entity_pos_t hw, entity_pos_t hh, entity_pos_t circleRadius)
Decide whether to approximate the given range from a square target as a circle, rather than as a squa...
static const entity_pos_t DIRECT_PATH_RANGE
If we are this close to our target entity/point, then think about heading for it in a straight line i...
CFixed Multiply(CFixed n) const
Multiply by a CFixed.
Definition: Fixed.h:290
static const entity_pos_t CHECK_TARGET_MOVEMENT_MIN_DELTA_FORMATION
If we&#39;re following as part of a formation, but can&#39;t move to our assigned target point in a straight ...
CFixedVector2D m_TargetPos
virtual CFixedVector2D GetPosition2D()=0
Returns the current x,z position (no interpolation).
void SerializeCommon(S &serialize)
virtual player_id_t GetOwner()=0
bool CheckTargetMovement(CFixedVector2D from, entity_pos_t minDelta)
Returns whether the target entity has moved more than minDelta since our last path computations...
void RenderPath(const ICmpPathfinder::Path &path, std::vector< SOverlayLine > &lines, CColor color)
Convert a path into a renderable list of lines.
bool PathIsShort(const ICmpPathfinder::Path &path, CFixedVector2D from, entity_pos_t minDistance)
Returns whether the length of the given path, plus the distance from &#39;from&#39; to the first waypoints...
virtual void MoveTo(entity_pos_t x, entity_pos_t z)=0
Move smoothly to the given location.
static const entity_pos_t g_GoalDelta
#define PROFILE(name)
Definition: Profile.h:195
#define DEFAULT_COMPONENT_ALLOCATOR(cname)
Definition: Component.h:44
virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const Goal &goal, pass_class_t passClass, cost_class_t costClass, entity_id_t notify)=0
Asynchronous version of ComputePath.
const CSimContext & GetSimContext() const
Definition: IComponent.h:52
virtual bool IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
Determine whether the target is within the given range, using the same measurement as MoveToTargetRan...
A simplified syntax for accessing entity components.
Definition: CmpPtr.h:55
virtual bool IsMoving()
Get whether the unit is moving.
intptr_t ssize_t
Definition: wposix_types.h:82
CEntityHandle GetSystemEntity() const
Definition: IComponent.h:50
static CFixed FromInt(int n)
Definition: Fixed.h:136
Helper functions related to rendering.
virtual entity_pos_t GetUnitRadius()=0
void PostMessage(entity_id_t ent, const CMessage &msg) const
Send a message, targeted at a particular entity.
ICmpPathfinder::Path m_ShortPath
fixed Length() const
Returns the length of the vector.
Definition: FixedVector2D.h:95
bool TryGoingStraightToTargetEntity(CFixedVector2D from)
Attempts to replace the current path with a straight line to the target entity, if it&#39;s close enough ...
virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const Goal &goal, pass_class_t passClass, cost_class_t costClass)=0
If the debug overlay is enabled, render the path that will computed by ComputePath.
enum ICmpPathfinder::Goal::Type type
#define u32
Definition: types.h:41
entity_id_t m_TargetEntity
Sent by CCmpPathfinder after async path requests.
Definition: MessageTypes.h:354
bool IsZero() const
Returns true if the number is precisely 0.
Definition: Fixed.h:199
SceneCollector & collector
Definition: MessageTypes.h:155
virtual void SetSpeed(fixed speed)
Set the current movement speed.
virtual bool IsMoving()=0
Get whether the unit is moving.
virtual void SetFacePointAfterMove(bool facePointAfterMove)
Set whether the unit will turn to face the target point after finishing moving.
void ConstructSquareOnGround(const CSimContext &context, float x, float z, float w, float h, float a, SOverlayLine &overlay, bool floating, float heightOffset=0.25f)
Constructs overlay line as rectangle with given center and dimensions, conforming to terrain...
Definition: Render.cpp:121
static const CColor OVERLAY_COLOUR_SHORT_PATH(1, 0, 0, 1)
fixed DistanceToSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize)
Definition: Geometry.cpp:53
ICmpPathfinder::Path path
Definition: MessageTypes.h:365
void BeginPathing(CFixedVector2D from, const ICmpPathfinder::Goal &goal)
Start moving to the given goal, from our current position &#39;from&#39;.
virtual void Deinit()
virtual ICmpPathfinder::pass_class_t GetPassabilityClass()
Get the unit&#39;s passability class.
CFixed_15_16 atan2_approx(CFixed_15_16 y, CFixed_15_16 x)
Inaccurate approximation of atan2 over fixed-point numbers.
Definition: Fixed.cpp:147
virtual CFixedVector2D GetNearestPointOnGoal(CFixedVector2D pos, const Goal &goal)=0
Returns the coordinates of the point on the goal that is closest to pos in a straight line...
virtual bool FindMostImportantObstruction(const IObstructionTestFilter &filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, ObstructionSquare &square)=0
Find a single obstruction that blocks a unit at the given point with the given radius.
static float Length(const SVec3 v)
Definition: mikktspace.cpp:112
const entity_id_t INVALID_ENTITY
Invalid entity ID.
Definition: Entity.h:36
u32 entity_id_t
Entity ID type.
Definition: Entity.h:24
virtual void Submit(CPatch *patch)=0
Submit a terrain patch that is part of the scene.
virtual void SetDebugOverlay(bool enabled)
Toggle the rendering of debug info.
static const entity_pos_t CHECK_TARGET_MOVEMENT_MIN_DELTA
If we&#39;re following a target entity, we will recompute our path if the target has moved more than this...
static const entity_pos_t SHORT_PATH_GOAL_RADIUS
When short-pathing to an intermediate waypoint, we aim for a circle of this radius around the waypoin...
Standard representation for all types of shapes, for use with geometry processing code...
static const CColor OVERLAY_COLOUR_LONG_PATH(1, 1, 1, 1)
Helper functions related to geometry algorithms.
std::vector< SOverlayLine > m_DebugOverlayLongPathLines
CComponentManager & GetComponentManager() const
Definition: SimContext.cpp:35
Deserialization interface; see serialization overview.
Definition: IDeserializer.h:34
ICmpPathfinder::Goal m_FinalGoal
static const entity_pos_t SHORT_PATH_SEARCH_RANGE
Maximum range to restrict short path queries to.
virtual bool IsFloating()=0
Returns whether the entity floats on water.
ICmpPathfinder::pass_class_t m_PassClass