Pyrogenesis  trunk
CCmpUnitMotion.h
Go to the documentation of this file.
1 /* Copyright (C) 2022 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 #ifndef INCLUDED_CCMPUNITMOTION
19 #define INCLUDED_CCMPUNITMOTION
20 
22 #include "ICmpUnitMotion.h"
23 
38 
39 #include "graphics/Overlay.h"
40 #include "maths/FixedVector2D.h"
41 #include "ps/CLogger.h"
42 #include "ps/Profile.h"
43 #include "renderer/Scene.h"
44 
45 #include <algorithm>
46 
47 // NB: this implementation of ICmpUnitMotion is very tightly coupled with UnitMotionManager.
48 // As such, both are compiled in the same TU.
49 
50 // For debugging; units will start going straight to the target
51 // instead of calling the pathfinder
52 #define DISABLE_PATHFINDER 0
53 
54 namespace
55 {
56 /**
57  * Min/Max range to restrict short path queries to. (Larger ranges are (much) slower,
58  * smaller ranges might miss some legitimate routes around large obstacles.)
59  * NB: keep the max-range in sync with the vertex pathfinder "move the search space" heuristic.
60  */
65 
66 /**
67  * When using the short-pathfinder to rejoin a long-path waypoint, aim for a circle of this radius around the waypoint.
68  */
70 
71 /**
72  * Minimum distance to goal for a long path request
73  */
75 
76 /**
77  * If we are this close to our target entity/point, then think about heading
78  * for it in a straight line instead of pathfinding.
79  */
81 
82 /**
83  * To avoid recomputing paths too often, have some leeway for target range checks
84  * based on our distance to the target. Increase that incertainty by one navcell
85  * for every this many tiles of distance.
86  */
88 
89 /**
90  * When following a known imperfect path (i.e. a path that won't take us in range of our goal
91  * we still recompute a new path every N turn to adapt to moving targets (for example, ships that must pickup
92  * units may easily end up in this state, they still need to adjust to moving units).
93  * This is rather arbitrary and mostly for simplicity & optimisation (a better recomputing algorithm
94  * would not need this).
95  */
97 
98 /**
99  * When we fail to move this many turns in a row, inform other components that the move will fail.
100  * Experimentally, this number needs to be somewhat high or moving groups of units will lead to stuck units.
101  * However, too high means units will look idle for a long time when they are failing to move.
102  * TODO: if UnitMotion could send differentiated "unreachable" and "currently stuck" failing messages,
103  * this could probably be lowered.
104  * TODO: when unit pushing is implemented, this number can probably be lowered.
105  */
106 constexpr u8 MAX_FAILED_MOVEMENTS = 35;
107 
108 /**
109  * When computing paths but failing to move, we want to occasionally alternate pathfinder systems
110  * to avoid getting stuck (the short pathfinder can unstuck the long-range one and vice-versa, depending).
111  */
114 
115 /**
116  * Units can occasionally get stuck near corners. The cause is a mismatch between CheckMovement and the short pathfinder.
117  * The problem is the short pathfinder finds an impassable path when units are right on an obstruction edge.
118  * Fixing this math mismatch is perhaps possible, but fixing it in UM is rather easy: just try backing up a bit
119  * and that will probably un-stuck the unit. This is the 'failed movement' turn on which to try that.
120  */
121 constexpr u8 BACKUP_HACK_DELAY = 10;
122 
123 /**
124  * After this many failed computations, start sending "VERY_OBSTRUCTED" messages instead.
125  * Should probably be larger than ALTERNATE_PATH_TYPE_DELAY.
126  */
128 
129 const CColor OVERLAY_COLOR_LONG_PATH(1, 1, 1, 1);
130 const CColor OVERLAY_COLOR_SHORT_PATH(1, 0, 0, 1);
131 } // anonymous namespace
132 
133 class CCmpUnitMotion final : public ICmpUnitMotion
134 {
135  friend class CCmpUnitMotionManager;
136 public:
137  static void ClassInit(CComponentManager& componentManager)
138  {
139  componentManager.SubscribeToMessageType(MT_Create);
140  componentManager.SubscribeToMessageType(MT_Destroy);
141  componentManager.SubscribeToMessageType(MT_PathResult);
145  componentManager.SubscribeToMessageType(MT_Deserialized);
146  }
147 
149 
153 
154  // Template state:
155 
157 
161 
162  // Dynamic state:
163 
165 
166  // cached for efficiency
168 
170 
171  // Whether the unit participates in pushing.
172  bool m_Pushing = false;
173 
174  // Whether the unit blocks movement (& is blocked by movement blockers)
175  // Cached from ICmpObstruction.
176  bool m_BlockMovement = false;
177 
178  // Internal counter used when recovering from obstructed movement.
179  // Most notably, increases the search range of the vertex pathfinder.
180  // See HandleObstructedMove() for more details.
182 
183  // If > 0, PathingUpdateNeeded returns false always.
184  // This exists because the goal may be unreachable to the short/long pathfinder.
185  // In such cases, we would compute inacceptable paths and PathingUpdateNeeded would trigger every turn,
186  // which would be quite bad for performance.
187  // To avoid that, when we know the new path is imperfect, treat it as OK and follow it anyways.
188  // When reaching the end, we'll go through HandleObstructedMove and reset regardless.
189  // To still recompute now and then (the target may be moving), this is a countdown decremented on each frame.
191 
192  struct Ticket {
193  u32 m_Ticket = 0; // asynchronous request ID we're waiting for, or 0 if none
194  enum Type {
196  LONG_PATH
197  } m_Type = SHORT_PATH; // Pick some default value to avoid UB.
198 
199  void clear() { m_Ticket = 0; }
201 
202  struct MoveRequest {
203  enum Type {
208  } m_Type = NONE;
212 
213  // For readability
214  CFixedVector2D GetOffset() const { return m_Position; };
215 
216  MoveRequest() = default;
217  MoveRequest(CFixedVector2D pos, entity_pos_t minRange, entity_pos_t maxRange) : m_Type(POINT), m_Position(pos), m_MinRange(minRange), m_MaxRange(maxRange) {};
218  MoveRequest(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) : m_Type(ENTITY), m_Entity(target), m_MinRange(minRange), m_MaxRange(maxRange) {};
219  MoveRequest(entity_id_t target, CFixedVector2D offset) : m_Type(OFFSET), m_Entity(target), m_Position(offset) {};
220  } m_MoveRequest;
221 
222  // If this is not INVALID_ENTITY, the unit is a formation member.
224 
225  // If the entity moves, it will do so at m_WalkSpeed * m_SpeedMultiplier.
227  // This caches the resulting speed from m_WalkSpeed * m_SpeedMultiplier for convenience.
229 
230  // Mean speed over the last turn.
232 
233  // The speed achieved at the end of the current turn.
235 
237 
239 
240  // Currently active paths (storing waypoints in reverse order).
241  // The last item in each path is the point we're currently heading towards.
244 
245  static std::string GetSchema()
246  {
247  return
248  "<a:help>Provides the unit with the ability to move around the world by itself.</a:help>"
249  "<a:example>"
250  "<WalkSpeed>7.0</WalkSpeed>"
251  "<PassabilityClass>default</PassabilityClass>"
252  "</a:example>"
253  "<element name='FormationController'>"
254  "<data type='boolean'/>"
255  "</element>"
256  "<element name='WalkSpeed' a:help='Basic movement speed (in metres per second).'>"
257  "<ref name='positiveDecimal'/>"
258  "</element>"
259  "<optional>"
260  "<element name='RunMultiplier' a:help='How much faster the unit goes when running (as a multiple of walk speed).'>"
261  "<ref name='positiveDecimal'/>"
262  "</element>"
263  "</optional>"
264  "<element name='InstantTurnAngle' a:help='Angle we can turn instantly. Any value greater than pi will disable turning times. Avoid zero since it stops the entity every turn.'>"
265  "<ref name='positiveDecimal'/>"
266  "</element>"
267  "<element name='Acceleration' a:help='Acceleration (in metres per second^2).'>"
268  "<ref name='positiveDecimal'/>"
269  "</element>"
270  "<element name='PassabilityClass' a:help='Identifies the terrain passability class (values are defined in special/pathfinder.xml).'>"
271  "<text/>"
272  "</element>"
273  "<element name='Weight' a:help='Makes this unit both push harder and harder to push. 10 is considered the base value.'>"
274  "<ref name='positiveDecimal'/>"
275  "</element>"
276  "<optional>"
277  "<element name='DisablePushing'>"
278  "<data type='boolean'/>"
279  "</element>"
280  "</optional>";
281  }
282 
283  void Init(const CParamNode& paramNode) override
284  {
285  m_IsFormationController = paramNode.GetChild("FormationController").ToBool();
286 
287  m_FacePointAfterMove = true;
288 
289  m_WalkSpeed = m_TemplateWalkSpeed = m_Speed = paramNode.GetChild("WalkSpeed").ToFixed();
290  m_SpeedMultiplier = fixed::FromInt(1);
291  m_LastTurnSpeed = m_CurrentSpeed = fixed::Zero();
292 
294  if (paramNode.GetChild("RunMultiplier").IsOk())
295  m_RunMultiplier = m_TemplateRunMultiplier = paramNode.GetChild("RunMultiplier").ToFixed();
296 
297  m_InstantTurnAngle = paramNode.GetChild("InstantTurnAngle").ToFixed();
298 
299  m_Acceleration = m_TemplateAcceleration = paramNode.GetChild("Acceleration").ToFixed();
300 
301  m_TemplateWeight = paramNode.GetChild("Weight").ToFixed();
302 
303  m_PassClassName = paramNode.GetChild("PassabilityClass").ToString();
305 
306  CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
307  if (cmpObstruction)
308  m_BlockMovement = cmpObstruction->GetBlockMovementFlag(true);
309 
310  SetParticipateInPushing(!paramNode.GetChild("DisablePushing").IsOk() || !paramNode.GetChild("DisablePushing").ToBool());
311 
312  m_DebugOverlayEnabled = false;
313  }
314 
315  void Deinit() override
316  {
317  }
318 
319  template<typename S>
320  void SerializeCommon(S& serialize)
321  {
322  // m_Clearance and m_PassClass are constructed from this.
323  serialize.StringASCII("pass class", m_PassClassName, 0, 64);
324 
325  serialize.NumberU32_Unbounded("ticket", m_ExpectedPathTicket.m_Ticket);
326  Serializer(serialize, "ticket type", m_ExpectedPathTicket.m_Type, Ticket::Type::LONG_PATH);
327 
328  serialize.NumberU8_Unbounded("failed movements", m_FailedMovements);
329  serialize.NumberU8_Unbounded("followknownimperfectpath", m_FollowKnownImperfectPathCountdown);
330 
331  Serializer(serialize, "target type", m_MoveRequest.m_Type, MoveRequest::Type::OFFSET);
332  serialize.NumberU32_Unbounded("target entity", m_MoveRequest.m_Entity);
333  serialize.NumberFixed_Unbounded("target pos x", m_MoveRequest.m_Position.X);
334  serialize.NumberFixed_Unbounded("target pos y", m_MoveRequest.m_Position.Y);
335  serialize.NumberFixed_Unbounded("target min range", m_MoveRequest.m_MinRange);
336  serialize.NumberFixed_Unbounded("target max range", m_MoveRequest.m_MaxRange);
337 
338  serialize.NumberU32_Unbounded("formation controller", m_FormationController);
339 
340  serialize.NumberFixed_Unbounded("speed multiplier", m_SpeedMultiplier);
341 
342  serialize.NumberFixed_Unbounded("last turn speed", m_LastTurnSpeed);
343  serialize.NumberFixed_Unbounded("current speed", m_CurrentSpeed);
344 
345  serialize.NumberFixed_Unbounded("instant turn angle", m_InstantTurnAngle);
346 
347  serialize.NumberFixed_Unbounded("acceleration", m_Acceleration);
348 
349  serialize.Bool("facePointAfterMove", m_FacePointAfterMove);
350  serialize.Bool("pushing", m_Pushing);
351 
352  Serializer(serialize, "long path", m_LongPath.m_Waypoints);
353  Serializer(serialize, "short path", m_ShortPath.m_Waypoints);
354  }
355 
356  void Serialize(ISerializer& serialize) override
357  {
358  SerializeCommon(serialize);
359  }
360 
361  void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) override
362  {
363  Init(paramNode);
364 
365  SerializeCommon(deserialize);
366 
368 
369  CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
370  if (cmpObstruction)
371  m_BlockMovement = cmpObstruction->GetBlockMovementFlag(false);
372  }
373 
374  void HandleMessage(const CMessage& msg, bool UNUSED(global)) override
375  {
376  switch (msg.GetType())
377  {
378  case MT_RenderSubmit:
379  {
380  PROFILE("UnitMotion::RenderSubmit");
381  const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
382  RenderSubmit(msgData.collector);
383  break;
384  }
385  case MT_PathResult:
386  {
387  const CMessagePathResult& msgData = static_cast<const CMessagePathResult&> (msg);
388  PathResult(msgData.ticket, msgData.path);
389  break;
390  }
391  case MT_Create:
392  {
395  break;
396  }
397  case MT_Destroy:
398  {
401  break;
402  }
404  {
405  CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
406  if (cmpObstruction)
407  m_BlockMovement = cmpObstruction->GetBlockMovementFlag(false);
408  break;
409  }
411  {
412  const CMessageValueModification& msgData = static_cast<const CMessageValueModification&> (msg);
413  if (msgData.component != L"UnitMotion")
414  break;
415  FALLTHROUGH;
416  }
417  case MT_OwnershipChanged:
418  {
420  break;
421  }
422  case MT_Deserialized:
423  {
425  break;
426  }
427  }
428  }
429 
431  {
432  bool needRender = m_DebugOverlayEnabled;
434  }
435 
436  bool IsMoveRequested() const override
437  {
439  }
440 
441  fixed GetSpeedMultiplier() const override
442  {
443  return m_SpeedMultiplier;
444  }
445 
446  void SetSpeedMultiplier(fixed multiplier) override
447  {
448  m_SpeedMultiplier = std::min(multiplier, m_RunMultiplier);
449  m_Speed = m_SpeedMultiplier.Multiply(GetWalkSpeed());
450  }
451 
452  fixed GetSpeed() const override
453  {
454  return m_Speed;
455  }
456 
457  fixed GetWalkSpeed() const override
458  {
459  return m_WalkSpeed;
460  }
461 
462  fixed GetRunMultiplier() const override
463  {
464  return m_RunMultiplier;
465  }
466 
467  CFixedVector2D EstimateFuturePosition(const fixed dt) const override
468  {
469  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
470  if (!cmpPosition || !cmpPosition->IsInWorld())
471  return CFixedVector2D();
472 
473  // TODO: formation members should perhaps try to use the controller's position.
474 
475  CFixedVector2D pos = cmpPosition->GetPosition2D();
476  entity_angle_t angle = cmpPosition->GetRotation().Y;
477  fixed speed = m_CurrentSpeed;
478  // Copy the path so we don't change it.
479  WaypointPath shortPath = m_ShortPath;
480  WaypointPath longPath = m_LongPath;
481 
482  PerformMove(dt, cmpPosition->GetTurnRate(), shortPath, longPath, pos, speed, angle, 0);
483  return pos;
484  }
485 
486  fixed GetAcceleration() const override
487  {
488  return m_Acceleration;
489  }
490 
491  void SetAcceleration(fixed acceleration) override
492  {
493  m_Acceleration = acceleration;
494  }
495 
496  virtual entity_pos_t GetWeight() const
497  {
498  return m_TemplateWeight;
499  }
500 
502  {
503  return m_PassClass;
504  }
505 
506  std::string GetPassabilityClassName() const override
507  {
508  return m_PassClassName;
509  }
510 
511  void SetPassabilityClassName(const std::string& passClassName) override
512  {
514  {
515  LOGWARNING("Only formation controllers can change their passability class");
516  return;
517  }
518  SetPassabilityData(passClassName);
519  }
520 
521  fixed GetCurrentSpeed() const override
522  {
523  return m_CurrentSpeed;
524  }
525 
526  void SetFacePointAfterMove(bool facePointAfterMove) override
527  {
528  m_FacePointAfterMove = facePointAfterMove;
529  }
530 
531  bool GetFacePointAfterMove() const override
532  {
533  return m_FacePointAfterMove;
534  }
535 
536  void SetDebugOverlay(bool enabled) override
537  {
538  m_DebugOverlayEnabled = enabled;
540  }
541 
542  bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) override
543  {
544  return MoveTo(MoveRequest(CFixedVector2D(x, z), minRange, maxRange));
545  }
546 
547  bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) override
548  {
549  return MoveTo(MoveRequest(target, minRange, maxRange));
550  }
551 
553  {
554  // Pass the controller to the move request anyways.
555  MoveTo(MoveRequest(controller, CFixedVector2D(x, z)));
556  }
557 
558  void SetMemberOfFormation(entity_id_t controller) override
559  {
560  m_FormationController = controller;
561  }
562 
563  bool IsTargetRangeReachable(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) override;
564 
565  void FaceTowardsPoint(entity_pos_t x, entity_pos_t z) override;
566 
567  /**
568  * Clears the current MoveRequest - the unit will stop and no longer try and move.
569  * This should never be called from UnitMotion, since MoveToX orders are given
570  * by other components - these components should also decide when to stop.
571  */
572  void StopMoving() override
573  {
575  {
576  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
577  if (cmpPosition && cmpPosition->IsInWorld())
578  {
579  CFixedVector2D targetPos;
580  if (ComputeTargetPosition(targetPos))
581  FaceTowardsPointFromPos(cmpPosition->GetPosition2D(), targetPos.X, targetPos.Y);
582  }
583  }
584 
587  m_LongPath.m_Waypoints.clear();
588  m_ShortPath.m_Waypoints.clear();
589  }
590 
592  {
593  return m_Clearance;
594  }
595 
596 private:
597  bool IsFormationMember() const
598  {
599  return m_FormationController != INVALID_ENTITY;
600  }
601 
602  bool IsMovingAsFormation() const
603  {
605  }
606 
608  {
609  CmpPtr<ICmpUnitMotion> cmpControllerMotion(GetSimContext(), m_FormationController);
610  return cmpControllerMotion && cmpControllerMotion->IsMoveRequested();
611  }
612 
614  {
616  }
617 
618  void SetParticipateInPushing(bool pushing)
619  {
620  CmpPtr<ICmpUnitMotionManager> cmpUnitMotionManager(GetSystemEntity());
621  m_Pushing = pushing && cmpUnitMotionManager->IsPushingActivated();
622  }
623 
624  void SetPassabilityData(const std::string& passClassName)
625  {
626  m_PassClassName = passClassName;
627  CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
628  if (cmpPathfinder)
629  {
630  m_PassClass = cmpPathfinder->GetPassabilityClass(passClassName);
631  m_Clearance = cmpPathfinder->GetClearance(m_PassClass);
632 
633  CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
634  if (cmpObstruction)
635  cmpObstruction->SetUnitClearance(m_Clearance);
636  }
637  }
638 
639  /**
640  * Warns other components that our current movement will likely fail (e.g. we won't be able to reach our target)
641  * This should only be called before the actual movement in a given turn, or units might both move and try to do things
642  * on the same turn, leading to gliding units.
643  */
644  void MoveFailed()
645  {
646  // Don't notify if we are a formation member in a moving formation - we can occasionally be stuck for a long time
647  // if our current offset is unreachable, but we don't want to end up stuck.
648  // (If the formation controller has stopped moving however, we can safely message).
650  return;
651 
654  }
655 
656  /**
657  * Warns other components that our current movement is likely over (i.e. we probably reached our destination)
658  * This should only be called before the actual movement in a given turn, or units might both move and try to do things
659  * on the same turn, leading to gliding units.
660  */
662  {
663  // Don't notify if we are a formation member in a moving formation - we can occasionally be stuck for a long time
664  // if our current offset is unreachable, but we don't want to end up stuck.
665  // (If the formation controller has stopped moving however, we can safely message).
667  return;
668 
671  }
672 
673  /**
674  * Warns other components that our current movement was obstructed (i.e. we failed to move this turn).
675  * This should only be called before the actual movement in a given turn, or units might both move and try to do things
676  * on the same turn, leading to gliding units.
677  */
679  {
680  // Don't notify if we are a formation member in a moving formation - we can occasionally be stuck for a long time
681  // if our current offset is unreachable, but we don't want to end up stuck.
682  // (If the formation controller has stopped moving however, we can safely message).
684  return;
685 
689  }
690 
691  /**
692  * Increment the number of failed movements and notify other components if required.
693  * @returns true if the failure was notified, false otherwise.
694  */
696  {
699  {
700  MoveFailed();
701  m_FailedMovements = 0;
702  return true;
703  }
704  return false;
705  }
706 
707  /**
708  * If path would take us farther away from the goal than pos currently is, return false, else return true.
709  */
710  bool RejectFartherPaths(const PathGoal& goal, const WaypointPath& path, const CFixedVector2D& pos) const;
711 
713  {
715  }
716 
717  bool InShortPathRange(const PathGoal& goal, const CFixedVector2D& pos) const
718  {
719  return goal.DistanceToPoint(pos) < LONG_PATH_MIN_DIST;
720  }
721 
723  {
726  if (searchRange > SHORT_PATH_MAX_SEARCH_RANGE)
727  searchRange = SHORT_PATH_MAX_SEARCH_RANGE;
728  return searchRange;
729  }
730 
731  /**
732  * Handle the result of an asynchronous path query.
733  */
734  void PathResult(u32 ticket, const WaypointPath& path);
735 
737  {
738  CmpPtr<ICmpValueModificationManager> cmpValueModificationManager(GetSystemEntity());
739  if (!cmpValueModificationManager)
740  return;
741 
742  m_WalkSpeed = cmpValueModificationManager->ApplyModifications(L"UnitMotion/WalkSpeed", m_TemplateWalkSpeed, GetEntityId());
743  m_RunMultiplier = cmpValueModificationManager->ApplyModifications(L"UnitMotion/RunMultiplier", m_TemplateRunMultiplier, GetEntityId());
744 
745  // For MT_Deserialize compute m_Speed from the serialized m_SpeedMultiplier.
746  // For MT_ValueModification and MT_OwnershipChanged, adjust m_SpeedMultiplier if needed
747  // (in case then new m_RunMultiplier value is lower than the old).
748  SetSpeedMultiplier(m_SpeedMultiplier);
749  }
750 
751  /**
752  * Check if we are at destination early in the turn, this both lets units react faster
753  * and ensure that distance comparisons are done while units are not being moved
754  * (otherwise they won't be commutative).
755  */
756  void OnTurnStart();
757 
761 
762  /**
763  * Returns true if we are possibly at our destination.
764  * Since the concept of being at destination is dependent on why the move was requested,
765  * UnitMotion can only ever hint about this, hence the conditional tone.
766  */
767  bool PossiblyAtDestination() const;
768 
769  /**
770  * Process the move the unit will do this turn.
771  * This does not send actually change the position.
772  * @returns true if the move was obstructed.
773  */
774  bool PerformMove(fixed dt, const fixed& turnRate, WaypointPath& shortPath, WaypointPath& longPath, CFixedVector2D& pos, fixed& speed, entity_angle_t& angle, uint8_t pushingPressure) const;
775 
776  /**
777  * Update other components on our speed.
778  * (For performance, this should try to avoid sending messages).
779  */
780  void UpdateMovementState(entity_pos_t speed, entity_pos_t meanSpeed);
781 
782  /**
783  * React if our move was obstructed.
784  * @param moved - true if the unit still managed to move.
785  * @returns true if the obstruction required handling, false otherwise.
786  */
787  bool HandleObstructedMove(bool moved);
788 
789  /**
790  * Returns true if the target position is valid. False otherwise.
791  * (this may indicate that the target is e.g. out of the world/dead).
792  * NB: for code-writing convenience, if we have no target, this returns true.
793  */
794  bool TargetHasValidPosition(const MoveRequest& moveRequest) const;
796  {
798  }
799 
800  /**
801  * Computes the current location of our target entity (plus offset).
802  * Returns false if no target entity or no valid position.
803  */
804  bool ComputeTargetPosition(CFixedVector2D& out, const MoveRequest& moveRequest) const;
806  {
808  }
809 
810  /**
811  * Attempts to replace the current path with a straight line to the target,
812  * if it's close enough and the route is not obstructed.
813  */
814  bool TryGoingStraightToTarget(const CFixedVector2D& from, bool updatePaths);
815 
816  /**
817  * Returns whether our we need to recompute a path to reach our target.
818  */
819  bool PathingUpdateNeeded(const CFixedVector2D& from) const;
820 
821  /**
822  * Rotate to face towards the target point, given the current pos
823  */
825 
826  /**
827  * Units in 'pushing' mode are marked as 'moving' in the obstruction manager.
828  * Units in 'pushing' mode should skip them in checkMovement (to enable pushing).
829  * However, units for which pushing is deactivated should collide against everyone.
830  * Units that don't block movement never participate in pushing, but they also
831  * shouldn't collide with pushing units.
832  */
834  {
835  return !m_Pushing && m_BlockMovement;
836  }
837 
838  /**
839  * Returns an appropriate obstruction filter for use with path requests.
840  */
842  {
844  }
845  /**
846  * Filter a specific tag on top of the existing control groups.
847  */
849  {
851  }
852 
853  /**
854  * Decide whether to approximate the given range from a square target as a circle,
855  * rather than as a square.
856  */
857  bool ShouldTreatTargetAsCircle(entity_pos_t range, entity_pos_t circleRadius) const;
858 
859  /**
860  * Create a PathGoal from a move request.
861  * @returns true if the goal was successfully created.
862  */
863  bool ComputeGoal(PathGoal& out, const MoveRequest& moveRequest) const;
864 
865  /**
866  * Compute a path to the given goal from the given position.
867  * Might go in a straight line immediately, or might start an asynchronous path request.
868  */
869  void ComputePathToGoal(const CFixedVector2D& from, const PathGoal& goal);
870 
871  /**
872  * Start an asynchronous long path query.
873  */
874  void RequestLongPath(const CFixedVector2D& from, const PathGoal& goal);
875 
876  /**
877  * Start an asynchronous short path query.
878  * @param extendRange - if true, extend the search range to at least the distance to the goal.
879  */
880  void RequestShortPath(const CFixedVector2D& from, const PathGoal& goal, bool extendRange);
881 
882  /**
883  * General handler for MoveTo interface functions.
884  */
885  bool MoveTo(MoveRequest request);
886 
887  /**
888  * Convert a path into a renderable list of lines
889  */
890  void RenderPath(const WaypointPath& path, std::vector<SOverlayLine>& lines, CColor color);
891 
892  void RenderSubmit(SceneCollector& collector);
893 };
894 
895 REGISTER_COMPONENT_TYPE(UnitMotion)
896 
897 bool CCmpUnitMotion::RejectFartherPaths(const PathGoal& goal, const WaypointPath& path, const CFixedVector2D& pos) const
898 {
899  if (path.m_Waypoints.empty())
900  return false;
901 
902  // Reject the new path if it does not lead us closer to the target's position.
903  if (goal.DistanceToPoint(pos) <= goal.DistanceToPoint(CFixedVector2D(path.m_Waypoints.front().x, path.m_Waypoints.front().z)))
904  return true;
905 
906  return false;
907 }
908 
909 void CCmpUnitMotion::PathResult(u32 ticket, const WaypointPath& path)
910 {
911  // Ignore obsolete path requests
913  return;
914 
917 
918  // If we not longer have a position, we won't be able to do much.
919  // Fail in the next Move() call.
920  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
921  if (!cmpPosition || !cmpPosition->IsInWorld())
922  return;
923  CFixedVector2D pos = cmpPosition->GetPosition2D();
924 
925  // Assume all long paths were towards the goal, and assume short paths were if there are no long waypoints.
926  bool pathedTowardsGoal = ticketType == Ticket::LONG_PATH || m_LongPath.m_Waypoints.empty();
927 
928  // Check if we need to run the short-path hack (warning: tricky control flow).
929  bool shortPathHack = false;
930  if (path.m_Waypoints.empty())
931  {
932  // No waypoints means pathing failed. If this was a long-path, try the short-path hack.
933  if (!pathedTowardsGoal)
934  return;
935  shortPathHack = ticketType == Ticket::LONG_PATH;
936  }
937  else if (PathGoal goal; pathedTowardsGoal && ComputeGoal(goal, m_MoveRequest) && RejectFartherPaths(goal, path, pos))
938  {
939  // Reject paths that would take the unit further away from the goal.
940  // This assumes that we prefer being closer 'as the crow flies' to unreachable goals.
941  // This is a hack of sorts around units 'dancing' between two positions (see e.g. #3144),
942  // but never actually failing to move, ergo never actually informing unitAI that it succeeds/fails.
943  // (for short paths, only do so if aiming directly for the goal
944  // as sub-goals may be farther than we are).
945 
946  // If this was a long-path and we no longer have waypoints, try the short-path hack.
947  if (!m_LongPath.m_Waypoints.empty())
948  return;
949  shortPathHack = ticketType == Ticket::LONG_PATH;
950  }
951 
952  // Short-path hack: if the long-range pathfinder doesn't find an acceptable path, push a fake waypoint at the goal.
953  // This means HandleObstructedMove will use the short-pathfinder to try and reach it,
954  // and that may find a path as the vertex pathfinder is more precise.
955  if (shortPathHack)
956  {
957  // If we're resorting to the short-path hack, the situation is dire. Most likely, the goal is unreachable.
958  // We want to find a path or fail fast. Bump failed movements so the short pathfinder will run at max-range
959  // right away. This is safe from a performance PoV because it can only happen if the target is unreachable to
960  // the long-range pathfinder, which is rare, and since the entity will fail to move if the goal is actually unreachable,
961  // the failed movements will be increased to MAX anyways, so just shortcut.
963 
964  CFixedVector2D targetPos;
965  if (ComputeTargetPosition(targetPos))
966  m_LongPath.m_Waypoints.emplace_back(Waypoint{ targetPos.X, targetPos.Y });
967  return;
968  }
969 
970  if (ticketType == Ticket::LONG_PATH)
971  {
972  m_LongPath = path;
973  // Long paths don't properly follow diagonals because of JPS/the grid. Since units now take time turning,
974  // they can actually slow down substantially if they have to do a one navcell diagonal movement,
975  // which is somewhat common at the beginning of a new path.
976  // For that reason, if the first waypoint is really close, check if we can't go directly to the second.
977  if (m_LongPath.m_Waypoints.size() >= 2)
978  {
979  const Waypoint& firstWpt = m_LongPath.m_Waypoints.back();
980  if (CFixedVector2D(firstWpt.x - pos.X, firstWpt.z - pos.Y).CompareLength(Pathfinding::NAVCELL_SIZE * 4) <= 0)
981  {
982  CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
983  ENSURE(cmpPathfinder);
984  const Waypoint& secondWpt = m_LongPath.m_Waypoints[m_LongPath.m_Waypoints.size() - 2];
985  if (cmpPathfinder->CheckMovement(GetObstructionFilter(), pos.X, pos.Y, secondWpt.x, secondWpt.z, m_Clearance, m_PassClass))
986  m_LongPath.m_Waypoints.pop_back();
987  }
988 
989  }
990  }
991  else
992  m_ShortPath = path;
993 
995 
996  if (!pathedTowardsGoal)
997  return;
998 
999  // Performance hack: If we were pathing towards the goal and this new path won't put us in range,
1000  // it's highly likely that we are going somewhere unreachable.
1001  // However, Move() will try to recompute the path every turn, which can be quite slow.
1002  // To avoid this, act as if our current path leads us to the correct destination.
1003  // NB: for short-paths, the problem might be that the search space is too small
1004  // but we'll still follow this path until the en and try again then.
1005  // Because we reject farther paths, it works out.
1006  if (PathingUpdateNeeded(pos))
1007  {
1008  // Inform other components early, as they might have better behaviour than waiting for the path to carry out.
1009  // Send OBSTRUCTED at first - moveFailed is likely to trigger path recomputation and we might end up
1010  // recomputing too often for nothing.
1012  MoveObstructed();
1013  // We'll automatically recompute a path when this reaches 0, as a way to improve behaviour.
1014  // (See D665 - this is needed because the target may be moving, and we should adjust to that).
1016  }
1017 }
1018 
1020 {
1021  if (PossiblyAtDestination())
1022  MoveSucceeded();
1023  else if (!TargetHasValidPosition())
1024  {
1025  // Scrap waypoints - we don't know where to go.
1026  // If the move request remains unchanged and the target again has a valid position later on,
1027  // moving will be resumed.
1028  // Units may want to move to move to the target's last known position,
1029  // but that should be decided by UnitAI (handling MoveFailed), not UnitMotion.
1030  m_LongPath.m_Waypoints.clear();
1031  m_ShortPath.m_Waypoints.clear();
1032 
1033  MoveFailed();
1034  }
1035 }
1036 
1038 {
1039  state.ignore = !m_Pushing || !m_BlockMovement;
1040 
1041  state.wasObstructed = false;
1042  state.wentStraight = false;
1043 
1044  // If we were idle and will still be, no need for an update.
1045  state.needUpdate = state.cmpPosition->IsInWorld() &&
1047 
1048  if (!m_BlockMovement)
1049  return;
1050 
1052 
1053  // Update moving flag, this is an internal construct used for pushing,
1054  // so it does not really reflect whether the unit is actually moving or not.
1056  CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
1057  if (cmpObstruction)
1058  cmpObstruction->SetMovingFlag(state.isMoving);
1059 }
1060 
1062 {
1063  PROFILE("Move");
1064 
1065  // If we're chasing a potentially-moving unit and are currently close
1066  // enough to its current position, and we can head in a straight line
1067  // to it, then throw away our current path and go straight to it.
1068  state.wentStraight = TryGoingStraightToTarget(state.initialPos, true);
1069 
1070  state.wasObstructed = PerformMove(dt, state.cmpPosition->GetTurnRate(), m_ShortPath, m_LongPath, state.pos, state.speed, state.angle, state.pushingPressure);
1071 }
1072 
1074 {
1075  // Update our speed over this turn so that the visual actor shows the correct animation.
1076  if (state.pos == state.initialPos)
1077  {
1078  if (state.angle != state.initialAngle)
1079  state.cmpPosition->TurnTo(state.angle);
1081  }
1082  else
1083  {
1084  // Update the Position component after our movement (if we actually moved anywhere)
1085  CFixedVector2D offset = state.pos - state.initialPos;
1086  state.cmpPosition->MoveAndTurnTo(state.pos.X, state.pos.Y, state.angle);
1087 
1088  // Calculate the mean speed over this past turn.
1089  UpdateMovementState(state.speed, offset.Length() / dt);
1090  }
1091 
1092  if (state.wasObstructed && HandleObstructedMove(state.pos != state.initialPos))
1093  return;
1094  else if (!state.wasObstructed && state.pos != state.initialPos)
1095  m_FailedMovements = 0;
1096 
1097  // If we moved straight, and didn't quite finish the path, reset - we'll update it next turn if still OK.
1098  if (state.wentStraight && !state.wasObstructed)
1099  m_ShortPath.m_Waypoints.clear();
1100 
1101  // We may need to recompute our path sometimes (e.g. if our target moves).
1102  // Since we request paths asynchronously anyways, this does not need to be done before moving.
1103  if (!state.wentStraight && PathingUpdateNeeded(state.pos))
1104  {
1105  PathGoal goal;
1106  if (ComputeGoal(goal, m_MoveRequest))
1107  ComputePathToGoal(state.pos, goal);
1108  }
1111 }
1112 
1114 {
1116  return false;
1117 
1118  CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
1119  ENSURE(cmpObstructionManager);
1120 
1126  {
1128  if (cmpControllerMotion && cmpControllerMotion->IsMoveRequested())
1129  return false;
1130 
1131  // In formation, return a match only if we are exactly at the target position.
1132  // Otherwise, units can go in an infinite "walzting" loop when the Idle formation timer
1133  // reforms them.
1134  CFixedVector2D targetPos;
1135  ComputeTargetPosition(targetPos);
1136  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
1137  return (targetPos-cmpPosition->GetPosition2D()).CompareLength(fixed::Zero()) <= 0;
1138  }
1139  return false;
1140 }
1141 
1142 bool CCmpUnitMotion::PerformMove(fixed dt, const fixed& turnRate, WaypointPath& shortPath, WaypointPath& longPath, CFixedVector2D& pos, fixed& speed, entity_angle_t& angle, uint8_t pushingPressure) const
1143 {
1144  // If there are no waypoint, behave as though we were obstructed and let HandleObstructedMove handle it.
1145  if (shortPath.m_Waypoints.empty() && longPath.m_Waypoints.empty())
1146  return true;
1147 
1148  // Wrap the angle to (-Pi, Pi].
1149  while (angle > entity_angle_t::Pi())
1150  angle -= entity_angle_t::Pi() * 2;
1151  while (angle < -entity_angle_t::Pi())
1152  angle += entity_angle_t::Pi() * 2;
1153 
1154  CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
1155  ENSURE(cmpPathfinder);
1156 
1157  fixed basicSpeed = m_Speed;
1158  // If in formation, run to keep up; otherwise just walk.
1159  if (IsMovingAsFormation())
1160  basicSpeed = m_Speed.Multiply(m_RunMultiplier);
1161 
1162  // If pushing pressure is applied, slow the unit down.
1163  if (pushingPressure)
1164  {
1165  // Values below this pressure don't slow the unit down (avoids slowing groups down).
1166  constexpr int pressureMinThreshold = 10;
1167 
1168  // Lower speed up to a floor to prevent units from getting stopped.
1169  // This helped pushing particularly for fast units, since they'll end up slowing down.
1170  constexpr int maxPressure = CCmpUnitMotionManager::MAX_PRESSURE - pressureMinThreshold - 80;
1171  constexpr entity_pos_t floorSpeed = entity_pos_t::FromFraction(3, 2);
1172  static_assert(maxPressure > 0);
1173 
1174  uint8_t slowdown = maxPressure - std::min(maxPressure, std::max(0, pushingPressure - pressureMinThreshold));
1175  basicSpeed = basicSpeed.Multiply(fixed::FromInt(slowdown) / maxPressure);
1176  // NB: lowering this too much will make the units behave a lot like viscous fluid
1177  // when the density becomes extreme. While perhaps realistic (and kind of neat),
1178  // it's not very helpful for gameplay. Empirically, a value of 1.5 avoids most of the effect
1179  // while still slowing down movement significantly, and seems like a good balance.
1180  // Min with the template speed to allow units that are explicitly absurdly slow.
1181  basicSpeed = std::max(std::min(m_TemplateWalkSpeed, floorSpeed), basicSpeed);
1182  }
1183 
1184  // TODO: would be nice to support terrain-dependent speed again.
1185  fixed maxSpeed = basicSpeed;
1186 
1187  fixed timeLeft = dt;
1188  fixed zero = fixed::Zero();
1189 
1190  ICmpObstructionManager::tag_t specificIgnore;
1192  {
1194  if (cmpTargetObstruction)
1195  specificIgnore = cmpTargetObstruction->GetObstruction();
1196  }
1197 
1198  while (timeLeft > zero)
1199  {
1200  // If we ran out of path, we have to stop.
1201  if (shortPath.m_Waypoints.empty() && longPath.m_Waypoints.empty())
1202  break;
1203 
1204  CFixedVector2D target;
1205  if (shortPath.m_Waypoints.empty())
1206  target = CFixedVector2D(longPath.m_Waypoints.back().x, longPath.m_Waypoints.back().z);
1207  else
1208  target = CFixedVector2D(shortPath.m_Waypoints.back().x, shortPath.m_Waypoints.back().z);
1209 
1210  CFixedVector2D offset = target - pos;
1211 
1212  if (turnRate > zero && !offset.IsZero())
1213  {
1214  fixed angleDiff = angle - atan2_approx(offset.X, offset.Y);
1215  fixed absoluteAngleDiff = angleDiff.Absolute();
1216  if (absoluteAngleDiff > entity_angle_t::Pi())
1217  absoluteAngleDiff = entity_angle_t::Pi() * 2 - absoluteAngleDiff;
1218 
1219  // We only rotate to the instantTurnAngle angle. The rest we rotate during movement.
1220  if (absoluteAngleDiff > m_InstantTurnAngle)
1221  {
1222  // Stop moving when rotating this far.
1223  speed = zero;
1224 
1225  fixed maxRotation = turnRate.Multiply(timeLeft);
1226 
1227  // Figure out whether rotating will increase or decrease the angle, and how far we need to rotate in that direction.
1228  int direction = (entity_angle_t::Zero() < angleDiff && angleDiff <= entity_angle_t::Pi()) || angleDiff < -entity_angle_t::Pi() ? -1 : 1;
1229 
1230  // Can't rotate far enough, just rotate in the correct direction.
1231  if (absoluteAngleDiff - m_InstantTurnAngle > maxRotation)
1232  {
1233  angle += maxRotation * direction;
1234  if (angle * direction > entity_angle_t::Pi())
1235  angle -= entity_angle_t::Pi() * 2 * direction;
1236  break;
1237  }
1238  // Rotate towards the next waypoint and continue moving.
1239  angle = atan2_approx(offset.X, offset.Y);
1240  timeLeft = std::min(maxRotation, maxRotation - absoluteAngleDiff + m_InstantTurnAngle) / turnRate;
1241  }
1242  else
1243  {
1244  // Modify the speed depending on the angle difference.
1245  fixed sin, cos;
1246  sincos_approx(angleDiff, sin, cos);
1247  speed = speed.Multiply(cos);
1248  angle = atan2_approx(offset.X, offset.Y);
1249  }
1250  }
1251 
1252  // Work out how far we can travel in timeLeft.
1253  fixed accelTime = std::min(timeLeft, (maxSpeed - speed) / m_Acceleration);
1254  fixed accelDist = speed.Multiply(accelTime) + accelTime.Square().Multiply(m_Acceleration) / 2;
1255  fixed maxdist = accelDist + maxSpeed.Multiply(timeLeft - accelTime);
1256 
1257  // If the target is close, we can move there directly.
1258  fixed offsetLength = offset.Length();
1259  if (offsetLength <= maxdist)
1260  {
1261  if (cmpPathfinder->CheckMovement(GetObstructionFilter(specificIgnore), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass))
1262  {
1263  pos = target;
1264 
1265  // Spend the rest of the time heading towards the next waypoint.
1266  // Either we still need to accelerate after, or we have reached maxSpeed.
1267  // The former is much less likely than the latter: usually we can reach
1268  // maxSpeed within one waypoint. So the Sqrt is not too bad.
1269  if (offsetLength <= accelDist)
1270  {
1271  fixed requiredTime = (-speed + (speed.Square() + offsetLength.Multiply(m_Acceleration).Multiply(fixed::FromInt(2))).Sqrt()) / m_Acceleration;
1272  timeLeft -= requiredTime;
1273  speed += m_Acceleration.Multiply(requiredTime);
1274  }
1275  else
1276  {
1277  timeLeft -= accelTime + (offsetLength - accelDist) / maxSpeed;
1278  speed = maxSpeed;
1279  }
1280 
1281  if (shortPath.m_Waypoints.empty())
1282  longPath.m_Waypoints.pop_back();
1283  else
1284  shortPath.m_Waypoints.pop_back();
1285 
1286  continue;
1287  }
1288  else
1289  {
1290  // Error - path was obstructed.
1291  return true;
1292  }
1293  }
1294  else
1295  {
1296  // Not close enough, so just move in the right direction.
1297  offset.Normalize(maxdist);
1298  target = pos + offset;
1299 
1300  speed = std::min(maxSpeed, speed + m_Acceleration.Multiply(timeLeft));
1301 
1302  if (cmpPathfinder->CheckMovement(GetObstructionFilter(specificIgnore), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass))
1303  pos = target;
1304  else
1305  return true;
1306 
1307  break;
1308  }
1309  }
1310  return false;
1311 }
1312 
1314 {
1315  CmpPtr<ICmpVisual> cmpVisual(GetEntityHandle());
1316  if (cmpVisual)
1317  {
1318  if (meanSpeed == fixed::Zero())
1319  cmpVisual->SelectMovementAnimation("idle", fixed::FromInt(1));
1320  else
1321  cmpVisual->SelectMovementAnimation(meanSpeed > (m_WalkSpeed / 2).Multiply(m_RunMultiplier + fixed::FromInt(1)) ? "run" : "walk", meanSpeed);
1322  }
1323 
1324  m_LastTurnSpeed = meanSpeed;
1325  m_CurrentSpeed = speed;
1326 }
1327 
1329 {
1330  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
1331  if (!cmpPosition || !cmpPosition->IsInWorld())
1332  return false;
1333 
1334  // We failed to move, inform other components as they might handle it.
1335  // (don't send messages on the first failure, as that would be too noisy).
1336  // Also don't increment above the initial MoveObstructed message if we actually manage to move a little.
1337  if (!moved || m_FailedMovements < 2)
1338  {
1340  MoveObstructed();
1341  }
1342 
1343  PathGoal goal;
1344  if (!ComputeGoal(goal, m_MoveRequest))
1345  return false;
1346 
1347  // At this point we have a position in the world since ComputeGoal checked for that.
1348  CFixedVector2D pos = cmpPosition->GetPosition2D();
1349 
1350  // Assume that we are merely obstructed and the long path is salvageable, so try going around the obstruction.
1351  // This could be a separate function, but it doesn't really make sense to call it outside of here, and I can't find a name.
1352  // I use an IIFE to have nice 'return' semantics still.
1353  if ([&]() -> bool {
1354  // If the goal is close enough, we should ignore any remaining long waypoint and just
1355  // short path there directly, as that improves behaviour in general - see D2095).
1356  if (InShortPathRange(goal, pos))
1357  return false;
1358 
1359  // On rare occasions, when following a short path, we can end up in a position where
1360  // the short pathfinder thinks we are inside an obstruction (and can leave)
1361  // but the CheckMovement logic doesn't. I believe the cause is a small numerical difference
1362  // in their calculation, but haven't been able to pinpoint it precisely.
1363  // In those cases, the solution is to back away to prevent the short-pathfinder from being confused.
1364  // TODO: this should only be done if we're obstructed by a static entity.
1366  {
1367  Waypoint next = m_ShortPath.m_Waypoints.back();
1368  CFixedVector2D backUp(pos.X - next.x, pos.Y - next.z);
1369  backUp.Normalize();
1370  next.x = pos.X + backUp.X;
1371  next.z = pos.Y + backUp.Y;
1372  m_ShortPath.m_Waypoints.push_back(next);
1373  return true;
1374  }
1375  // Delete the next waypoint if it's reasonably close,
1376  // because it might be blocked by units and thus unreachable.
1377  // NB: this number is tricky. Make it too high, and units start going down dead ends, which looks odd (#5795)
1378  // Make it too low, and they might get stuck behind other obstructed entities.
1379  // It also has performance implications because it calls the short-pathfinder.
1380  fixed skipbeyond = std::max(ShortPathSearchRange() / 3, Pathfinding::NAVCELL_SIZE * 8);
1381  if (m_LongPath.m_Waypoints.size() > 1 &&
1382  (pos - CFixedVector2D(m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z)).CompareLength(skipbeyond) < 0)
1383  {
1384  m_LongPath.m_Waypoints.pop_back();
1385  }
1386  else if (ShouldAlternatePathfinder())
1387  {
1388  // Recompute the whole thing occasionally, in case we got stuck in a dead end from removing long waypoints.
1389  RequestLongPath(pos, goal);
1390  return true;
1391  }
1392 
1393  if (m_LongPath.m_Waypoints.empty())
1394  return false;
1395 
1396  // Compute a short path in the general vicinity of the next waypoint, to help pathfinding in crowds.
1397  // The goal here is to manage to move in the general direction of our target, not to be super accurate.
1398  fixed radius = Clamp(skipbeyond/3, Pathfinding::NAVCELL_SIZE * 4, Pathfinding::NAVCELL_SIZE * 12);
1399  PathGoal subgoal = { PathGoal::CIRCLE, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z, radius };
1400  RequestShortPath(pos, subgoal, false);
1401  return true;
1402  }()) return true;
1403 
1404  // If we couldn't use a workaround, try recomputing the entire path.
1405  ComputePathToGoal(pos, goal);
1406 
1407  return true;
1408 }
1409 
1411 {
1412  if (moveRequest.m_Type != MoveRequest::ENTITY)
1413  return true;
1414 
1415  CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), moveRequest.m_Entity);
1416  return cmpPosition && cmpPosition->IsInWorld();
1417 }
1418 
1420 {
1421  if (moveRequest.m_Type == MoveRequest::POINT)
1422  {
1423  out = moveRequest.m_Position;
1424  return true;
1425  }
1426 
1427  CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), moveRequest.m_Entity);
1428  if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
1429  return false;
1430 
1431  if (moveRequest.m_Type == MoveRequest::OFFSET)
1432  {
1433  // There is an offset, so compute it relative to orientation
1434  entity_angle_t angle = cmpTargetPosition->GetRotation().Y;
1435  CFixedVector2D offset = moveRequest.GetOffset().Rotate(angle);
1436  out = cmpTargetPosition->GetPosition2D() + offset;
1437  }
1438  else
1439  {
1440  out = cmpTargetPosition->GetPosition2D();
1441  // Position is only updated after all units have moved & pushed.
1442  // Therefore, we may need to interpolate the target position, depending on when this call takes place during the turn:
1443  // - On "Turn Start", we'll check positions directly without interpolation.
1444  // - During movement, we'll call this for direct-pathing & we need to interpolate
1445  // (this way, we move where the unit will end up at the end of _this_ turn, making it match on next turn start).
1446  // - After movement, we'll call this to request paths & we need to interpolate
1447  // (this way, we'll move where the unit ends up in the end of _next_ turn, making it a match in 2 turns).
1448  // TODO: This does not really aim many turns in advance, with orthogonal trajectories it probably should.
1449  CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), moveRequest.m_Entity);
1450  CmpPtr<ICmpUnitMotionManager> cmpUnitMotionManager(GetSystemEntity());
1451  bool needInterpolation = cmpUnitMotion && cmpUnitMotion->IsMoveRequested() && cmpUnitMotionManager->ComputingMotion();
1452  if (needInterpolation)
1453  {
1454  // Add predicted movement.
1455  CFixedVector2D tempPos = out + (out - cmpTargetPosition->GetPreviousPosition2D());
1456  out = tempPos;
1457  }
1458  }
1459  return true;
1460 }
1461 
1463 {
1464  // Assume if we have short paths we want to follow them.
1465  // Exception: offset movement (formations) generally have very short deltas
1466  // and to look good we need them to walk-straight most of the time.
1467  if (!IsFormationMember() && !m_ShortPath.m_Waypoints.empty())
1468  return false;
1469 
1470  CFixedVector2D targetPos;
1471  if (!ComputeTargetPosition(targetPos))
1472  return false;
1473 
1474  CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
1475  if (!cmpPathfinder)
1476  return false;
1477 
1478  // Move the goal to match the target entity's new position
1479  PathGoal goal;
1480  if (!ComputeGoal(goal, m_MoveRequest))
1481  return false;
1482  goal.x = targetPos.X;
1483  goal.z = targetPos.Y;
1484  // (we ignore changes to the target's rotation, since only buildings are
1485  // square and buildings don't move)
1486 
1487  // Find the point on the goal shape that we should head towards
1488  CFixedVector2D goalPos = goal.NearestPointOnGoal(from);
1489 
1490  // Fail if the target is too far away
1491  if ((goalPos - from).CompareLength(DIRECT_PATH_RANGE) > 0)
1492  return false;
1493 
1494  // Check if there's any collisions on that route.
1495  // For entity goals, skip only the specific obstruction tag or with e.g. walls we might ignore too many entities.
1496  ICmpObstructionManager::tag_t specificIgnore;
1498  {
1500  if (cmpTargetObstruction)
1501  specificIgnore = cmpTargetObstruction->GetObstruction();
1502  }
1503 
1504  // Check movement against units - we want to use the short pathfinder to walk around those if needed.
1505  if (specificIgnore.valid())
1506  {
1507  if (!cmpPathfinder->CheckMovement(GetObstructionFilter(specificIgnore), from.X, from.Y, goalPos.X, goalPos.Y, m_Clearance, m_PassClass))
1508  return false;
1509  }
1510  else if (!cmpPathfinder->CheckMovement(GetObstructionFilter(), from.X, from.Y, goalPos.X, goalPos.Y, m_Clearance, m_PassClass))
1511  return false;
1512 
1513  if (!updatePaths)
1514  return true;
1515 
1516  // That route is okay, so update our path
1517  m_LongPath.m_Waypoints.clear();
1518  m_ShortPath.m_Waypoints.clear();
1519  m_ShortPath.m_Waypoints.emplace_back(Waypoint{ goalPos.X, goalPos.Y });
1520  return true;
1521 }
1522 
1524 {
1526  return false;
1527 
1528  CFixedVector2D targetPos;
1529  if (!ComputeTargetPosition(targetPos))
1530  return false;
1531 
1533  return false;
1534 
1535  if (PossiblyAtDestination())
1536  return false;
1537 
1538  // Get the obstruction shape and translate it where we estimate the target to be.
1539  ICmpObstructionManager::ObstructionSquare estimatedTargetShape;
1541  {
1543  if (cmpTargetObstruction)
1544  cmpTargetObstruction->GetObstructionSquare(estimatedTargetShape);
1545  }
1546 
1547  estimatedTargetShape.x = targetPos.X;
1548  estimatedTargetShape.z = targetPos.Y;
1549 
1550  CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
1552  if (cmpObstruction)
1553  cmpObstruction->GetObstructionSquare(shape);
1554 
1555  // Translate our own obstruction shape to our last waypoint or our current position, lacking that.
1556  if (m_LongPath.m_Waypoints.empty() && m_ShortPath.m_Waypoints.empty())
1557  {
1558  shape.x = from.X;
1559  shape.z = from.Y;
1560  }
1561  else
1562  {
1563  const Waypoint& lastWaypoint = m_LongPath.m_Waypoints.empty() ? m_ShortPath.m_Waypoints.front() : m_LongPath.m_Waypoints.front();
1564  shape.x = lastWaypoint.x;
1565  shape.z = lastWaypoint.z;
1566  }
1567 
1568  CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
1569  ENSURE(cmpObstructionManager);
1570 
1571  // Increase the ranges with distance, to avoid recomputing every turn against units that are moving and far-away for example.
1572  entity_pos_t distance = (from - CFixedVector2D(estimatedTargetShape.x, estimatedTargetShape.z)).Length();
1573 
1574  // TODO: it could be worth computing this based on time to collision instead of linear distance.
1578 
1579  if (cmpObstructionManager->AreShapesInRange(shape, estimatedTargetShape, minRange, maxRange, false))
1580  return false;
1581 
1582  return true;
1583 }
1584 
1586 {
1587  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
1588  if (!cmpPosition || !cmpPosition->IsInWorld())
1589  return;
1590 
1591  CFixedVector2D pos = cmpPosition->GetPosition2D();
1592  FaceTowardsPointFromPos(pos, x, z);
1593 }
1594 
1596 {
1597  CFixedVector2D target(x, z);
1598  CFixedVector2D offset = target - pos;
1599  if (!offset.IsZero())
1600  {
1601  entity_angle_t angle = atan2_approx(offset.X, offset.Y);
1602 
1603  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
1604  if (!cmpPosition)
1605  return;
1606  cmpPosition->TurnTo(angle);
1607  }
1608 }
1609 
1610 // The pathfinder cannot go to "rounded rectangles" goals, which are what happens with square targets and a non-null range.
1611 // Depending on what the best approximation is, we either pretend the target is a circle or a square.
1612 // One needs to be careful that the approximated geometry will be in the range.
1614 {
1615  // Given a square, plus a target range we should reach, the shape at that distance
1616  // is a round-cornered square which we can approximate as either a circle or as a square.
1617  // Previously, we used the shape that minimized the worst-case error.
1618  // However that is unsage in some situations. So let's be less clever and
1619  // just check if our range is at least three times bigger than the circleradius
1620  return (range > circleRadius*3);
1621 }
1622 
1623 bool CCmpUnitMotion::ComputeGoal(PathGoal& out, const MoveRequest& moveRequest) const
1624 {
1625  if (moveRequest.m_Type == MoveRequest::NONE)
1626  return false;
1627 
1628  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
1629  if (!cmpPosition || !cmpPosition->IsInWorld())
1630  return false;
1631 
1632  CFixedVector2D pos = cmpPosition->GetPosition2D();
1633 
1634  CFixedVector2D targetPosition;
1635  if (!ComputeTargetPosition(targetPosition, moveRequest))
1636  return false;
1637 
1639  if (moveRequest.m_Type == MoveRequest::ENTITY)
1640  {
1641  CmpPtr<ICmpObstruction> cmpTargetObstruction(GetSimContext(), moveRequest.m_Entity);
1642  if (cmpTargetObstruction)
1643  cmpTargetObstruction->GetObstructionSquare(targetObstruction);
1644  }
1645  targetObstruction.x = targetPosition.X;
1646  targetObstruction.z = targetPosition.Y;
1647 
1649  CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
1650  if (cmpObstruction)
1651  cmpObstruction->GetObstructionSquare(obstruction);
1652  else
1653  {
1654  obstruction.x = pos.X;
1655  obstruction.z = pos.Y;
1656  }
1657 
1658  CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
1659  ENSURE(cmpObstructionManager);
1660 
1661  entity_pos_t distance = cmpObstructionManager->DistanceBetweenShapes(obstruction, targetObstruction);
1662 
1663  out.x = targetObstruction.x;
1664  out.z = targetObstruction.z;
1665  out.hw = targetObstruction.hw;
1666  out.hh = targetObstruction.hh;
1667  out.u = targetObstruction.u;
1668  out.v = targetObstruction.v;
1669 
1670  if (moveRequest.m_MinRange > fixed::Zero() || moveRequest.m_MaxRange > fixed::Zero() ||
1671  targetObstruction.hw > fixed::Zero())
1672  out.type = PathGoal::SQUARE;
1673  else
1674  {
1675  out.type = PathGoal::POINT;
1676  return true;
1677  }
1678 
1679  entity_pos_t circleRadius = CFixedVector2D(targetObstruction.hw, targetObstruction.hh).Length();
1680 
1681  // TODO: because we cannot move to rounded rectangles, we have to make conservative approximations.
1682  // This means we might end up in a situation where cons(max-range) < min range < max range < cons(min-range)
1683  // When going outside of the min-range or inside the max-range, the unit will still go through the correct range
1684  // but if it moves fast enough, this might not be picked up by PossiblyAtDestination().
1685  // Fixing this involves moving to rounded rectangles, or checking more often in PerformMove().
1686  // In the meantime, one should avoid that 'Speed over a turn' > MaxRange - MinRange, in case where
1687  // min-range is not 0 and max-range is not infinity.
1688  if (distance < moveRequest.m_MinRange)
1689  {
1690  // Distance checks are nearest edge to nearest edge, so we need to account for our clearance
1691  // and we must make sure diagonals also fit so multiply by slightly more than sqrt(2)
1692  entity_pos_t goalDistance = moveRequest.m_MinRange + m_Clearance * 3 / 2;
1693 
1694  if (ShouldTreatTargetAsCircle(moveRequest.m_MinRange, circleRadius))
1695  {
1696  // We are safely away from the obstruction itself if we are away from the circumscribing circle
1698  out.hw = circleRadius + goalDistance;
1699  }
1700  else
1701  {
1703  out.hw = targetObstruction.hw + goalDistance;
1704  out.hh = targetObstruction.hh + goalDistance;
1705  }
1706  }
1707  else if (moveRequest.m_MaxRange >= fixed::Zero() && distance > moveRequest.m_MaxRange)
1708  {
1709  if (ShouldTreatTargetAsCircle(moveRequest.m_MaxRange, circleRadius))
1710  {
1711  entity_pos_t goalDistance = moveRequest.m_MaxRange;
1712  // We must go in-range of the inscribed circle, not the circumscribing circle.
1713  circleRadius = std::min(targetObstruction.hw, targetObstruction.hh);
1714 
1715  out.type = PathGoal::CIRCLE;
1716  out.hw = circleRadius + goalDistance;
1717  }
1718  else
1719  {
1720  // The target is large relative to our range, so treat it as a square and
1721  // get close enough that the diagonals come within range
1722 
1723  entity_pos_t goalDistance = moveRequest.m_MaxRange * 2 / 3; // multiply by slightly less than 1/sqrt(2)
1724 
1725  out.type = PathGoal::SQUARE;
1726  entity_pos_t delta = std::max(goalDistance, m_Clearance + entity_pos_t::FromInt(4)/16); // ensure it's far enough to not intersect the building itself
1727  out.hw = targetObstruction.hw + delta;
1728  out.hh = targetObstruction.hh + delta;
1729  }
1730  }
1731  // Do nothing in particular in case we are already in range.
1732  return true;
1733 }
1734 
1736 {
1737 #if DISABLE_PATHFINDER
1738  {
1740  CFixedVector2D goalPos = m_FinalGoal.NearestPointOnGoal(from);
1741  m_LongPath.m_Waypoints.clear();
1742  m_ShortPath.m_Waypoints.clear();
1743  m_ShortPath.m_Waypoints.emplace_back(Waypoint{ goalPos.X, goalPos.Y });
1744  return;
1745  }
1746 #endif
1747 
1748  // If the target is close enough, hope that we'll be able to go straight next turn.
1749  if (!ShouldAlternatePathfinder() && TryGoingStraightToTarget(from, false))
1750  {
1751  // NB: since we may fail to move straight next turn, we should edge our bets.
1752  // Since the 'go straight' logic currently fires only if there's no short path,
1753  // we'll compute a long path regardless to make sure _that_ stays up to date.
1754  // (it's also extremely likely to be very fast to compute, so no big deal).
1755  m_ShortPath.m_Waypoints.clear();
1756  RequestLongPath(from, goal);
1757  return;
1758  }
1759 
1760  // Otherwise we need to compute a path.
1761 
1762  // If it's close then just do a short path, not a long path
1763  // TODO: If it's close on the opposite side of a river then we really
1764  // need a long path, so we shouldn't simply check linear distance
1765  // the check is arbitrary but should be a reasonably small distance.
1766  // We want to occasionally compute a long path if we're computing short-paths, because the short path domain
1767  // is bounded and thus it can't around very large static obstacles.
1768  // Likewise, we want to compile a short-path occasionally when the target is far because we might be stuck
1769  // on a navcell surrounded by impassable navcells, but the short-pathfinder could move us out of there.
1770  bool shortPath = InShortPathRange(goal, from);
1772  shortPath = !shortPath;
1773  if (shortPath)
1774  {
1775  m_LongPath.m_Waypoints.clear();
1776  // Extend the range so that our first path is probably valid.
1777  RequestShortPath(from, goal, true);
1778  }
1779  else
1780  {
1781  m_ShortPath.m_Waypoints.clear();
1782  RequestLongPath(from, goal);
1783  }
1784 }
1785 
1787 {
1788  CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
1789  if (!cmpPathfinder)
1790  return;
1791 
1792  // this is by how much our waypoints will be apart at most.
1793  // this value here seems sensible enough.
1794  PathGoal improvedGoal = goal;
1796 
1797  cmpPathfinder->SetDebugPath(from.X, from.Y, improvedGoal, m_PassClass);
1798 
1800  m_ExpectedPathTicket.m_Ticket = cmpPathfinder->ComputePathAsync(from.X, from.Y, improvedGoal, m_PassClass, GetEntityId());
1801 }
1802 
1803 void CCmpUnitMotion::RequestShortPath(const CFixedVector2D &from, const PathGoal& goal, bool extendRange)
1804 {
1805  CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
1806  if (!cmpPathfinder)
1807  return;
1808 
1809  entity_pos_t searchRange = ShortPathSearchRange();
1810  if (extendRange)
1811  {
1812  CFixedVector2D dist(from.X - goal.x, from.Y - goal.z);
1813  if (dist.CompareLength(searchRange - entity_pos_t::FromInt(1)) >= 0)
1814  {
1815  searchRange = dist.Length() + fixed::FromInt(1);
1816  if (searchRange > SHORT_PATH_MAX_SEARCH_RANGE)
1817  searchRange = SHORT_PATH_MAX_SEARCH_RANGE;
1818  }
1819  }
1820 
1823 }
1824 
1826 {
1827  PROFILE("MoveTo");
1828 
1829  if (request.m_MinRange == request.m_MaxRange && !request.m_MinRange.IsZero())
1830  LOGWARNING("MaxRange must be larger than MinRange; See CCmpUnitMotion.cpp for more information");
1831 
1832  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
1833  if (!cmpPosition || !cmpPosition->IsInWorld())
1834  return false;
1835 
1836  PathGoal goal;
1837  if (!ComputeGoal(goal, request))
1838  return false;
1839 
1840  m_MoveRequest = request;
1841  m_FailedMovements = 0;
1843 
1844  ComputePathToGoal(cmpPosition->GetPosition2D(), goal);
1845  return true;
1846 }
1847 
1849 {
1850  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
1851  if (!cmpPosition || !cmpPosition->IsInWorld())
1852  return false;
1853 
1854  MoveRequest request(target, minRange, maxRange);
1855  PathGoal goal;
1856  if (!ComputeGoal(goal, request))
1857  return false;
1858 
1860  CFixedVector2D pos = cmpPosition->GetPosition2D();
1861  return cmpPathfinder->IsGoalReachable(pos.X, pos.Y, goal, m_PassClass);
1862 }
1863 
1864 
1865 void CCmpUnitMotion::RenderPath(const WaypointPath& path, std::vector<SOverlayLine>& lines, CColor color)
1866 {
1867  bool floating = false;
1868  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
1869  if (cmpPosition)
1870  floating = cmpPosition->CanFloat();
1871 
1872  lines.clear();
1873  std::vector<float> waypointCoords;
1874  for (size_t i = 0; i < path.m_Waypoints.size(); ++i)
1875  {
1876  float x = path.m_Waypoints[i].x.ToFloat();
1877  float z = path.m_Waypoints[i].z.ToFloat();
1878  waypointCoords.push_back(x);
1879  waypointCoords.push_back(z);
1880  lines.push_back(SOverlayLine());
1881  lines.back().m_Color = color;
1882  SimRender::ConstructSquareOnGround(GetSimContext(), x, z, 1.0f, 1.0f, 0.0f, lines.back(), floating);
1883  }
1884  float x = cmpPosition->GetPosition2D().X.ToFloat();
1885  float z = cmpPosition->GetPosition2D().Y.ToFloat();
1886  waypointCoords.push_back(x);
1887  waypointCoords.push_back(z);
1888  lines.push_back(SOverlayLine());
1889  lines.back().m_Color = color;
1890  SimRender::ConstructLineOnGround(GetSimContext(), waypointCoords, lines.back(), floating);
1891 
1892 }
1893 
1895 {
1896  if (!m_DebugOverlayEnabled)
1897  return;
1898 
1901 
1902  for (size_t i = 0; i < m_DebugOverlayLongPathLines.size(); ++i)
1903  collector.Submit(&m_DebugOverlayLongPathLines[i]);
1904 
1905  for (size_t i = 0; i < m_DebugOverlayShortPathLines.size(); ++i)
1906  collector.Submit(&m_DebugOverlayShortPathLines[i]);
1907 }
1908 
1909 #endif // INCLUDED_CCMPUNITMOTION
bool IsFormationControllerMoving() const
Definition: CCmpUnitMotion.h:607
bool isMoving
Definition: CCmpUnitMotionManager.h:89
An entity initialisation parameter node.
Definition: ParamNode.h:150
void SubscribeToMessageType(MessageTypeId mtid)
Subscribe the current component type to the given message type.
Definition: ComponentManager.cpp:549
constexpr u8 ALTERNATE_PATH_TYPE_EVERY
Definition: CCmpUnitMotion.h:113
MoveRequest(CFixedVector2D pos, entity_pos_t minRange, entity_pos_t maxRange)
Definition: CCmpUnitMotion.h:217
A simple fixed-point number class.
Definition: Fixed.h:119
static constexpr int MAX_PRESSURE
Maximum value for pushing pressure.
Definition: CCmpUnitMotionManager.h:41
bool TryGoingStraightToTarget(const CFixedVector2D &from, bool updatePaths)
Attempts to replace the current path with a straight line to the target, if it&#39;s close enough and the...
Definition: CCmpUnitMotion.h:1462
fixed speed
Definition: CCmpUnitMotionManager.h:64
entity_pos_t x
Definition: ICmpObstructionManager.h:68
pass_class_t GetPassabilityClass() const override
Get the unit&#39;s passability class.
Definition: CCmpUnitMotion.h:501
fixed m_SpeedMultiplier
Definition: CCmpUnitMotion.h:226
fixed m_Acceleration
Definition: CCmpUnitMotion.h:238
#define REGISTER_COMPONENT_TYPE(cname)
Definition: Component.h:32
void PathResult(u32 ticket, const WaypointPath &path)
Handle the result of an asynchronous path query.
Definition: CCmpUnitMotion.h:909
virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const PathGoal &goal, pass_class_t passClass)=0
If the debug overlay is enabled, render the path that will computed by ComputePath.
const CColor OVERLAY_COLOR_LONG_PATH(1, 1, 1, 1)
Definition: FixedVector2D.h:24
bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) override
Attempt to walk into range of a given target entity, or as close as possible.
Definition: CCmpUnitMotion.h:547
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning. ...
Definition: code_annotation.h:38
Sent by CCmpUnitMotion during Update if an event happened that might interest other components...
Definition: MessageTypes.h:322
CFixedVector2D EstimateFuturePosition(const fixed dt) const override
Definition: CCmpUnitMotion.h:467
u16 pass_class_t
Definition: Pathfinding.h:27
Line-based overlay, with world-space coordinates, rendered in the world potentially behind other obje...
Definition: Overlay.h:38
entity_id_t m_FormationController
Definition: CCmpUnitMotion.h:223
enum PathGoal::Type type
Definition: Components.h:75
T Clamp(T value, T min, T max)
Definition: MathUtil.h:32
virtual bool CanFloat() const =0
Returns whether the entity can float on water.
bool TargetHasValidPosition() const
Definition: CCmpUnitMotion.h:795
static CFixed Zero()
Definition: Fixed.h:131
bool RejectFartherPaths(const PathGoal &goal, const WaypointPath &path, const CFixedVector2D &pos) const
If path would take us farther away from the goal than pos currently is, return false, else return true.
Definition: CCmpUnitMotion.h:897
virtual pass_class_t GetPassabilityClass(const std::string &name) const =0
Get the tag for a given passability class name.
virtual bool IsInPointRange(entity_id_t ent, entity_pos_t px, entity_pos_t pz, entity_pos_t minRange, entity_pos_t maxRange, bool opposite) const =0
Check if the given entity is in range of the other point given those parameters.
entity_pos_t GetUnitClearance() const override
Get the unit clearance (used by the Obstruction component)
Definition: CCmpUnitMotion.h:591
virtual CFixedVector2D GetPosition2D() const =0
Returns the current x,z position (no interpolation).
static constexpr CFixed FromFraction(int n, int d)
Definition: Fixed.h:146
Definition: CCmpUnitMotion.h:195
Returned path.
Definition: Pathfinding.h:66
entity_pos_t m_Clearance
Definition: CCmpUnitMotion.h:164
CComponentManager & GetComponentManager() const
Definition: SimContext.cpp:35
bool valid() const
Definition: ICmpObstructionManager.h:81
void MoveToFormationOffset(entity_id_t controller, entity_pos_t x, entity_pos_t z) override
Join a formation, and move towards a given offset relative to the formation controller entity...
Definition: CCmpUnitMotion.h:552
void RenderSubmit(SceneCollector &collector)
Definition: CCmpUnitMotion.h:1894
void Init(const CParamNode &paramNode) override
Definition: CCmpUnitMotion.h:283
CFixedVector2D initialPos
Definition: CCmpUnitMotionManager.h:57
#define ENTITY_IS_LOCAL(id)
Definition: Entity.h:59
bool ignore
Definition: CCmpUnitMotionManager.h:80
void SetPassabilityClassName(const std::string &passClassName) override
Sets the passability class name (as defined in pathfinder.xml)
Definition: CCmpUnitMotion.h:511
static enum @32 state
bool IsTargetRangeReachable(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) override
Check if the target is reachable.
Definition: CCmpUnitMotion.h:1848
void Normalize()
Normalize the vector so that length is close to 1.
Definition: FixedVector2D.h:181
const std::string & ToString() const
Returns the content of this node as an UTF8 string.
Definition: ParamNode.cpp:271
virtual bool IsInTargetRange(entity_id_t ent, entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, bool opposite) const =0
Check if the given entity is in range of the target given those parameters.
Definition: Color.h:42
Serialization interface; see serialization overview.
Definition: ISerializer.h:120
void OnTurnStart()
Check if we are at destination early in the turn, this both lets units react faster and ensure that d...
Definition: CCmpUnitMotion.h:1019
CFixedVector2D Rotate(fixed angle) const
Rotate the vector by the given angle (anticlockwise).
Definition: FixedVector2D.h:241
uint8_t pushingPressure
Definition: CCmpUnitMotionManager.h:76
Obstruction test filter that reject shapes in a given control group, and rejects shapes that don&#39;t bl...
Definition: ICmpObstructionManager.h:400
Add renderable objects to the scene collector.
Definition: MessageTypes.h:149
static std::string GetSchema()
Definition: CCmpUnitMotion.h:245
static void out(const wchar_t *fmt,...)
Definition: wdbg_sym.cpp:421
std::vector< SOverlayLine > m_DebugOverlayShortPathLines
Definition: CCmpUnitMotion.h:152
entity_pos_t hw
Definition: PathGoal.h:45
Definition: CCmpUnitMotionManager.h:44
entity_id_t controlGroup
Definition: CCmpUnitMotionManager.h:71
void Serializer(S &serialize, const char *name, Args &&... args)
Definition: SerializeTemplates.h:51
constexpr 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...
Definition: CCmpUnitMotion.h:80
std::vector< Waypoint > m_Waypoints
Definition: Pathfinding.h:68
Similar to ControlGroupMovementObstructionFilter, but also ignoring a specific tag.
Definition: ICmpObstructionManager.h:541
virtual entity_pos_t GetWeight() const
Definition: CCmpUnitMotion.h:496
fixed m_TemplateWeight
Definition: CCmpUnitMotion.h:158
bool MoveTo(MoveRequest request)
General handler for MoveTo interface functions.
Definition: CCmpUnitMotion.h:1825
Definition: CCmpUnitMotion.h:202
bool IsFormationMember() const
Definition: CCmpUnitMotion.h:597
bool ComputeGoal(PathGoal &out, const MoveRequest &moveRequest) const
Create a PathGoal from a move request.
Definition: CCmpUnitMotion.h:1623
Definition: PathGoal.h:37
Definition: Pathfinding.h:57
void RequestShortPath(const CFixedVector2D &from, const PathGoal &goal, bool extendRange)
Start an asynchronous short path query.
Definition: CCmpUnitMotion.h:1803
entity_pos_t m_MaxRange
Definition: CCmpUnitMotion.h:211
#define LOGWARNING(...)
Definition: CLogger.h:35
Definition: CCmpUnitMotion.h:205
virtual void SelectMovementAnimation(const std::string &name, fixed speed)=0
Start playing the given movement animation unless we are currently playing a non-movement animation...
void Move(CCmpUnitMotionManager::MotionState &state, fixed dt)
Definition: CCmpUnitMotion.h:1061
void RenderPath(const WaypointPath &path, std::vector< SOverlayLine > &lines, CColor color)
Convert a path into a renderable list of lines.
Definition: CCmpUnitMotion.h:1865
Definition: ShaderDefines.cpp:30
fixed Y
Definition: FixedVector3D.h:27
Pathfinder goal.
Definition: PathGoal.h:32
constexpr entity_pos_t LONG_PATH_MIN_DIST
Minimum distance to goal for a long path request.
Definition: CCmpUnitMotion.h:74
virtual void MoveAndTurnTo(entity_pos_t x, entity_pos_t z, entity_angle_t ry)=0
Combines MoveTo and TurnTo to avoid an uncessary "AdvertisePositionChange".
void UpdateMovementState(entity_pos_t speed, entity_pos_t meanSpeed)
Update other components on our speed.
Definition: CCmpUnitMotion.h:1313
virtual CFixedVector3D GetRotation() const =0
Returns the current rotation (relative to the upwards axis), as Euler angles with X=pitch...
entity_pos_t hh
Definition: ICmpObstructionManager.h:70
virtual CFixedVector2D GetPreviousPosition2D() const =0
Returns the previous turn&#39;s x,z position (no interpolation).
void SetAcceleration(fixed acceleration) override
Set the current acceleration.
Definition: CCmpUnitMotion.h:491
CEntityHandle GetSystemEntity() const
Definition: IComponent.h:56
static CFixed Pi()
Definition: Fixed.cpp:182
int CompareLength(fixed cmp) const
Returns -1, 0, +1 depending on whether length is less/equal/greater than the argument.
Definition: FixedVector2D.h:122
bool m_DebugOverlayEnabled
Definition: CCmpUnitMotion.h:150
MoveRequest(entity_id_t target, CFixedVector2D offset)
Definition: CCmpUnitMotion.h:219
void Deserialize(const CParamNode &paramNode, IDeserializer &deserialize) override
Definition: CCmpUnitMotion.h:361
std::string m_PassClassName
Definition: CCmpUnitMotion.h:160
uint8_t u8
Definition: types.h:37
virtual u32 ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t clearance, entity_pos_t range, const PathGoal &goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t controller, entity_id_t notify)=0
Request a short path computation, asynchronously.
enum CCmpUnitMotion::Ticket::Type m_Type
virtual bool IsInWorld() const =0
Returns true if the entity currently exists at a defined position in the world.
bool IsMovingAsFormation() const
Definition: CCmpUnitMotion.h:602
const entity_id_t SYSTEM_ENTITY
Entity ID for singleton &#39;system&#39; components.
Definition: Entity.h:43
CFixedVector2D m_Position
Definition: CCmpUnitMotion.h:210
struct CCmpUnitMotion::Ticket m_ExpectedPathTicket
fixed Y
Definition: FixedVector2D.h:27
This interface accepts renderable objects.
Definition: Scene.h:89
constexpr entity_pos_t SHORT_PATH_MIN_SEARCH_RANGE
Min/Max range to restrict short path queries to.
Definition: CCmpUnitMotion.h:61
virtual bool GetBlockMovementFlag(bool templateOnly) const =0
Motion interface for entities with complex movement capabilities.
Definition: ICmpUnitMotion.h:34
void SetPassabilityData(const std::string &passClassName)
Definition: CCmpUnitMotion.h:624
Definition: Components.h:84
entity_pos_t z
Definition: Pathfinding.h:59
virtual fixed ApplyModifications(std::wstring valueName, fixed currentValue, entity_id_t entity) const =0
bool IncrementFailedMovementsAndMaybeNotify()
Increment the number of failed movements and notify other components if required. ...
Definition: CCmpUnitMotion.h:695
virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const PathGoal &goal, pass_class_t passClass, entity_id_t notify)=0
Asynchronous version of ComputePath.
#define ENSURE(expr)
ensure the expression <expr> evaluates to non-zero.
Definition: debug.h:290
fixed m_TemplateAcceleration
Definition: CCmpUnitMotion.h:158
virtual void SetMovingFlag(bool enabled)=0
fixed initialAngle
Definition: CCmpUnitMotionManager.h:66
fixed GetAcceleration() const override
Get the current acceleration.
Definition: CCmpUnitMotion.h:486
fixed m_CurrentSpeed
Definition: CCmpUnitMotion.h:234
Definition: Components.h:70
virtual int GetType() const =0
constexpr fixed NAVCELL_SIZE
The long-range pathfinder operates primarily over a navigation grid (a uniform-cost 2D passability gr...
Definition: Pathfinding.h:143
uint32_t u32
Definition: types.h:39
Type
Definition: CCmpUnitMotion.h:203
Type
Definition: CCmpUnitMotion.h:194
fixed GetRunMultiplier() const override
Get the unit template running (i.e.
Definition: CCmpUnitMotion.h:462
virtual ICmpObstructionManager::tag_t GetObstruction() const =0
entity_pos_t m_MinRange
Definition: CCmpUnitMotion.h:211
fixed DistanceToPoint(CFixedVector2D pos) const
Returns the minimum distance from the point pos to any point on the goal shape.
Definition: PathGoal.cpp:294
constexpr int NAVCELL_SIZE_INT
Definition: Pathfinding.h:144
SkipTagAndControlGroupObstructionFilter GetObstructionFilter(const ICmpObstructionManager::tag_t &tag) const
Filter a specific tag on top of the existing control groups.
Definition: CCmpUnitMotion.h:848
void PostMove(CCmpUnitMotionManager::MotionState &state, fixed dt)
Definition: CCmpUnitMotion.h:1073
entity_pos_t hw
Definition: ICmpObstructionManager.h:70
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:36
Provide specializations for some generic types and containers.
const CColor OVERLAY_COLOR_SHORT_PATH(1, 0, 0, 1)
Definition: ComponentManager.h:39
static void ClassInit(CComponentManager &componentManager)
Definition: CCmpUnitMotion.h:137
virtual bool AreShapesInRange(const ObstructionSquare &source, const ObstructionSquare &target, entity_pos_t minRange, entity_pos_t maxRange, bool opposite) const =0
Check if the given shape is in range of the target shape given those parameters.
bool m_FacePointAfterMove
Definition: CCmpUnitMotion.h:169
std::string GetPassabilityClassName() const override
Get the passability class name (as defined in pathfinder.xml)
Definition: CCmpUnitMotion.h:506
void UpdateMessageSubscriptions()
Definition: CCmpUnitMotion.h:430
bool InShortPathRange(const PathGoal &goal, const CFixedVector2D &pos) const
Definition: CCmpUnitMotion.h:717
virtual void TurnTo(entity_angle_t y)=0
Rotate smoothly to the given angle around the upwards axis.
bool HandleObstructedMove(bool moved)
React if our move was obstructed.
Definition: CCmpUnitMotion.h:1328
void SetDebugOverlay(bool enabled) override
Toggle the rendering of debug info.
Definition: CCmpUnitMotion.h:536
unsigned char uint8_t
Definition: wposix_types.h:51
void StopMoving() override
Clears the current MoveRequest - the unit will stop and no longer try and move.
Definition: CCmpUnitMotion.h:572
struct CCmpUnitMotion::MoveRequest m_MoveRequest
fixed GetSpeed() const override
Get the speed at which the unit intends to move.
Definition: CCmpUnitMotion.h:452
entity_id_t GetEntityId() const
Definition: IComponent.h:54
Definition: MessageTypes.h:328
Definition: MessageTypes.h:329
Sent by aura manager when a value of a certain entity&#39;s component is changed.
Definition: MessageTypes.h:496
WaypointPath m_LongPath
Definition: CCmpUnitMotion.h:242
entity_pos_t hh
Definition: PathGoal.h:45
void SerializeCommon(S &serialize)
Definition: CCmpUnitMotion.h:320
void SetParticipateInPushing(bool pushing)
Definition: CCmpUnitMotion.h:618
constexpr u8 ALTERNATE_PATH_TYPE_DELAY
When computing paths but failing to move, we want to occasionally alternate pathfinder systems to avo...
Definition: CCmpUnitMotion.h:112
Definition: Components.h:74
void SetMemberOfFormation(entity_id_t controller) override
Set/unset the unit as a formation member.
Definition: CCmpUnitMotion.h:558
fixed Length() const
Returns the length of the vector.
Definition: FixedVector2D.h:101
entity_pos_t x
Definition: PathGoal.h:43
void PostMessage(entity_id_t ent, const CMessage &msg)
Send a message, targeted at a particular entity.
Definition: ComponentManager.cpp:992
enum CCmpUnitMotion::MoveRequest::Type m_Type
fixed ToFixed() const
Parses the content of this node as a fixed-point number.
Definition: ParamNode.cpp:286
constexpr u8 BACKUP_HACK_DELAY
Units can occasionally get stuck near corners.
Definition: CCmpUnitMotion.h:121
#define PROFILE(name)
Definition: Profile.h:159
void HandleMessage(const CMessage &msg, bool global) override
Definition: CCmpUnitMotion.h:374
#define DEFAULT_COMPONENT_ALLOCATOR(cname)
Definition: Component.h:39
entity_id_t m_Entity
Definition: CCmpUnitMotion.h:209
bool IsOk() const
Returns true if this is a valid CParamNode, false if it represents a non-existent node...
Definition: ParamNode.cpp:261
Definition: Components.h:87
Definition: PathGoal.h:40
bool PerformMove(fixed dt, const fixed &turnRate, WaypointPath &shortPath, WaypointPath &longPath, CFixedVector2D &pos, fixed &speed, entity_angle_t &angle, uint8_t pushingPressure) const
Process the move the unit will do this turn.
Definition: CCmpUnitMotion.h:1142
fixed GetWalkSpeed() const override
Get the unit template walk speed after modifications.
Definition: CCmpUnitMotion.h:457
constexpr bool IsZero() const
Returns true if the number is precisely 0.
Definition: Fixed.h:209
void PreMove(CCmpUnitMotionManager::MotionState &state)
Definition: CCmpUnitMotion.h:1037
Definition: CCmpUnitMotionManager.h:31
Definition: PathGoal.h:39
u8 m_FollowKnownImperfectPathCountdown
Definition: CCmpUnitMotion.h:190
bool ShouldTreatTargetAsCircle(entity_pos_t range, entity_pos_t circleRadius) const
Decide whether to approximate the given range from a square target as a circle, rather than as a squa...
Definition: CCmpUnitMotion.h:1613
CFixedVector2D GetOffset() const
Definition: CCmpUnitMotion.h:214
u32 ticket
Definition: MessageTypes.h:489
fixed m_RunMultiplier
Definition: CCmpUnitMotion.h:167
void ComputePathToGoal(const CFixedVector2D &from, const PathGoal &goal)
Compute a path to the given goal from the given position.
Definition: CCmpUnitMotion.h:1735
A simplified syntax for accessing entity components.
Definition: CmpPtr.h:55
constexpr u8 VERY_OBSTRUCTED_THRESHOLD
After this many failed computations, start sending "VERY_OBSTRUCTED" messages instead.
Definition: CCmpUnitMotion.h:127
WaypointPath path
Definition: MessageTypes.h:490
bool needUpdate
Definition: CCmpUnitMotionManager.h:83
Definition: CCmpUnitMotion.h:196
bool m_IsFormationController
Definition: CCmpUnitMotion.h:156
MoveRequest(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
Definition: CCmpUnitMotion.h:218
bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) override
Attempt to walk into range of a to a given point, or as close as possible.
Definition: CCmpUnitMotion.h:542
constexpr u8 SHORT_PATH_SEARCH_RANGE_INCREASE_DELAY
Definition: CCmpUnitMotion.h:64
bool ComputeTargetPosition(CFixedVector2D &out, const MoveRequest &moveRequest) const
Computes the current location of our target entity (plus offset).
Definition: CCmpUnitMotion.h:1419
void FaceTowardsPoint(entity_pos_t x, entity_pos_t z) override
Turn to look towards the given point.
Definition: CCmpUnitMotion.h:1585
CFixedVector2D v
Definition: PathGoal.h:47
ControlGroupMovementObstructionFilter GetObstructionFilter() const
Returns an appropriate obstruction filter for use with path requests.
Definition: CCmpUnitMotion.h:841
constexpr CFixed Absolute() const
Definition: Fixed.h:315
fixed GetCurrentSpeed() const override
Get the speed at the end of the current turn.
Definition: CCmpUnitMotion.h:521
fixed m_InstantTurnAngle
Definition: CCmpUnitMotion.h:236
fixed m_TemplateRunMultiplier
Definition: CCmpUnitMotion.h:158
bool m_BlockMovement
Definition: CCmpUnitMotion.h:176
Definition: Components.h:88
fixed GetSpeedMultiplier() const override
Returns the ratio of GetSpeed() / GetWalkSpeed().
Definition: CCmpUnitMotion.h:441
bool GetFacePointAfterMove() const override
Definition: CCmpUnitMotion.h:531
CFixedVector2D NearestPointOnGoal(CFixedVector2D pos) const
Returns the coordinates of the point on the goal that is closest to the point pos.
Definition: PathGoal.cpp:327
CFixedVector2D u
Definition: PathGoal.h:47
u32 m_Ticket
Definition: CCmpUnitMotion.h:193
bool PathingUpdateNeeded(const CFixedVector2D &from) const
Returns whether our we need to recompute a path to reach our target.
Definition: CCmpUnitMotion.h:1523
virtual bool ComputingMotion() const =0
True if entities are currently in the "Move" phase.
CFixed Multiply(CFixed n) const
Multiply by a CFixed.
Definition: Fixed.h:321
pass_class_t m_PassClass
Definition: CCmpUnitMotion.h:159
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) const =0
Check whether the given movement line is valid and doesn&#39;t hit any obstructions or impassable terrain...
bool m_Pushing
Definition: CCmpUnitMotion.h:172
Definition: PathGoal.h:38
void clear()
Definition: CCmpUnitMotion.h:199
virtual fixed GetTurnRate() const =0
Returns the turn rate in radians per second.
constexpr u8 MAX_FAILED_MOVEMENTS
When we fail to move this many turns in a row, inform other components that the move will fail...
Definition: CCmpUnitMotion.h:106
constexpr entity_pos_t TARGET_UNCERTAINTY_MULTIPLIER
To avoid recomputing paths too often, have some leeway for target range checks based on our distance ...
Definition: CCmpUnitMotion.h:87
void Serialize(ISerializer &serialize) override
Definition: CCmpUnitMotion.h:356
ICmpPosition * cmpPosition
Definition: CCmpUnitMotionManager.h:53
WaypointPath m_ShortPath
Definition: CCmpUnitMotion.h:243
Definition: PathGoal.h:36
virtual bool IsPushingActivated() const =0
Sent by CCmpPathfinder after async path requests.
Definition: MessageTypes.h:479
SceneCollector & collector
Definition: MessageTypes.h:159
const CSimContext & GetSimContext() const
Definition: IComponent.h:58
fixed m_WalkSpeed
Definition: CCmpUnitMotion.h:167
CEntityHandle GetEntityHandle() const
Definition: IComponent.h:51
void RequestLongPath(const CFixedVector2D &from, const PathGoal &goal)
Start an asynchronous long path query.
Definition: CCmpUnitMotion.h:1786
Definition: MessageTypes.h:331
bool PossiblyAtDestination() const
Returns true if we are possibly at our destination.
Definition: CCmpUnitMotion.h:1113
virtual fixed DistanceBetweenShapes(const ObstructionSquare &source, const ObstructionSquare &target) const =0
Calculate the shortest straight line distance between the source and the target.
bool ShouldAlternatePathfinder() const
Definition: CCmpUnitMotion.h:712
bool ComputeTargetPosition(CFixedVector2D &out) const
Definition: CCmpUnitMotion.h:805
virtual entity_pos_t GetClearance(pass_class_t passClass) const =0
bool ToBool() const
Parses the content of this node as a boolean ("true" == true, anything else == false) ...
Definition: ParamNode.cpp:296
entity_pos_t maxdist
Definition: PathGoal.h:49
virtual bool IsMoveRequested() const =0
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:151
entity_id_t GetGroup() const
Definition: CCmpUnitMotion.h:613
void SetSpeedMultiplier(fixed multiplier) override
Set the current movement speed.
Definition: CCmpUnitMotion.h:446
#define FALLTHROUGH
Definition: code_annotation.h:412
void FaceTowardsPointFromPos(const CFixedVector2D &pos, entity_pos_t x, entity_pos_t z)
Rotate to face towards the target point, given the current pos.
Definition: CCmpUnitMotion.h:1595
virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare &out) const =0
Gets the square corresponding to this obstruction shape.
Definition: MessageTypes.h:330
Definition: CCmpUnitMotion.h:207
entity_pos_t ShortPathSearchRange() const
Definition: CCmpUnitMotion.h:722
Definition: Components.h:72
CFixedVector2D pos
Definition: CCmpUnitMotionManager.h:59
fixed X
Definition: FixedVector2D.h:27
void MoveObstructed()
Warns other components that our current movement was obstructed (i.e.
Definition: CCmpUnitMotion.h:678
entity_pos_t z
Definition: ICmpObstructionManager.h:68
void sincos_approx(CFixed_15_16 a, CFixed_15_16 &sin_out, CFixed_15_16 &cos_out)
Compute sin(a) and cos(a).
Definition: Fixed.cpp:187
void MoveSucceeded()
Warns other components that our current movement is likely over (i.e.
Definition: CCmpUnitMotion.h:661
fixed m_Speed
Definition: CCmpUnitMotion.h:228
bool ShouldCollideWithMovingUnits() const
Units in &#39;pushing&#39; mode are marked as &#39;moving&#39; in the obstruction manager.
Definition: CCmpUnitMotion.h:833
Definition: CCmpUnitMotion.h:206
fixed m_LastTurnSpeed
Definition: CCmpUnitMotion.h:231
float ToFloat() const
Convert to float. May be lossy - float can&#39;t represent all values.
Definition: Fixed.h:171
void Deinit() override
Definition: CCmpUnitMotion.h:315
u8 m_FailedMovements
Definition: CCmpUnitMotion.h:181
entity_pos_t x
Definition: Pathfinding.h:59
constexpr entity_pos_t SHORT_PATH_SEARCH_RANGE_INCREMENT
Definition: CCmpUnitMotion.h:63
bool IsZero() const
Definition: FixedVector2D.h:172
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
fixed angle
Definition: CCmpUnitMotionManager.h:67
constexpr entity_pos_t SHORT_PATH_MAX_SEARCH_RANGE
Definition: CCmpUnitMotion.h:62
void MoveFailed()
Warns other components that our current movement will likely fail (e.g.
Definition: CCmpUnitMotion.h:644
Definition: CCmpUnitMotion.h:192
Definition: Components.h:73
static constexpr CFixed FromInt(int n)
Definition: Fixed.h:140
static float Length(const SVec3 v)
Definition: mikktspace.cpp:109
virtual void SetUnitClearance(const entity_pos_t &clearance)=0
const entity_id_t INVALID_ENTITY
Invalid entity ID.
Definition: Entity.h:35
Definition: CCmpUnitMotion.h:133
u32 entity_id_t
Entity ID type.
Definition: Entity.h:23
virtual void Submit(CPatch *patch)=0
Submit a terrain patch that is part of the scene.
fixed m_TemplateWalkSpeed
Definition: CCmpUnitMotion.h:158
constexpr entity_pos_t SHORT_PATH_LONG_WAYPOINT_RANGE
When using the short-pathfinder to rejoin a long-path waypoint, aim for a circle of this radius aroun...
Definition: CCmpUnitMotion.h:69
Definition: CCmpUnitMotion.h:204
constexpr u8 KNOWN_IMPERFECT_PATH_RESET_COUNTDOWN
When following a known imperfect path (i.e.
Definition: CCmpUnitMotion.h:96
virtual bool IsGoalReachable(entity_pos_t x0, entity_pos_t z0, const PathGoal &goal, pass_class_t passClass)=0
void OnValueModification()
Definition: CCmpUnitMotion.h:736
entity_pos_t z
Definition: PathGoal.h:43
CFixedVector2D u
Definition: ICmpObstructionManager.h:69
Standard representation for all types of shapes, for use with geometry processing code...
Definition: ICmpObstructionManager.h:66
constexpr CFixed Square() const
Multiply the value by itself.
Definition: Fixed.h:333
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:253
std::wstring component
Definition: MessageTypes.h:509
Helper functions related to geometry algorithms.
std::vector< SOverlayLine > m_DebugOverlayLongPathLines
Definition: CCmpUnitMotion.h:151
bool wentStraight
Definition: CCmpUnitMotionManager.h:85
bool wasObstructed
Definition: CCmpUnitMotionManager.h:86
void SetFacePointAfterMove(bool facePointAfterMove) override
Set whether the unit will turn to face the target point after finishing moving.
Definition: CCmpUnitMotion.h:526
Deserialization interface; see serialization overview.
Definition: IDeserializer.h:34
External identifiers for shapes.
Definition: ICmpObstructionManager.h:77
Definition: Message.h:23
CFixedVector2D v
Definition: ICmpObstructionManager.h:69
void DynamicSubscriptionNonsync(MessageTypeId mtid, IComponent *component, bool enabled)
Subscribe the given component instance to all messages of the given message type. ...
Definition: ComponentManager.cpp:577
bool IsMoveRequested() const override
Definition: CCmpUnitMotion.h:436