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