2021-01-19 07:59:03 -08:00
/* Copyright (C) 2021 Wildfire Games.
2010-01-09 11:20:14 -08:00
* This file is part of 0 A . D .
*
* 0 A . D . is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 2 of the License , or
* ( at your option ) any later version .
*
* 0 A . D . is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with 0 A . D . If not , see < http : //www.gnu.org/licenses/>.
*/
# include "precompiled.h"
# include "simulation2/system/Component.h"
# include "ICmpUnitMotion.h"
2011-08-06 01:11:05 -07:00
# include "simulation2/components/ICmpObstruction.h"
# include "simulation2/components/ICmpObstructionManager.h"
# include "simulation2/components/ICmpOwnership.h"
# include "simulation2/components/ICmpPosition.h"
# include "simulation2/components/ICmpPathfinder.h"
# include "simulation2/components/ICmpRangeManager.h"
2013-10-30 09:12:53 -07:00
# include "simulation2/components/ICmpValueModificationManager.h"
2019-07-09 12:56:28 -07:00
# include "simulation2/components/ICmpVisual.h"
2010-04-29 16:36:05 -07:00
# include "simulation2/helpers/Geometry.h"
# include "simulation2/helpers/Render.h"
2011-08-06 01:11:05 -07:00
# include "simulation2/MessageTypes.h"
2020-12-19 01:10:37 -08:00
# include "simulation2/serialization/SerializedPathfinder.h"
# include "simulation2/serialization/SerializedTypes.h"
2010-04-29 16:36:05 -07:00
# include "graphics/Overlay.h"
# include "graphics/Terrain.h"
# include "maths/FixedVector2D.h"
2010-09-03 02:55:14 -07:00
# include "ps/CLogger.h"
2010-04-29 16:36:05 -07:00
# include "ps/Profile.h"
# include "renderer/Scene.h"
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
// For debugging; units will start going straight to the target
// instead of calling the pathfinder
# define DISABLE_PATHFINDER 0
2010-11-30 04:31:54 -08:00
/**
2019-07-22 11:07:24 -07:00
* Min / Max range to restrict short path queries to . ( Larger ranges are ( much ) slower ,
2010-11-30 04:31:54 -08:00
* smaller ranges might miss some legitimate routes around large obstacles . )
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
* NB : keep the max - range in sync with the vertex pathfinder " move the search space " heuristic .
2010-11-30 04:31:54 -08:00
*/
2019-07-22 11:07:24 -07:00
static const entity_pos_t SHORT_PATH_MIN_SEARCH_RANGE = entity_pos_t : : FromInt ( TERRAIN_TILE_SIZE * 3 ) / 2 ;
2020-05-25 13:13:35 -07:00
static const entity_pos_t SHORT_PATH_MAX_SEARCH_RANGE = entity_pos_t : : FromInt ( TERRAIN_TILE_SIZE * 14 ) ;
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
static const entity_pos_t SHORT_PATH_SEARCH_RANGE_INCREMENT = entity_pos_t : : FromInt ( TERRAIN_TILE_SIZE * 1 ) ;
static const u8 SHORT_PATH_SEARCH_RANGE_INCREASE_DELAY = 2 ;
2019-07-22 11:07:24 -07:00
/**
* When using the short - pathfinder to rejoin a long - path waypoint , aim for a circle of this radius around the waypoint .
*/
static const entity_pos_t SHORT_PATH_LONG_WAYPOINT_RANGE = entity_pos_t : : FromInt ( TERRAIN_TILE_SIZE * 1 ) ;
2010-11-30 04:31:54 -08:00
2016-02-17 11:00:34 -08:00
/**
* Minimum distance to goal for a long path request
*/
static const entity_pos_t LONG_PATH_MIN_DIST = entity_pos_t : : FromInt ( TERRAIN_TILE_SIZE * 4 ) ;
2010-11-30 04:31:54 -08:00
/**
* If we are this close to our target entity / point , then think about heading
* for it in a straight line instead of pathfinding .
*/
2021-01-19 11:09:55 -08:00
static const entity_pos_t DIRECT_PATH_RANGE = entity_pos_t : : FromInt ( TERRAIN_TILE_SIZE * 6 ) ;
2010-11-30 04:31:54 -08:00
2019-07-14 04:08:15 -07:00
/**
* To avoid recomputing paths too often , have some leeway for target range checks
* based on our distance to the target . Increase that incertainty by one navcell
* for every this many tiles of distance .
*/
static const entity_pos_t TARGET_UNCERTAINTY_MULTIPLIER = entity_pos_t : : FromInt ( TERRAIN_TILE_SIZE * 2 ) ;
2020-08-03 05:02:24 -07:00
/**
* When following a known imperfect path ( i . e . a path that won ' t take us in range of our goal
* we still recompute a new path every N turn to adapt to moving targets ( for example , ships that must pickup
* units may easily end up in this state , they still need to adjust to moving units ) .
* This is rather arbitrary and mostly for simplicity & optimisation ( a better recomputing algorithm
* would not need this ) .
* Keep in mind that MP turns are currently 500 ms .
*/
static const u8 KNOWN_IMPERFECT_PATH_RESET_COUNTDOWN = 12 ;
2019-07-13 08:53:21 -07:00
/**
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
* When we fail to move this many turns in a row , inform other components that the move will fail .
2019-07-13 08:53:21 -07:00
* Experimentally , this number needs to be somewhat high or moving groups of units will lead to stuck units .
* However , too high means units will look idle for a long time when they are failing to move .
* TODO : if UnitMotion could send differentiated " unreachable " and " currently stuck " failing messages ,
* this could probably be lowered .
* TODO : when unit pushing is implemented , this number can probably be lowered .
*/
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
static const u8 MAX_FAILED_MOVEMENTS = 40 ;
2019-07-13 08:53:21 -07:00
UnitMotion - Fix a rare pathfinding issues where units tried going straight through walls, and make sure a long path is computed even when the target is within short path or direct path range.
This fixes a regression introduced by 055c848c1a: when an entity is
ordered to move to a target within short path distance (or direct path
distance), it no longer computed at least one long path, which meant it
could be stuck forever if the target was not actually reachable (such as
behind a wall).
To fix this, compute a long path after 3 failed computations, which
should result in a delay of 1-3 turns. The previous code did this after
1 failed try - the decision to make it 3 is mostly based on the idea
that in most cases, being stuck means we ran into units, not that we
were ordered somewhere close. Should there be complaints, it could be
lowered to 2 or 1.
This fixes a second issue, reported in #4473: units sometimes get stuck,
particularly when trying to garrison a turret from 'inside' the walls.
The issue is that the turret is not accessible via the inside as its
obstruction + garrison range is blocked by the surrounding walls.
However, as introduced by 6e05a00929, TryGoingStraightToTargetEntity
ignores all entities with the obstruction group of the target (the
reason for this being that otherwise it would never succeed, since the
line towards the target would likely go through the target).
For walls and formations, this means ignoring possibly too many
entities, and in the case of #4473, ignoring wall pieces. The unit thus
mistakenly thought it could direct-path to the turret, and got stuck.
To fix this, we can ignore specifically the targeted entity's
obstruction tag. This can be considered a fix to 6e05a00929.
temple accepted an earlier version of this patch (specifically elexis'
version).
Fixes #4473
Based on a patch by: elexis
Differential Revision: https://code.wildfiregames.com/D1424
This was SVN commit r22533.
2019-07-22 23:18:07 -07:00
/**
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
* When computing paths but failing to move , we want to occasionally alternate pathfinder systems
* to avoid getting stuck ( the short pathfinder can unstuck the long - range one and vice - versa , depending ) .
UnitMotion - Fix a rare pathfinding issues where units tried going straight through walls, and make sure a long path is computed even when the target is within short path or direct path range.
This fixes a regression introduced by 055c848c1a: when an entity is
ordered to move to a target within short path distance (or direct path
distance), it no longer computed at least one long path, which meant it
could be stuck forever if the target was not actually reachable (such as
behind a wall).
To fix this, compute a long path after 3 failed computations, which
should result in a delay of 1-3 turns. The previous code did this after
1 failed try - the decision to make it 3 is mostly based on the idea
that in most cases, being stuck means we ran into units, not that we
were ordered somewhere close. Should there be complaints, it could be
lowered to 2 or 1.
This fixes a second issue, reported in #4473: units sometimes get stuck,
particularly when trying to garrison a turret from 'inside' the walls.
The issue is that the turret is not accessible via the inside as its
obstruction + garrison range is blocked by the surrounding walls.
However, as introduced by 6e05a00929, TryGoingStraightToTargetEntity
ignores all entities with the obstruction group of the target (the
reason for this being that otherwise it would never succeed, since the
line towards the target would likely go through the target).
For walls and formations, this means ignoring possibly too many
entities, and in the case of #4473, ignoring wall pieces. The unit thus
mistakenly thought it could direct-path to the turret, and got stuck.
To fix this, we can ignore specifically the targeted entity's
obstruction tag. This can be considered a fix to 6e05a00929.
temple accepted an earlier version of this patch (specifically elexis'
version).
Fixes #4473
Based on a patch by: elexis
Differential Revision: https://code.wildfiregames.com/D1424
This was SVN commit r22533.
2019-07-22 23:18:07 -07:00
*/
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
static const u8 ALTERNATE_PATH_TYPE_DELAY = 3 ;
static const u8 ALTERNATE_PATH_TYPE_EVERY = 6 ;
UnitMotion - Fix a rare pathfinding issues where units tried going straight through walls, and make sure a long path is computed even when the target is within short path or direct path range.
This fixes a regression introduced by 055c848c1a: when an entity is
ordered to move to a target within short path distance (or direct path
distance), it no longer computed at least one long path, which meant it
could be stuck forever if the target was not actually reachable (such as
behind a wall).
To fix this, compute a long path after 3 failed computations, which
should result in a delay of 1-3 turns. The previous code did this after
1 failed try - the decision to make it 3 is mostly based on the idea
that in most cases, being stuck means we ran into units, not that we
were ordered somewhere close. Should there be complaints, it could be
lowered to 2 or 1.
This fixes a second issue, reported in #4473: units sometimes get stuck,
particularly when trying to garrison a turret from 'inside' the walls.
The issue is that the turret is not accessible via the inside as its
obstruction + garrison range is blocked by the surrounding walls.
However, as introduced by 6e05a00929, TryGoingStraightToTargetEntity
ignores all entities with the obstruction group of the target (the
reason for this being that otherwise it would never succeed, since the
line towards the target would likely go through the target).
For walls and formations, this means ignoring possibly too many
entities, and in the case of #4473, ignoring wall pieces. The unit thus
mistakenly thought it could direct-path to the turret, and got stuck.
To fix this, we can ignore specifically the targeted entity's
obstruction tag. This can be considered a fix to 6e05a00929.
temple accepted an earlier version of this patch (specifically elexis'
version).
Fixes #4473
Based on a patch by: elexis
Differential Revision: https://code.wildfiregames.com/D1424
This was SVN commit r22533.
2019-07-22 23:18:07 -07:00
2021-01-05 02:12:47 -08:00
/**
* After this many failed computations , start sending " VERY_OBSTRUCTED " messages instead .
* Should probably be larger than ALTERNATE_PATH_TYPE_DELAY .
*/
static const u8 VERY_OBSTRUCTED_THRESHOLD = 10 ;
2015-03-15 16:59:48 -07:00
static const CColor OVERLAY_COLOR_LONG_PATH ( 1 , 1 , 1 , 1 ) ;
static const CColor OVERLAY_COLOR_SHORT_PATH ( 1 , 0 , 0 , 1 ) ;
2010-11-30 04:31:54 -08:00
2010-01-09 11:20:14 -08:00
class CCmpUnitMotion : public ICmpUnitMotion
{
public :
static void ClassInit ( CComponentManager & componentManager )
{
2021-01-27 07:11:57 -08:00
componentManager . SubscribeToMessageType ( MT_TurnStart ) ;
2010-09-03 02:55:14 -07:00
componentManager . SubscribeToMessageType ( MT_Update_MotionFormation ) ;
componentManager . SubscribeToMessageType ( MT_Update_MotionUnit ) ;
componentManager . SubscribeToMessageType ( MT_PathResult ) ;
2015-11-10 15:31:06 -08:00
componentManager . SubscribeToMessageType ( MT_OwnershipChanged ) ;
2013-10-30 09:12:53 -07:00
componentManager . SubscribeToMessageType ( MT_ValueModification ) ;
2015-02-20 17:41:24 -08:00
componentManager . SubscribeToMessageType ( MT_Deserialized ) ;
2010-01-09 11:20:14 -08:00
}
DEFAULT_COMPONENT_ALLOCATOR ( UnitMotion )
2010-04-29 16:36:05 -07:00
bool m_DebugOverlayEnabled ;
2010-11-30 04:31:54 -08:00
std : : vector < SOverlayLine > m_DebugOverlayLongPathLines ;
2010-04-29 16:36:05 -07:00
std : : vector < SOverlayLine > m_DebugOverlayShortPathLines ;
2010-01-09 11:20:14 -08:00
// Template state:
2010-02-10 11:28:46 -08:00
2010-09-03 02:55:14 -07:00
bool m_FormationController ;
2019-04-19 03:04:50 -07:00
2019-05-13 09:47:51 -07:00
fixed m_TemplateWalkSpeed , m_TemplateRunMultiplier ;
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
pass_class_t m_PassClass ;
2014-05-18 00:59:43 -07:00
std : : string m_PassClassName ;
2010-01-09 11:20:14 -08:00
// Dynamic state:
2010-02-10 11:28:46 -08:00
2015-07-18 01:37:49 -07:00
entity_pos_t m_Clearance ;
2019-04-19 03:04:50 -07:00
// cached for efficiency
2019-05-13 09:47:51 -07:00
fixed m_WalkSpeed , m_RunMultiplier ;
2019-04-19 03:04:50 -07:00
2013-09-30 16:52:22 -07:00
bool m_FacePointAfterMove ;
2010-01-09 11:20:14 -08:00
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
// Number of turns since we last managed to move successfully.
// See HandleObstructedMove() for more details.
u8 m_FailedMovements = 0 ;
2019-07-13 08:53:21 -07:00
2020-08-03 05:02:24 -07:00
// If > 0, PathingUpdateNeeded returns false always.
2019-08-04 01:16:09 -07:00
// This exists because the goal may be unreachable to the short/long pathfinder.
2020-08-03 05:02:24 -07:00
// In such cases, we would compute inacceptable paths and PathingUpdateNeeded would trigger every turn,
// which would be quite bad for performance.
// To avoid that, when we know the new path is imperfect, treat it as OK and follow it anyways.
// When reaching the end, we'll go through HandleObstructedMove and reset regardless.
// To still recompute now and then (the target may be moving), this is a countdown decremented on each frame.
u8 m_FollowKnownImperfectPathCountdown = 0 ;
2019-07-14 04:08:15 -07:00
2019-07-12 07:49:32 -07:00
struct Ticket {
u32 m_Ticket = 0 ; // asynchronous request ID we're waiting for, or 0 if none
enum Type {
SHORT_PATH ,
LONG_PATH
2019-07-14 02:24:37 -07:00
} m_Type = SHORT_PATH ; // Pick some default value to avoid UB.
2019-07-12 07:49:32 -07:00
void clear ( ) { m_Ticket = 0 ; }
} m_ExpectedPathTicket ;
2010-09-03 02:55:14 -07:00
2019-06-09 04:18:06 -07:00
struct MoveRequest {
enum Type {
NONE ,
POINT ,
ENTITY ,
OFFSET
} m_Type = NONE ;
entity_id_t m_Entity = INVALID_ENTITY ;
CFixedVector2D m_Position ;
entity_pos_t m_MinRange , m_MaxRange ;
// For readability
CFixedVector2D GetOffset ( ) const { return m_Position ; } ;
MoveRequest ( ) = default ;
MoveRequest ( CFixedVector2D pos , entity_pos_t minRange , entity_pos_t maxRange ) : m_Type ( POINT ) , m_Position ( pos ) , m_MinRange ( minRange ) , m_MaxRange ( maxRange ) { } ;
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 ) { } ;
MoveRequest ( entity_id_t target , CFixedVector2D offset ) : m_Type ( OFFSET ) , m_Entity ( target ) , m_Position ( offset ) { } ;
} m_MoveRequest ;
2010-09-03 02:55:14 -07:00
2019-05-13 09:47:51 -07:00
// If the entity moves, it will do so at m_WalkSpeed * m_SpeedMultiplier.
fixed m_SpeedMultiplier ;
// This caches the resulting speed from m_WalkSpeed * m_SpeedMultiplier for convenience.
2010-09-03 02:55:14 -07:00
fixed m_Speed ;
2012-12-06 11:46:13 -08:00
// Current mean speed (over the last turn).
fixed m_CurSpeed ;
2010-11-30 04:31:54 -08:00
// Currently active paths (storing waypoints in reverse order).
// The last item in each path is the point we're currently heading towards.
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
WaypointPath m_LongPath ;
WaypointPath m_ShortPath ;
2021-01-27 11:13:29 -08:00
// Hack - units move one-at-a-time, so they may need to interplate their target position.
// However, some computations are not doing during the motion messages, and those shouldn't (e.g. turn start).
// This is true if and only if the calls take place during handling of the entity's MT_Motion* messages.
// NB: this won't be true if we end up in UnitMotion because of another entity's motion messages,
// but I think it fixes the issue of interpolating target position OK for current needs,
// without having to add parameters everywhere.
// No need for serialisation, it's just a transient boolean.
bool m_InMotionMessage = false ;
2010-04-09 12:02:39 -07:00
static std : : string GetSchema ( )
{
return
2010-04-23 09:09:03 -07:00
" <a:help>Provides the unit with the ability to move around the world by itself.</a:help> "
" <a:example> "
" <WalkSpeed>7.0</WalkSpeed> "
2010-05-27 16:31:03 -07:00
" <PassabilityClass>default</PassabilityClass> "
2010-04-23 09:09:03 -07:00
" </a:example> "
2010-09-03 02:55:14 -07:00
" <element name='FormationController'> "
" <data type='boolean'/> "
" </element> "
2010-04-23 09:09:03 -07:00
" <element name='WalkSpeed' a:help='Basic movement speed (in metres per second)'> "
2010-04-09 12:02:39 -07:00
" <ref name='positiveDecimal'/> "
2010-05-15 14:07:52 -07:00
" </element> "
" <optional> "
2019-04-19 03:04:50 -07:00
" <element name='RunMultiplier' a:help='How much faster the unit goes when running (as a multiple of walk speed)'> "
" <ref name='positiveDecimal'/> "
2010-05-15 14:07:52 -07:00
" </element> "
2010-05-27 16:31:03 -07:00
" </optional> "
" <element name='PassabilityClass' a:help='Identifies the terrain passability class (values are defined in special/pathfinder.xml)'> "
" <text/> "
" </element> " ;
2010-04-09 12:02:39 -07:00
}
2010-04-23 09:09:03 -07:00
2011-01-16 06:08:38 -08:00
virtual void Init ( const CParamNode & paramNode )
2010-01-09 11:20:14 -08:00
{
2010-09-03 02:55:14 -07:00
m_FormationController = paramNode . GetChild ( " FormationController " ) . ToBool ( ) ;
2010-02-07 12:06:16 -08:00
2013-09-30 16:52:22 -07:00
m_FacePointAfterMove = true ;
2019-04-19 03:04:50 -07:00
m_WalkSpeed = m_TemplateWalkSpeed = m_Speed = paramNode . GetChild ( " WalkSpeed " ) . ToFixed ( ) ;
2019-05-13 09:47:51 -07:00
m_SpeedMultiplier = fixed : : FromInt ( 1 ) ;
2012-12-06 11:46:13 -08:00
m_CurSpeed = fixed : : Zero ( ) ;
2010-02-10 11:28:46 -08:00
2019-05-13 09:47:51 -07:00
m_RunMultiplier = m_TemplateRunMultiplier = fixed : : FromInt ( 1 ) ;
2019-04-19 03:04:50 -07:00
if ( paramNode . GetChild ( " RunMultiplier " ) . IsOk ( ) )
2019-05-13 09:47:51 -07:00
m_RunMultiplier = m_TemplateRunMultiplier = paramNode . GetChild ( " RunMultiplier " ) . ToFixed ( ) ;
2010-06-04 17:49:14 -07:00
2013-09-11 13:41:53 -07:00
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSystemEntity ( ) ) ;
2012-02-07 18:46:15 -08:00
if ( cmpPathfinder )
2010-05-27 16:31:03 -07:00
{
2014-05-18 00:59:43 -07:00
m_PassClassName = paramNode . GetChild ( " PassabilityClass " ) . ToUTF8 ( ) ;
m_PassClass = cmpPathfinder - > GetPassabilityClass ( m_PassClassName ) ;
2015-07-18 01:37:49 -07:00
m_Clearance = cmpPathfinder - > GetClearance ( m_PassClass ) ;
CmpPtr < ICmpObstruction > cmpObstruction ( GetEntityHandle ( ) ) ;
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
if ( cmpObstruction )
2015-07-18 01:37:49 -07:00
cmpObstruction - > SetUnitClearance ( m_Clearance ) ;
2010-05-27 16:31:03 -07:00
}
2010-04-29 16:36:05 -07:00
m_DebugOverlayEnabled = false ;
2010-01-09 11:20:14 -08:00
}
2011-01-16 06:08:38 -08:00
virtual void Deinit ( )
2010-01-09 11:20:14 -08:00
{
}
2010-11-30 04:31:54 -08:00
template < typename S >
void SerializeCommon ( S & serialize )
2010-01-09 11:20:14 -08:00
{
2014-05-18 00:59:43 -07:00
serialize . StringASCII ( " pass class " , m_PassClassName , 0 , 64 ) ;
2019-07-12 07:49:32 -07:00
serialize . NumberU32_Unbounded ( " ticket " , m_ExpectedPathTicket . m_Ticket ) ;
2020-12-19 01:10:37 -08:00
Serializer ( serialize , " ticket type " , m_ExpectedPathTicket . m_Type , Ticket : : Type : : LONG_PATH ) ;
2010-02-10 11:28:46 -08:00
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
serialize . NumberU8_Unbounded ( " failed movements " , m_FailedMovements ) ;
2020-08-03 05:02:24 -07:00
serialize . NumberU8_Unbounded ( " followknownimperfectpath " , m_FollowKnownImperfectPathCountdown ) ;
2019-07-13 08:53:21 -07:00
2020-12-19 01:10:37 -08:00
Serializer ( serialize , " target type " , m_MoveRequest . m_Type , MoveRequest : : Type : : OFFSET ) ;
2019-06-09 04:18:06 -07:00
serialize . NumberU32_Unbounded ( " target entity " , m_MoveRequest . m_Entity ) ;
serialize . NumberFixed_Unbounded ( " target pos x " , m_MoveRequest . m_Position . X ) ;
serialize . NumberFixed_Unbounded ( " target pos y " , m_MoveRequest . m_Position . Y ) ;
serialize . NumberFixed_Unbounded ( " target min range " , m_MoveRequest . m_MinRange ) ;
serialize . NumberFixed_Unbounded ( " target max range " , m_MoveRequest . m_MaxRange ) ;
2010-11-30 04:31:54 -08:00
2019-05-13 09:47:51 -07:00
serialize . NumberFixed_Unbounded ( " speed multiplier " , m_SpeedMultiplier ) ;
2019-04-19 03:04:50 -07:00
2016-10-10 03:49:49 -07:00
serialize . NumberFixed_Unbounded ( " current speed " , m_CurSpeed ) ;
2010-09-17 10:53:26 -07:00
2013-09-30 16:52:22 -07:00
serialize . Bool ( " facePointAfterMove " , m_FacePointAfterMove ) ;
2012-12-04 08:59:08 -08:00
2020-12-19 01:10:37 -08:00
Serializer ( serialize , " long path " , m_LongPath . m_Waypoints ) ;
Serializer ( serialize , " short path " , m_ShortPath . m_Waypoints ) ;
2010-01-09 11:20:14 -08:00
}
2010-11-30 04:31:54 -08:00
virtual void Serialize ( ISerializer & serialize )
{
SerializeCommon ( serialize ) ;
}
2011-01-16 06:08:38 -08:00
virtual void Deserialize ( const CParamNode & paramNode , IDeserializer & deserialize )
2010-01-09 11:20:14 -08:00
{
2011-01-16 06:08:38 -08:00
Init ( paramNode ) ;
2010-01-09 11:20:14 -08:00
2010-11-30 04:31:54 -08:00
SerializeCommon ( deserialize ) ;
2014-05-18 00:59:43 -07:00
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSystemEntity ( ) ) ;
if ( cmpPathfinder )
m_PassClass = cmpPathfinder - > GetPassabilityClass ( m_PassClassName ) ;
2010-01-09 11:20:14 -08:00
}
2011-01-16 06:08:38 -08:00
virtual void HandleMessage ( const CMessage & msg , bool UNUSED ( global ) )
2010-01-09 11:20:14 -08:00
{
switch ( msg . GetType ( ) )
{
2021-01-27 07:11:57 -08:00
case MT_TurnStart :
{
TurnStart ( ) ;
break ;
}
2010-09-03 02:55:14 -07:00
case MT_Update_MotionFormation :
2010-01-09 11:20:14 -08:00
{
2010-09-03 02:55:14 -07:00
if ( m_FormationController )
2010-02-10 11:28:46 -08:00
{
2021-01-27 11:13:29 -08:00
m_InMotionMessage = true ;
2010-09-03 02:55:14 -07:00
fixed dt = static_cast < const CMessageUpdate_MotionFormation & > ( msg ) . turnLength ;
Move ( dt ) ;
2021-01-27 11:13:29 -08:00
m_InMotionMessage = false ;
2010-09-03 02:55:14 -07:00
}
break ;
}
case MT_Update_MotionUnit :
{
if ( ! m_FormationController )
{
2021-01-27 11:13:29 -08:00
m_InMotionMessage = true ;
2010-09-03 02:55:14 -07:00
fixed dt = static_cast < const CMessageUpdate_MotionUnit & > ( msg ) . turnLength ;
Move ( dt ) ;
2021-01-27 11:13:29 -08:00
m_InMotionMessage = false ;
2010-02-10 11:28:46 -08:00
}
2010-01-09 11:20:14 -08:00
break ;
}
2010-04-29 16:36:05 -07:00
case MT_RenderSubmit :
{
2016-06-25 06:12:35 -07:00
PROFILE ( " UnitMotion::RenderSubmit " ) ;
2010-04-29 16:36:05 -07:00
const CMessageRenderSubmit & msgData = static_cast < const CMessageRenderSubmit & > ( msg ) ;
2010-05-01 02:48:39 -07:00
RenderSubmit ( msgData . collector ) ;
2010-04-29 16:36:05 -07:00
break ;
}
2010-09-03 02:55:14 -07:00
case MT_PathResult :
{
const CMessagePathResult & msgData = static_cast < const CMessagePathResult & > ( msg ) ;
PathResult ( msgData . ticket , msgData . path ) ;
break ;
}
2013-10-30 09:12:53 -07:00
case MT_ValueModification :
{
2016-11-23 05:02:58 -08:00
const CMessageValueModification & msgData = static_cast < const CMessageValueModification & > ( msg ) ;
2013-10-30 09:12:53 -07:00
if ( msgData . component ! = L " UnitMotion " )
break ;
2017-09-01 13:04:53 -07:00
FALLTHROUGH ;
2015-02-20 17:41:24 -08:00
}
2015-11-10 15:31:06 -08:00
case MT_OwnershipChanged :
2015-02-20 17:41:24 -08:00
case MT_Deserialized :
{
CmpPtr < ICmpValueModificationManager > cmpValueModificationManager ( GetSystemEntity ( ) ) ;
if ( ! cmpValueModificationManager )
break ;
2013-10-30 09:12:53 -07:00
2019-04-19 03:04:50 -07:00
m_WalkSpeed = cmpValueModificationManager - > ApplyModifications ( L " UnitMotion/WalkSpeed " , m_TemplateWalkSpeed , GetEntityId ( ) ) ;
2019-05-13 09:47:51 -07:00
m_RunMultiplier = cmpValueModificationManager - > ApplyModifications ( L " UnitMotion/RunMultiplier " , m_TemplateRunMultiplier , GetEntityId ( ) ) ;
2013-10-30 09:12:53 -07:00
2019-05-13 09:47:51 -07:00
// For MT_Deserialize compute m_Speed from the serialized m_SpeedMultiplier.
// For MT_ValueModification and MT_OwnershipChanged, adjust m_SpeedMultiplier if needed
// (in case then new m_RunMultiplier value is lower than the old).
SetSpeedMultiplier ( m_SpeedMultiplier ) ;
2013-10-30 09:12:53 -07:00
2015-02-20 17:41:24 -08:00
break ;
2013-10-30 09:12:53 -07:00
}
2010-01-09 11:20:14 -08:00
}
}
2014-06-19 16:20:12 -07:00
void UpdateMessageSubscriptions ( )
{
bool needRender = m_DebugOverlayEnabled ;
GetSimContext ( ) . GetComponentManager ( ) . DynamicSubscriptionNonsync ( MT_RenderSubmit , this , needRender ) ;
}
2019-07-28 03:51:12 -07:00
virtual bool IsMoveRequested ( ) const
2012-08-31 01:20:36 -07:00
{
2019-06-09 10:04:18 -07:00
return m_MoveRequest . m_Type ! = MoveRequest : : NONE ;
2012-08-31 01:20:36 -07:00
}
2019-05-13 09:47:51 -07:00
virtual fixed GetSpeedMultiplier ( ) const
2010-02-10 11:28:46 -08:00
{
2019-05-13 09:47:51 -07:00
return m_SpeedMultiplier ;
2019-04-19 03:04:50 -07:00
}
2019-05-13 09:47:51 -07:00
virtual void SetSpeedMultiplier ( fixed multiplier )
2019-04-19 03:04:50 -07:00
{
2019-05-13 09:47:51 -07:00
m_SpeedMultiplier = std : : min ( multiplier , m_RunMultiplier ) ;
m_Speed = m_SpeedMultiplier . Multiply ( GetWalkSpeed ( ) ) ;
2019-04-19 03:04:50 -07:00
}
virtual fixed GetSpeed ( ) const
{
return m_Speed ;
2010-04-29 16:36:05 -07:00
}
2010-02-10 11:28:46 -08:00
2019-04-19 03:04:50 -07:00
virtual fixed GetWalkSpeed ( ) const
2010-06-04 17:49:14 -07:00
{
2019-04-19 03:04:50 -07:00
return m_WalkSpeed ;
2010-06-04 17:49:14 -07:00
}
2019-05-13 09:47:51 -07:00
virtual fixed GetRunMultiplier ( ) const
{
return m_RunMultiplier ;
}
2021-01-19 07:59:03 -08:00
virtual CFixedVector2D EstimateFuturePosition ( const fixed dt ) const
{
CmpPtr < ICmpPosition > cmpPosition ( GetEntityHandle ( ) ) ;
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
return CFixedVector2D ( ) ;
// TODO: formation members should perhaps try to use the controller's position.
CFixedVector2D pos = cmpPosition - > GetPosition2D ( ) ;
entity_angle_t angle = cmpPosition - > GetRotation ( ) . Y ;
// Copy the path so we don't change it.
WaypointPath shortPath = m_ShortPath ;
WaypointPath longPath = m_LongPath ;
PerformMove ( dt , cmpPosition - > GetTurnRate ( ) , shortPath , longPath , pos , angle ) ;
return pos ;
}
2017-01-19 18:25:19 -08:00
virtual pass_class_t GetPassabilityClass ( ) const
2011-08-06 01:11:05 -07:00
{
return m_PassClass ;
}
2017-01-19 18:25:19 -08:00
virtual std : : string GetPassabilityClassName ( ) const
2014-05-18 00:59:43 -07:00
{
return m_PassClassName ;
}
2017-01-19 18:25:19 -08:00
virtual void SetPassabilityClassName ( const std : : string & passClassName )
2014-05-18 00:59:43 -07:00
{
m_PassClassName = passClassName ;
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSystemEntity ( ) ) ;
if ( cmpPathfinder )
m_PassClass = cmpPathfinder - > GetPassabilityClass ( passClassName ) ;
}
2017-01-19 18:25:19 -08:00
virtual fixed GetCurrentSpeed ( ) const
2012-12-06 11:46:13 -08:00
{
return m_CurSpeed ;
}
2013-09-30 16:52:22 -07:00
virtual void SetFacePointAfterMove ( bool facePointAfterMove )
{
m_FacePointAfterMove = facePointAfterMove ;
}
2020-07-19 03:42:45 -07:00
virtual bool GetFacePointAfterMove ( ) const
{
return m_FacePointAfterMove ;
}
2010-04-29 16:36:05 -07:00
virtual void SetDebugOverlay ( bool enabled )
{
m_DebugOverlayEnabled = enabled ;
2014-06-19 16:20:12 -07:00
UpdateMessageSubscriptions ( ) ;
2010-04-29 16:36:05 -07:00
}
2019-07-28 03:51:12 -07:00
virtual bool MoveToPointRange ( entity_pos_t x , entity_pos_t z , entity_pos_t minRange , entity_pos_t maxRange )
{
return MoveTo ( MoveRequest ( CFixedVector2D ( x , z ) , minRange , maxRange ) ) ;
}
virtual bool MoveToTargetRange ( entity_id_t target , entity_pos_t minRange , entity_pos_t maxRange )
{
return MoveTo ( MoveRequest ( target , minRange , maxRange ) ) ;
}
virtual void MoveToFormationOffset ( entity_id_t target , entity_pos_t x , entity_pos_t z )
{
MoveTo ( MoveRequest ( target , CFixedVector2D ( x , z ) ) ) ;
}
2010-07-18 08:19:49 -07:00
2020-08-03 05:02:24 -07:00
virtual bool IsTargetRangeReachable ( entity_id_t target , entity_pos_t minRange , entity_pos_t maxRange ) ;
2011-06-09 12:44:40 -07:00
virtual void FaceTowardsPoint ( entity_pos_t x , entity_pos_t z ) ;
2019-07-28 03:51:12 -07:00
/**
* Clears the current MoveRequest - the unit will stop and no longer try and move .
* This should never be called from UnitMotion , since MoveToX orders are given
* by other components - these components should also decide when to stop .
*/
2010-07-18 08:19:49 -07:00
virtual void StopMoving ( )
{
2019-06-09 10:06:24 -07:00
if ( m_FacePointAfterMove )
{
CmpPtr < ICmpPosition > cmpPosition ( GetEntityHandle ( ) ) ;
if ( cmpPosition & & cmpPosition - > IsInWorld ( ) )
2019-07-10 11:57:53 -07:00
{
CFixedVector2D targetPos ;
if ( ComputeTargetPosition ( targetPos ) )
FaceTowardsPointFromPos ( cmpPosition - > GetPosition2D ( ) , targetPos . X , targetPos . Y ) ;
}
2019-06-09 10:06:24 -07:00
}
2019-06-09 04:18:06 -07:00
m_MoveRequest = MoveRequest ( ) ;
2019-07-12 07:49:32 -07:00
m_ExpectedPathTicket . clear ( ) ;
2010-11-30 04:31:54 -08:00
m_LongPath . m_Waypoints . clear ( ) ;
m_ShortPath . m_Waypoints . clear ( ) ;
2010-09-03 02:55:14 -07:00
}
2017-01-19 18:25:19 -08:00
virtual entity_pos_t GetUnitClearance ( ) const
2010-09-03 02:55:14 -07:00
{
2015-07-18 01:37:49 -07:00
return m_Clearance ;
2010-07-18 08:19:49 -07:00
}
2010-04-29 16:36:05 -07:00
private :
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
bool ShouldAvoidMovingUnits ( ) const
2010-09-03 02:55:14 -07:00
{
return ! m_FormationController ;
}
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
bool IsFormationMember ( ) const
2010-11-30 04:31:54 -08:00
{
2019-07-08 11:09:21 -07:00
// TODO: this really shouldn't be what we are checking for.
return m_MoveRequest . m_Type = = MoveRequest : : OFFSET ;
2010-11-30 04:31:54 -08:00
}
2020-07-22 02:31:08 -07:00
bool IsFormationControllerMoving ( ) const
2020-02-13 11:44:03 -08:00
{
CmpPtr < ICmpUnitMotion > cmpControllerMotion ( GetSimContext ( ) , m_MoveRequest . m_Entity ) ;
2020-07-22 02:31:08 -07:00
return cmpControllerMotion & & cmpControllerMotion - > IsMoveRequested ( ) ;
2020-02-13 11:44:03 -08:00
}
2015-10-31 06:37:34 -07:00
entity_id_t GetGroup ( ) const
{
2019-06-09 04:18:06 -07:00
return IsFormationMember ( ) ? m_MoveRequest . m_Entity : GetEntityId ( ) ;
2015-10-31 06:37:34 -07:00
}
2019-07-28 03:51:12 -07:00
/**
* Warns other components that our current movement will likely fail ( e . g . we won ' t be able to reach our target )
* This should only be called before the actual movement in a given turn , or units might both move and try to do things
* on the same turn , leading to gliding units .
*/
2010-11-30 04:31:54 -08:00
void MoveFailed ( )
{
2020-07-22 02:31:08 -07:00
// Don't notify if we are a formation member in a moving formation - we can occasionally be stuck for a long time
// if our current offset is unreachable, but we don't want to end up stuck.
// (If the formation controller has stopped moving however, we can safely message).
if ( IsFormationMember ( ) & & IsFormationControllerMoving ( ) )
return ;
2019-07-22 11:07:24 -07:00
CMessageMotionUpdate msg ( CMessageMotionUpdate : : LIKELY_FAILURE ) ;
2010-09-03 02:55:14 -07:00
GetSimContext ( ) . GetComponentManager ( ) . PostMessage ( GetEntityId ( ) , msg ) ;
}
2019-07-28 03:51:12 -07:00
/**
* Warns other components that our current movement is likely over ( i . e . we probably reached our destination )
* This should only be called before the actual movement in a given turn , or units might both move and try to do things
* on the same turn , leading to gliding units .
*/
2010-11-30 04:31:54 -08:00
void MoveSucceeded ( )
2010-09-03 02:55:14 -07:00
{
2020-07-22 02:31:08 -07:00
// Don't notify if we are a formation member in a moving formation - we can occasionally be stuck for a long time
// if our current offset is unreachable, but we don't want to end up stuck.
// (If the formation controller has stopped moving however, we can safely message).
if ( IsFormationMember ( ) & & IsFormationControllerMoving ( ) )
return ;
2019-07-22 11:07:24 -07:00
CMessageMotionUpdate msg ( CMessageMotionUpdate : : LIKELY_SUCCESS ) ;
2010-09-03 02:55:14 -07:00
GetSimContext ( ) . GetComponentManager ( ) . PostMessage ( GetEntityId ( ) , msg ) ;
}
2010-04-29 16:36:05 -07:00
2020-07-22 02:31:08 -07:00
/**
* Warns other components that our current movement was obstructed ( i . e . we failed to move this turn ) .
* This should only be called before the actual movement in a given turn , or units might both move and try to do things
* on the same turn , leading to gliding units .
*/
void MoveObstructed ( )
{
// Don't notify if we are a formation member in a moving formation - we can occasionally be stuck for a long time
// if our current offset is unreachable, but we don't want to end up stuck.
// (If the formation controller has stopped moving however, we can safely message).
if ( IsFormationMember ( ) & & IsFormationControllerMoving ( ) )
return ;
2021-01-05 02:12:47 -08:00
CMessageMotionUpdate msg ( m_FailedMovements > = VERY_OBSTRUCTED_THRESHOLD ?
CMessageMotionUpdate : : VERY_OBSTRUCTED : CMessageMotionUpdate : : OBSTRUCTED ) ;
2020-07-22 02:31:08 -07:00
GetSimContext ( ) . GetComponentManager ( ) . PostMessage ( GetEntityId ( ) , msg ) ;
}
2019-06-10 12:33:24 -07:00
/**
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
* Increment the number of failed movements and notify other components if required .
2020-08-03 05:02:24 -07:00
* @ returns true if the failure was notified , false otherwise .
2019-07-13 08:53:21 -07:00
*/
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
bool IncrementFailedMovementsAndMaybeNotify ( )
2019-07-13 08:53:21 -07:00
{
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
m_FailedMovements + + ;
if ( m_FailedMovements > = MAX_FAILED_MOVEMENTS )
2019-07-13 08:53:21 -07:00
{
MoveFailed ( ) ;
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
m_FailedMovements = 0 ;
2020-08-03 05:02:24 -07:00
return true ;
2019-07-13 08:53:21 -07:00
}
2020-08-03 05:02:24 -07:00
return false ;
2019-07-13 08:53:21 -07:00
}
2019-07-28 03:29:28 -07:00
/**
* If path would take us farther away from the goal than pos currently is , return false , else return true .
*/
bool RejectFartherPaths ( const PathGoal & goal , const WaypointPath & path , const CFixedVector2D & pos ) const ;
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
bool ShouldAlternatePathfinder ( ) const
2019-08-04 01:16:09 -07:00
{
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
return ( m_FailedMovements = = ALTERNATE_PATH_TYPE_DELAY ) | | ( ( MAX_FAILED_MOVEMENTS - ALTERNATE_PATH_TYPE_DELAY ) % ALTERNATE_PATH_TYPE_EVERY = = 0 ) ;
2019-08-04 01:16:09 -07:00
}
bool InShortPathRange ( const PathGoal & goal , const CFixedVector2D & pos ) const
{
return goal . DistanceToPoint ( pos ) < LONG_PATH_MIN_DIST ;
}
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
entity_pos_t ShortPathSearchRange ( ) const
{
u8 multiple = m_FailedMovements < SHORT_PATH_SEARCH_RANGE_INCREASE_DELAY ? 0 : m_FailedMovements - SHORT_PATH_SEARCH_RANGE_INCREASE_DELAY ;
fixed searchRange = SHORT_PATH_MIN_SEARCH_RANGE + SHORT_PATH_SEARCH_RANGE_INCREMENT * multiple ;
if ( searchRange > SHORT_PATH_MAX_SEARCH_RANGE )
searchRange = SHORT_PATH_MAX_SEARCH_RANGE ;
return searchRange ;
}
2010-04-29 16:36:05 -07:00
/**
2010-11-30 04:31:54 -08:00
* Handle the result of an asynchronous path query .
*/
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
void PathResult ( u32 ticket , const WaypointPath & path ) ;
2010-11-30 04:31:54 -08:00
2021-01-27 07:11:57 -08:00
/**
* Check if we are at destination early in the turn , this both lets units react faster
* and ensure that distance comparisons are done while units are not being moved
* ( otherwise they won ' t be commutative ) .
*/
void TurnStart ( ) ;
2010-11-30 04:31:54 -08:00
/**
* Do the per - turn movement and other updates .
2010-04-29 16:36:05 -07:00
*/
2010-05-02 13:32:37 -07:00
void Move ( fixed dt ) ;
2010-04-29 16:36:05 -07:00
2019-06-11 10:25:59 -07:00
/**
* Returns true if we are possibly at our destination .
2019-07-28 03:51:12 -07:00
* Since the concept of being at destination is dependent on why the move was requested ,
* UnitMotion can only ever hint about this , hence the conditional tone .
2019-06-11 10:25:59 -07:00
*/
2019-06-11 12:52:40 -07:00
bool PossiblyAtDestination ( ) const ;
2019-06-11 10:25:59 -07:00
/**
* Process the move the unit will do this turn .
* This does not send actually change the position .
* @ returns true if the move was obstructed .
*/
2020-12-17 15:43:09 -08:00
bool PerformMove ( fixed dt , const fixed & turnRate , WaypointPath & shortPath , WaypointPath & longPath , CFixedVector2D & pos , entity_angle_t & angle ) const ;
2019-06-11 10:25:59 -07:00
/**
2019-07-28 03:51:12 -07:00
* Update other components on our speed .
* ( For performance , this should try to avoid sending messages ) .
2019-06-11 10:25:59 -07:00
*/
2019-07-28 03:51:12 -07:00
void UpdateMovementState ( entity_pos_t speed ) ;
2019-06-11 10:25:59 -07:00
2010-11-30 04:31:54 -08:00
/**
2019-07-28 03:51:12 -07:00
* React if our move was obstructed .
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
* @ param moved - true if the unit still managed to move .
2019-07-28 03:51:12 -07:00
* @ returns true if the obstruction required handling , false otherwise .
2010-11-30 04:31:54 -08:00
*/
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
bool HandleObstructedMove ( bool moved ) ;
2010-11-30 04:31:54 -08:00
2019-06-30 12:00:27 -07:00
/**
* Returns true if the target position is valid . False otherwise .
* ( this may indicate that the target is e . g . out of the world / dead ) .
* NB : for code - writing convenience , if we have no target , this returns true .
*/
2019-07-10 11:57:53 -07:00
bool TargetHasValidPosition ( const MoveRequest & moveRequest ) const ;
bool TargetHasValidPosition ( ) const
{
return TargetHasValidPosition ( m_MoveRequest ) ;
}
2019-06-30 12:00:27 -07:00
2010-11-30 04:31:54 -08:00
/**
* Computes the current location of our target entity ( plus offset ) .
* Returns false if no target entity or no valid position .
*/
2019-07-10 11:57:53 -07:00
bool ComputeTargetPosition ( CFixedVector2D & out , const MoveRequest & moveRequest ) const ;
bool ComputeTargetPosition ( CFixedVector2D & out ) const
{
return ComputeTargetPosition ( out , m_MoveRequest ) ;
}
2010-11-30 04:31:54 -08:00
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
/**
2019-07-02 00:29:31 -07:00
* Attempts to replace the current path with a straight line to the target ,
* if it ' s close enough and the route is not obstructed .
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
*/
2019-07-02 00:29:31 -07:00
bool TryGoingStraightToTarget ( const CFixedVector2D & from ) ;
2010-11-30 04:31:54 -08:00
/**
2019-07-03 11:06:53 -07:00
* Returns whether our we need to recompute a path to reach our target .
2010-11-30 04:31:54 -08:00
*/
2019-07-10 11:57:53 -07:00
bool PathingUpdateNeeded ( const CFixedVector2D & from ) const ;
2010-11-30 04:31:54 -08:00
2010-04-29 16:36:05 -07:00
/**
* Rotate to face towards the target point , given the current pos
*/
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
void FaceTowardsPointFromPos ( const CFixedVector2D & pos , entity_pos_t x , entity_pos_t z ) ;
2010-04-29 16:36:05 -07:00
/**
2010-11-30 04:31:54 -08:00
* Returns an appropriate obstruction filter for use with path requests .
2010-04-29 16:36:05 -07:00
*/
2021-01-27 09:44:31 -08:00
ControlGroupMovementObstructionFilter GetObstructionFilter ( ) const
{
return ControlGroupMovementObstructionFilter ( ShouldAvoidMovingUnits ( ) , GetGroup ( ) ) ;
}
/**
* Filter a specific tag on top of the existing control groups .
*/
SkipMovingTagAndControlGroupObstructionFilter GetObstructionFilter ( const ICmpObstructionManager : : tag_t & tag ) const
{
return SkipMovingTagAndControlGroupObstructionFilter ( tag , GetGroup ( ) ) ;
}
2010-09-03 02:55:14 -07:00
2019-07-28 03:51:12 -07:00
/**
* Decide whether to approximate the given range from a square target as a circle ,
* rather than as a square .
*/
bool ShouldTreatTargetAsCircle ( entity_pos_t range , entity_pos_t circleRadius ) const ;
2019-07-10 11:41:17 -07:00
/**
* Create a PathGoal from a move request .
* @ returns true if the goal was successfully created .
*/
bool ComputeGoal ( PathGoal & out , const MoveRequest & moveRequest ) const ;
2010-11-30 04:31:54 -08:00
/**
2019-07-28 03:51:12 -07:00
* Compute a path to the given goal from the given position .
* Might go in a straight line immediately , or might start an asynchronous path request .
2010-11-30 04:31:54 -08:00
*/
2019-07-28 03:51:12 -07:00
void ComputePathToGoal ( const CFixedVector2D & from , const PathGoal & goal ) ;
2010-04-29 16:36:05 -07:00
/**
2010-11-30 04:31:54 -08:00
* Start an asynchronous long path query .
2010-04-29 16:36:05 -07:00
*/
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
void RequestLongPath ( const CFixedVector2D & from , const PathGoal & goal ) ;
2010-04-29 16:36:05 -07:00
/**
2010-11-30 04:31:54 -08:00
* Start an asynchronous short path query .
2021-01-19 11:09:55 -08:00
* @ param extendRange - if true , extend the search range to at least the distance to the goal .
2010-04-29 16:36:05 -07:00
*/
2021-01-19 11:09:55 -08:00
void RequestShortPath ( const CFixedVector2D & from , const PathGoal & goal , bool extendRange ) ;
2010-04-29 16:36:05 -07:00
2019-07-28 03:51:12 -07:00
/**
* General handler for MoveTo interface functions .
*/
bool MoveTo ( MoveRequest request ) ;
2010-04-29 16:36:05 -07:00
/**
* Convert a path into a renderable list of lines
*/
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
void RenderPath ( const WaypointPath & path , std : : vector < SOverlayLine > & lines , CColor color ) ;
2010-04-29 16:36:05 -07:00
2010-05-01 02:48:39 -07:00
void RenderSubmit ( SceneCollector & collector ) ;
2010-04-29 16:36:05 -07:00
} ;
REGISTER_COMPONENT_TYPE ( UnitMotion )
2010-02-10 11:28:46 -08:00
2019-07-28 03:29:28 -07:00
bool CCmpUnitMotion : : RejectFartherPaths ( const PathGoal & goal , const WaypointPath & path , const CFixedVector2D & pos ) const
{
if ( path . m_Waypoints . empty ( ) )
return false ;
// Reject the new path if it does not lead us closer to the target's position.
if ( goal . DistanceToPoint ( pos ) < = goal . DistanceToPoint ( CFixedVector2D ( path . m_Waypoints . front ( ) . x , path . m_Waypoints . front ( ) . z ) ) )
return true ;
return false ;
}
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
void CCmpUnitMotion : : PathResult ( u32 ticket , const WaypointPath & path )
2010-04-29 16:36:05 -07:00
{
2010-09-03 02:55:14 -07:00
// Ignore obsolete path requests
2019-07-13 08:53:21 -07:00
if ( ticket ! = m_ExpectedPathTicket . m_Ticket | | m_MoveRequest . m_Type = = MoveRequest : : NONE )
2010-09-03 02:55:14 -07:00
return ;
2010-04-29 16:36:05 -07:00
2019-07-12 07:49:32 -07:00
Ticket : : Type ticketType = m_ExpectedPathTicket . m_Type ;
m_ExpectedPathTicket . clear ( ) ;
2010-09-03 02:55:14 -07:00
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
// If we not longer have a position, we won't be able to do much.
// Fail in the next Move() call.
2015-09-10 11:12:13 -07:00
CmpPtr < ICmpPosition > cmpPosition ( GetEntityHandle ( ) ) ;
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
return ;
2019-07-14 04:08:15 -07:00
CFixedVector2D pos = cmpPosition - > GetPosition2D ( ) ;
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
// Assume all long paths were towards the goal, and assume short paths were if there are no long waypoints.
bool pathedTowardsGoal = ticketType = = Ticket : : LONG_PATH | | m_LongPath . m_Waypoints . empty ( ) ;
2019-07-28 03:29:28 -07:00
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
// Check if we need to run the short-path hack (warning: tricky control flow).
bool shortPathHack = false ;
if ( path . m_Waypoints . empty ( ) )
2010-04-29 16:36:05 -07:00
{
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
// No waypoints means pathing failed. If this was a long-path, try the short-path hack.
if ( ! pathedTowardsGoal )
2019-07-28 03:29:28 -07:00
return ;
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
shortPathHack = ticketType = = Ticket : : LONG_PATH ;
}
else if ( PathGoal goal ; pathedTowardsGoal & & ComputeGoal ( goal , m_MoveRequest ) & & RejectFartherPaths ( goal , path , pos ) )
{
// Reject paths that would take the unit further away from the goal.
// This assumes that we prefer being closer 'as the crow flies' to unreachable goals.
// This is a hack of sorts around units 'dancing' between two positions (see e.g. #3144),
// but never actually failing to move, ergo never actually informing unitAI that it succeeds/fails.
// (for short paths, only do so if aiming directly for the goal
// as sub-goals may be farther than we are).
2019-07-14 04:08:15 -07:00
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
// If this was a long-path and we no longer have waypoints, try the short-path hack.
if ( ! m_LongPath . m_Waypoints . empty ( ) )
return ;
shortPathHack = ticketType = = Ticket : : LONG_PATH ;
2010-11-30 04:31:54 -08:00
}
2016-03-12 05:44:51 -08:00
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
// Short-path hack: if the long-range pathfinder doesn't find an acceptable path, push a fake waypoint at the goal.
// This means HandleObstructedMove will use the short-pathfinder to try and reach it,
// and that may find a path as the vertex pathfinder is more precise.
if ( shortPathHack )
2019-07-28 03:29:28 -07:00
{
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
// If we're resorting to the short-path hack, the situation is dire. Most likely, the goal is unreachable.
// We want to find a path or fail fast. Bump failed movements so the short pathfinder will run at max-range
// right away. This is safe from a performance PoV because it can only happen if the target is unreachable to
// the long-range pathfinder, which is rare, and since the entity will fail to move if the goal is actually unreachable,
// the failed movements will be increased to MAX anyways, so just shortcut.
m_FailedMovements = MAX_FAILED_MOVEMENTS - 2 ;
CFixedVector2D targetPos ;
if ( ComputeTargetPosition ( targetPos ) )
m_LongPath . m_Waypoints . emplace_back ( Waypoint { targetPos . X , targetPos . Y } ) ;
2019-07-28 03:29:28 -07:00
return ;
}
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
if ( ticketType = = Ticket : : LONG_PATH )
2021-01-27 11:13:29 -08:00
{
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
m_LongPath = path ;
2021-01-27 11:13:29 -08:00
// Long paths don't properly follow diagonals because of JPS/the grid. Since units now take time turning,
// they can actually slow down substantially if they have to do a one navcell diagonal movement,
// which is somewhat common at the beginning of a new path.
// For that reason, if the first waypoint is really close, check if we can't go directly to the second.
if ( m_LongPath . m_Waypoints . size ( ) > = 2 )
{
const Waypoint & firstWpt = m_LongPath . m_Waypoints . back ( ) ;
if ( CFixedVector2D ( firstWpt . x - pos . X , firstWpt . z - pos . Y ) . CompareLength ( fixed : : FromInt ( TERRAIN_TILE_SIZE ) ) < = 0 )
{
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSystemEntity ( ) ) ;
ENSURE ( cmpPathfinder ) ;
const Waypoint & secondWpt = m_LongPath . m_Waypoints [ m_LongPath . m_Waypoints . size ( ) - 2 ] ;
if ( cmpPathfinder - > CheckMovement ( GetObstructionFilter ( ) , pos . X , pos . Y , secondWpt . x , secondWpt . z , m_Clearance , m_PassClass ) )
m_LongPath . m_Waypoints . pop_back ( ) ;
}
}
}
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
else
m_ShortPath = path ;
2015-11-06 12:09:18 -08:00
2020-08-03 05:02:24 -07:00
m_FollowKnownImperfectPathCountdown = 0 ;
2019-07-22 11:07:24 -07:00
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
if ( ! pathedTowardsGoal )
return ;
2015-11-06 12:09:18 -08:00
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
// Performance hack: If we were pathing towards the goal and this new path won't put us in range,
// it's highly likely that we are going somewhere unreachable.
// However, Move() will try to recompute the path every turn, which can be quite slow.
// To avoid this, act as if our current path leads us to the correct destination.
// NB: for short-paths, the problem might be that the search space is too small
// but we'll still follow this path until the en and try again then.
// Because we reject farther paths, it works out.
if ( PathingUpdateNeeded ( pos ) )
{
// Inform other components early, as they might have better behaviour than waiting for the path to carry out.
// Send OBSTRUCTED at first - moveFailed is likely to trigger path recomputation and we might end up
// recomputing too often for nothing.
if ( ! IncrementFailedMovementsAndMaybeNotify ( ) )
MoveObstructed ( ) ;
// We'll automatically recompute a path when this reaches 0, as a way to improve behaviour.
// (See D665 - this is needed because the target may be moving, and we should adjust to that).
m_FollowKnownImperfectPathCountdown = KNOWN_IMPERFECT_PATH_RESET_COUNTDOWN ;
2010-11-30 04:31:54 -08:00
}
2010-04-29 16:36:05 -07:00
}
2021-01-27 07:11:57 -08:00
void CCmpUnitMotion : : TurnStart ( )
2010-04-29 16:36:05 -07:00
{
2019-06-11 11:51:55 -07:00
if ( PossiblyAtDestination ( ) )
MoveSucceeded ( ) ;
2019-06-30 12:00:27 -07:00
else if ( ! TargetHasValidPosition ( ) )
{
// Scrap waypoints - we don't know where to go.
// If the move request remains unchanged and the target again has a valid position later on,
// moving will be resumed.
// Units may want to move to move to the target's last known position,
// but that should be decided by UnitAI (handling MoveFailed), not UnitMotion.
m_LongPath . m_Waypoints . clear ( ) ;
m_ShortPath . m_Waypoints . clear ( ) ;
MoveFailed ( ) ;
}
2021-01-27 07:11:57 -08:00
}
void CCmpUnitMotion : : Move ( fixed dt )
{
PROFILE ( " Move " ) ;
// If we were idle and will still be, we can return.
// TODO: this will need to be removed if pushing is implemented.
if ( m_CurSpeed = = fixed : : Zero ( ) & & m_MoveRequest . m_Type = = MoveRequest : : NONE )
return ;
2019-06-11 11:51:55 -07:00
2019-06-10 10:15:27 -07:00
CmpPtr < ICmpPosition > cmpPosition ( GetEntityHandle ( ) ) ;
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
2010-04-29 16:36:05 -07:00
return ;
2019-06-10 10:15:27 -07:00
CFixedVector2D initialPos = cmpPosition - > GetPosition2D ( ) ;
2020-12-17 14:54:14 -08:00
entity_angle_t initialAngle = cmpPosition - > GetRotation ( ) . Y ;
2019-06-10 10:15:27 -07:00
2020-12-17 14:54:14 -08:00
// Keep track of the current unit's position and rotation during the update.
2019-06-10 10:15:27 -07:00
CFixedVector2D pos = initialPos ;
2020-12-17 14:54:14 -08:00
entity_angle_t angle = initialAngle ;
2019-06-10 10:15:27 -07:00
2019-06-10 12:35:02 -07:00
// If we're chasing a potentially-moving unit and are currently close
// enough to its current position, and we can head in a straight line
// to it, then throw away our current path and go straight to it
2019-07-14 04:08:15 -07:00
bool wentStraight = TryGoingStraightToTarget ( initialPos ) ;
2019-06-10 12:35:02 -07:00
2020-12-17 14:54:14 -08:00
bool wasObstructed = PerformMove ( dt , cmpPosition - > GetTurnRate ( ) , m_ShortPath , m_LongPath , pos , angle ) ;
2019-06-10 10:15:27 -07:00
2019-06-11 10:25:59 -07:00
// Update our speed over this turn so that the visual actor shows the correct animation.
if ( pos = = initialPos )
2020-12-17 14:54:14 -08:00
{
if ( angle ! = initialAngle )
cmpPosition - > TurnTo ( angle ) ;
2019-06-11 10:25:59 -07:00
UpdateMovementState ( fixed : : Zero ( ) ) ;
2020-12-17 14:54:14 -08:00
}
2019-06-11 10:25:59 -07:00
else
2010-09-03 02:55:14 -07:00
{
2019-06-11 10:25:59 -07:00
// Update the Position component after our movement (if we actually moved anywhere)
2020-12-17 14:54:14 -08:00
// When moving always set the angle in the direction of the movement.
2019-06-11 10:25:59 -07:00
CFixedVector2D offset = pos - initialPos ;
2020-12-17 14:54:14 -08:00
angle = atan2_approx ( offset . X , offset . Y ) ;
cmpPosition - > MoveAndTurnTo ( pos . X , pos . Y , angle ) ;
2010-04-29 16:36:05 -07:00
2019-06-11 10:25:59 -07:00
// Calculate the mean speed over this past turn.
UpdateMovementState ( offset . Length ( ) / dt ) ;
}
2010-11-30 06:48:04 -08:00
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
if ( wasObstructed & & HandleObstructedMove ( pos ! = initialPos ) )
2019-06-11 10:25:59 -07:00
return ;
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
else if ( ! wasObstructed & & pos ! = initialPos )
m_FailedMovements = 0 ;
2010-09-03 02:55:14 -07:00
2019-07-12 07:49:32 -07:00
// We may need to recompute our path sometimes (e.g. if our target moves).
// Since we request paths asynchronously anyways, this does not need to be done before moving.
2019-07-14 04:08:15 -07:00
if ( ! wentStraight & & PathingUpdateNeeded ( pos ) )
2019-06-11 10:25:59 -07:00
{
2019-07-12 07:49:32 -07:00
PathGoal goal ;
2019-07-12 09:16:13 -07:00
if ( ComputeGoal ( goal , m_MoveRequest ) )
2019-07-28 03:51:12 -07:00
ComputePathToGoal ( pos , goal ) ;
2019-06-11 10:25:59 -07:00
}
2020-08-03 05:02:24 -07:00
else if ( m_FollowKnownImperfectPathCountdown > 0 )
- - m_FollowKnownImperfectPathCountdown ;
2019-06-11 10:25:59 -07:00
}
2016-03-12 05:44:51 -08:00
2019-06-11 12:52:40 -07:00
bool CCmpUnitMotion : : PossiblyAtDestination ( ) const
2019-06-11 10:25:59 -07:00
{
2019-06-11 12:52:40 -07:00
if ( m_MoveRequest . m_Type = = MoveRequest : : NONE )
return false ;
CmpPtr < ICmpObstructionManager > cmpObstructionManager ( GetSystemEntity ( ) ) ;
ENSURE ( cmpObstructionManager ) ;
if ( m_MoveRequest . m_Type = = MoveRequest : : POINT )
return cmpObstructionManager - > IsInPointRange ( GetEntityId ( ) , m_MoveRequest . m_Position . X , m_MoveRequest . m_Position . Y , m_MoveRequest . m_MinRange , m_MoveRequest . m_MaxRange , false ) ;
if ( m_MoveRequest . m_Type = = MoveRequest : : ENTITY )
return cmpObstructionManager - > IsInTargetRange ( GetEntityId ( ) , m_MoveRequest . m_Entity , m_MoveRequest . m_MinRange , m_MoveRequest . m_MaxRange , false ) ;
if ( m_MoveRequest . m_Type = = MoveRequest : : OFFSET )
{
2019-07-09 12:58:44 -07:00
CmpPtr < ICmpUnitMotion > cmpControllerMotion ( GetSimContext ( ) , m_MoveRequest . m_Entity ) ;
2019-07-28 03:51:12 -07:00
if ( cmpControllerMotion & & cmpControllerMotion - > IsMoveRequested ( ) )
2019-07-09 12:58:44 -07:00
return false ;
2019-06-11 12:52:40 -07:00
CFixedVector2D targetPos ;
ComputeTargetPosition ( targetPos ) ;
2019-07-09 12:58:44 -07:00
CmpPtr < ICmpPosition > cmpPosition ( GetEntityHandle ( ) ) ;
return cmpObstructionManager - > IsInPointRange ( GetEntityId ( ) , targetPos . X , targetPos . Y , m_MoveRequest . m_MinRange , m_MoveRequest . m_MaxRange , false ) ;
2019-06-11 10:25:59 -07:00
}
return false ;
}
2010-11-30 04:31:54 -08:00
2020-12-17 15:43:09 -08:00
bool CCmpUnitMotion : : PerformMove ( fixed dt , const fixed & turnRate , WaypointPath & shortPath , WaypointPath & longPath , CFixedVector2D & pos , entity_angle_t & angle ) const
2019-06-11 10:25:59 -07:00
{
2019-07-13 08:53:21 -07:00
// If there are no waypoint, behave as though we were obstructed and let HandleObstructedMove handle it.
2019-07-12 07:49:32 -07:00
if ( shortPath . m_Waypoints . empty ( ) & & longPath . m_Waypoints . empty ( ) )
2019-07-13 08:53:21 -07:00
return true ;
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
2020-12-17 14:54:14 -08:00
// Wrap the angle to (-Pi, Pi].
while ( angle > entity_angle_t : : Pi ( ) )
angle - = entity_angle_t : : Pi ( ) * 2 ;
while ( angle < - entity_angle_t : : Pi ( ) )
angle + = entity_angle_t : : Pi ( ) * 2 ;
2019-06-11 10:25:59 -07:00
// TODO: there's some asymmetry here when units look at other
// units' positions - the result will depend on the order of execution.
// Maybe we should split the updates into multiple phases to minimise
// that problem.
2010-04-29 16:36:05 -07:00
2019-06-11 10:25:59 -07:00
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSystemEntity ( ) ) ;
2019-07-13 08:53:21 -07:00
ENSURE ( cmpPathfinder ) ;
2010-09-03 02:55:14 -07:00
2019-06-11 10:25:59 -07:00
fixed basicSpeed = m_Speed ;
2020-12-17 14:54:14 -08:00
// If in formation, run to keep up; otherwise just walk.
2019-06-11 10:25:59 -07:00
if ( IsFormationMember ( ) )
basicSpeed = m_Speed . Multiply ( m_RunMultiplier ) ;
2010-09-03 02:55:14 -07:00
2020-12-17 14:54:14 -08:00
// Find the speed factor of the underlying terrain.
2019-06-11 10:25:59 -07:00
// (We only care about the tile we start on - it doesn't matter if we're moving
2020-12-17 14:54:14 -08:00
// partially onto a much slower/faster tile).
// TODO: Terrain-dependent speeds are not currently supported.
2019-06-11 10:25:59 -07:00
fixed terrainSpeed = fixed : : FromInt ( 1 ) ;
2010-09-03 02:55:14 -07:00
2019-06-11 10:25:59 -07:00
fixed maxSpeed = basicSpeed . Multiply ( terrainSpeed ) ;
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
2019-06-11 10:25:59 -07:00
fixed timeLeft = dt ;
fixed zero = fixed : : Zero ( ) ;
2016-03-12 05:44:51 -08:00
2021-01-27 09:44:31 -08:00
ICmpObstructionManager : : tag_t specificIgnore ;
if ( m_MoveRequest . m_Type = = MoveRequest : : ENTITY )
{
CmpPtr < ICmpObstruction > cmpTargetObstruction ( GetSimContext ( ) , m_MoveRequest . m_Entity ) ;
if ( cmpTargetObstruction )
specificIgnore = cmpTargetObstruction - > GetObstruction ( ) ;
}
2019-06-11 10:25:59 -07:00
while ( timeLeft > zero )
2019-06-10 10:15:27 -07:00
{
2020-12-17 14:54:14 -08:00
// If we ran out of path, we have to stop.
2019-06-11 10:25:59 -07:00
if ( shortPath . m_Waypoints . empty ( ) & & longPath . m_Waypoints . empty ( ) )
break ;
2016-03-12 05:44:51 -08:00
2019-06-11 10:25:59 -07:00
CFixedVector2D target ;
if ( shortPath . m_Waypoints . empty ( ) )
target = CFixedVector2D ( longPath . m_Waypoints . back ( ) . x , longPath . m_Waypoints . back ( ) . z ) ;
else
target = CFixedVector2D ( shortPath . m_Waypoints . back ( ) . x , shortPath . m_Waypoints . back ( ) . z ) ;
2012-12-06 11:46:13 -08:00
2019-06-11 10:25:59 -07:00
CFixedVector2D offset = target - pos ;
2021-01-27 07:11:57 -08:00
if ( turnRate > zero & & ! offset . IsZero ( ) )
2020-12-17 14:54:14 -08:00
{
fixed maxRotation = turnRate . Multiply ( timeLeft ) ;
fixed angleDiff = angle - atan2_approx ( offset . X , offset . Y ) ;
if ( angleDiff ! = zero )
{
fixed absoluteAngleDiff = angleDiff . Absolute ( ) ;
if ( absoluteAngleDiff > entity_angle_t : : Pi ( ) )
absoluteAngleDiff = entity_angle_t : : Pi ( ) * 2 - absoluteAngleDiff ;
// Figure out whether rotating will increase or decrease the angle, and how far we need to rotate in that direction.
int direction = ( entity_angle_t : : Zero ( ) < angleDiff & & angleDiff < = entity_angle_t : : Pi ( ) ) | | angleDiff < - entity_angle_t : : Pi ( ) ? - 1 : 1 ;
// Can't rotate far enough, just rotate in the correct direction.
if ( absoluteAngleDiff > maxRotation )
{
angle + = maxRotation * direction ;
if ( angle * direction > entity_angle_t : : Pi ( ) )
angle - = entity_angle_t : : Pi ( ) * 2 * direction ;
break ;
}
// Rotate towards the next waypoint and continue moving.
angle = atan2_approx ( offset . X , offset . Y ) ;
2021-01-19 11:09:55 -08:00
// Give some 'free' rotation for angles below 0.5 radians.
timeLeft = ( std : : min ( maxRotation , maxRotation - absoluteAngleDiff + fixed : : FromInt ( 1 ) / 2 ) ) / turnRate ;
2020-12-17 14:54:14 -08:00
}
}
2019-06-10 10:15:27 -07:00
2020-12-17 14:54:14 -08:00
// Work out how far we can travel in timeLeft.
2019-06-11 10:25:59 -07:00
fixed maxdist = maxSpeed . Multiply ( timeLeft ) ;
2016-03-12 05:44:51 -08:00
2020-12-17 14:54:14 -08:00
// If the target is close, we can move there directly.
2019-06-11 10:25:59 -07:00
fixed offsetLength = offset . Length ( ) ;
if ( offsetLength < = maxdist )
2010-11-30 04:31:54 -08:00
{
2021-01-27 09:44:31 -08:00
if ( cmpPathfinder - > CheckMovement ( GetObstructionFilter ( specificIgnore ) , pos . X , pos . Y , target . X , target . Y , m_Clearance , m_PassClass ) )
2019-06-11 10:25:59 -07:00
{
pos = target ;
2016-03-12 05:44:51 -08:00
2020-12-17 14:54:14 -08:00
// Spend the rest of the time heading towards the next waypoint.
2019-06-11 10:25:59 -07:00
timeLeft = ( maxdist - offsetLength ) / maxSpeed ;
2019-06-10 10:15:27 -07:00
2019-06-11 10:25:59 -07:00
if ( shortPath . m_Waypoints . empty ( ) )
longPath . m_Waypoints . pop_back ( ) ;
else
shortPath . m_Waypoints . pop_back ( ) ;
continue ;
2019-05-25 06:23:44 -07:00
}
2019-06-11 10:25:59 -07:00
else
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
{
2020-12-17 14:54:14 -08:00
// Error - path was obstructed.
2019-06-11 10:25:59 -07:00
return true ;
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
}
2010-02-10 11:28:46 -08:00
}
2019-06-11 10:25:59 -07:00
else
{
2020-12-17 14:54:14 -08:00
// Not close enough, so just move in the right direction.
2019-06-11 10:25:59 -07:00
offset . Normalize ( maxdist ) ;
target = pos + offset ;
2010-09-03 02:55:14 -07:00
2021-01-27 09:44:31 -08:00
if ( cmpPathfinder - > CheckMovement ( GetObstructionFilter ( specificIgnore ) , pos . X , pos . Y , target . X , target . Y , m_Clearance , m_PassClass ) )
2019-06-11 10:25:59 -07:00
pos = target ;
else
return true ;
break ;
}
2019-06-10 10:15:27 -07:00
}
2019-06-11 10:25:59 -07:00
return false ;
}
2010-09-03 02:55:14 -07:00
2019-07-28 03:51:12 -07:00
void CCmpUnitMotion : : UpdateMovementState ( entity_pos_t speed )
{
CmpPtr < ICmpObstruction > cmpObstruction ( GetEntityHandle ( ) ) ;
CmpPtr < ICmpVisual > cmpVisual ( GetEntityHandle ( ) ) ;
// Moved last turn, didn't this turn.
if ( speed = = fixed : : Zero ( ) & & m_CurSpeed > fixed : : Zero ( ) )
{
if ( cmpObstruction )
cmpObstruction - > SetMovingFlag ( false ) ;
if ( cmpVisual )
cmpVisual - > SelectMovementAnimation ( " idle " , fixed : : FromInt ( 1 ) ) ;
}
// Moved this turn, didn't last turn
else if ( speed > fixed : : Zero ( ) & & m_CurSpeed = = fixed : : Zero ( ) )
{
if ( cmpObstruction )
cmpObstruction - > SetMovingFlag ( true ) ;
if ( cmpVisual )
2020-12-27 00:07:10 -08:00
cmpVisual - > SelectMovementAnimation ( speed > ( m_WalkSpeed / 2 ) . Multiply ( m_RunMultiplier + fixed : : FromInt ( 1 ) ) ? " run " : " walk " , speed ) ;
2019-07-28 03:51:12 -07:00
}
// Speed change, update the visual actor if necessary.
else if ( speed ! = m_CurSpeed & & cmpVisual )
2020-12-27 00:07:10 -08:00
cmpVisual - > SelectMovementAnimation ( speed > ( m_WalkSpeed / 2 ) . Multiply ( m_RunMultiplier + fixed : : FromInt ( 1 ) ) ? " run " : " walk " , speed ) ;
2019-07-28 03:51:12 -07:00
m_CurSpeed = speed ;
}
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
bool CCmpUnitMotion : : HandleObstructedMove ( bool moved )
2019-06-11 10:25:59 -07:00
{
CmpPtr < ICmpPosition > cmpPosition ( GetEntityHandle ( ) ) ;
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
return false ;
2019-06-10 10:15:27 -07:00
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
// We failed to move, inform other components as they might handle it.
// (don't send messages on the first failure, as that would be too noisy).
// Also don't increment above the initial MoveObstructed message if we actually manage to move a little.
if ( ! moved | | m_FailedMovements < 2 )
{
if ( ! IncrementFailedMovementsAndMaybeNotify ( ) & & m_FailedMovements > = 2 )
MoveObstructed ( ) ;
}
2019-07-22 11:07:24 -07:00
2019-07-18 13:00:38 -07:00
PathGoal goal ;
if ( ! ComputeGoal ( goal , m_MoveRequest ) )
return false ;
2020-08-06 01:40:14 -07:00
// At this point we have a position in the world since ComputeGoal checked for that.
CFixedVector2D pos = cmpPosition - > GetPosition2D ( ) ;
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
// Assume that we are merely obstructed and the long path is salvageable, so try going around the obstruction.
// 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.
// I use an IIFE to have nice 'return' semantics still.
if ( [ & ] ( ) - > bool {
// If the goal is close enough, we should ignore any remaining long waypoint and just
// short path there directly, as that improves behaviour in general - see D2095).
if ( InShortPathRange ( goal , pos ) )
return false ;
// Delete the next waypoint if it's reasonably close,
// because it might be blocked by units and thus unreachable.
// NB: this number is tricky. Make it too high, and units start going down dead ends, which looks odd (#5795)
// Make it too low, and they might get stuck behind other obstructed entities.
// It also has performance implications because it calls the short-pathfinder.
fixed skipbeyond = std : : max ( ShortPathSearchRange ( ) / 3 , fixed : : FromInt ( TERRAIN_TILE_SIZE * 2 ) ) ;
if ( m_LongPath . m_Waypoints . size ( ) > 1 & &
( pos - CFixedVector2D ( m_LongPath . m_Waypoints . back ( ) . x , m_LongPath . m_Waypoints . back ( ) . z ) ) . CompareLength ( skipbeyond ) < 0 )
{
2019-08-04 01:16:09 -07:00
m_LongPath . m_Waypoints . pop_back ( ) ;
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
}
else if ( ShouldAlternatePathfinder ( ) )
2019-08-04 01:16:09 -07:00
{
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
// Recompute the whole thing occasionally, in case we got stuck in a dead end from removing long waypoints.
RequestLongPath ( pos , goal ) ;
2019-08-04 01:16:09 -07:00
return true ;
}
2019-07-18 13:00:38 -07:00
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
if ( m_LongPath . m_Waypoints . empty ( ) )
return false ;
// Compute a short path in the general vicinity of the next waypoint, to help pathfinding in crowds.
// The goal here is to manage to move in the general direction of our target, not to be super accurate.
fixed radius = Clamp ( skipbeyond / 3 , fixed : : FromInt ( TERRAIN_TILE_SIZE ) , fixed : : FromInt ( TERRAIN_TILE_SIZE * 3 ) ) ;
PathGoal subgoal = { PathGoal : : CIRCLE , m_LongPath . m_Waypoints . back ( ) . x , m_LongPath . m_Waypoints . back ( ) . z , radius } ;
2021-01-19 11:09:55 -08:00
RequestShortPath ( pos , subgoal , false ) ;
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
return true ;
} ( ) ) return true ;
// If we couldn't use a workaround, try recomputing the entire path.
2019-07-28 03:51:12 -07:00
ComputePathToGoal ( pos , goal ) ;
2019-06-11 10:25:59 -07:00
return true ;
2010-04-29 16:36:05 -07:00
}
2019-07-10 11:57:53 -07:00
bool CCmpUnitMotion : : TargetHasValidPosition ( const MoveRequest & moveRequest ) const
2019-06-30 12:00:27 -07:00
{
2019-07-10 11:57:53 -07:00
if ( moveRequest . m_Type ! = MoveRequest : : ENTITY )
2019-06-30 12:00:27 -07:00
return true ;
2019-07-10 11:57:53 -07:00
CmpPtr < ICmpPosition > cmpPosition ( GetSimContext ( ) , moveRequest . m_Entity ) ;
2019-06-30 12:00:27 -07:00
return cmpPosition & & cmpPosition - > IsInWorld ( ) ;
}
2019-07-10 11:57:53 -07:00
bool CCmpUnitMotion : : ComputeTargetPosition ( CFixedVector2D & out , const MoveRequest & moveRequest ) const
2010-04-29 16:36:05 -07:00
{
2019-07-10 11:57:53 -07:00
if ( moveRequest . m_Type = = MoveRequest : : POINT )
2019-07-02 00:29:31 -07:00
{
2019-07-10 11:57:53 -07:00
out = moveRequest . m_Position ;
2019-07-02 00:29:31 -07:00
return true ;
}
2010-11-30 04:31:54 -08:00
2019-09-10 11:11:07 -07:00
CmpPtr < ICmpPosition > cmpTargetPosition ( GetSimContext ( ) , moveRequest . m_Entity ) ;
if ( ! cmpTargetPosition | | ! cmpTargetPosition - > IsInWorld ( ) )
2010-11-30 04:31:54 -08:00
return false ;
2010-04-29 16:36:05 -07:00
2019-07-10 11:57:53 -07:00
if ( moveRequest . m_Type = = MoveRequest : : OFFSET )
2010-11-30 04:31:54 -08:00
{
// There is an offset, so compute it relative to orientation
2019-09-10 11:11:07 -07:00
entity_angle_t angle = cmpTargetPosition - > GetRotation ( ) . Y ;
2019-07-10 11:57:53 -07:00
CFixedVector2D offset = moveRequest . GetOffset ( ) . Rotate ( angle ) ;
2019-09-10 11:11:07 -07:00
out = cmpTargetPosition - > GetPosition2D ( ) + offset ;
2010-11-30 04:31:54 -08:00
}
2019-06-09 04:18:06 -07:00
else
2019-07-03 11:09:31 -07:00
{
2019-09-10 11:11:07 -07:00
out = cmpTargetPosition - > GetPosition2D ( ) ;
2021-01-27 11:13:29 -08:00
// Because units move one-at-a-time and pathing is asynchronous, we need to account for target movement,
// if we are computing this during the MT_Motion* part of the turn.
2021-01-27 07:11:57 -08:00
// If our entity ID is lower, we move first, and so we need to add a predicted movement to compute a path for next turn.
// If our entity ID is higher, the target has already moved, so we can just use the position directly.
// TODO: This does not really aim many turns in advance, with orthogonal trajectories it probably should.
2019-07-10 11:57:53 -07:00
CmpPtr < ICmpUnitMotion > cmpUnitMotion ( GetSimContext ( ) , moveRequest . m_Entity ) ;
2021-01-27 11:13:29 -08:00
bool needInterpolation = cmpUnitMotion & & cmpUnitMotion - > IsMoveRequested ( ) & & m_InMotionMessage ;
if ( needInterpolation & & GetEntityId ( ) < moveRequest . m_Entity )
2019-09-10 11:11:07 -07:00
{
2021-01-27 09:44:31 -08:00
// Add predicted movement.
CFixedVector2D tempPos = out + ( out - cmpTargetPosition - > GetPreviousPosition2D ( ) ) ;
2019-09-10 11:11:07 -07:00
CmpPtr < ICmpPosition > cmpPosition ( GetEntityHandle ( ) ) ;
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
return true ; // Still return true since we don't need a position for the target to have one.
2021-01-27 09:44:31 -08:00
// Fleeing fix: if we anticipate the target to go through us, we'll suddenly turn around, which is bad.
// Pretend that the target is still behind us in those cases.
if ( m_MoveRequest . m_MinRange > fixed : : Zero ( ) )
{
if ( ( out - cmpPosition - > GetPosition2D ( ) ) . RelativeOrientation ( tempPos - cmpPosition - > GetPosition2D ( ) ) > = 0 )
out = tempPos ;
}
else
out = tempPos ;
}
2021-01-27 11:13:29 -08:00
else if ( needInterpolation & & GetEntityId ( ) > moveRequest . m_Entity )
2021-01-27 09:44:31 -08:00
{
CmpPtr < ICmpPosition > cmpPosition ( GetEntityHandle ( ) ) ;
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
return true ; // Still return true since we don't need a position for the target to have one.
2019-09-10 11:11:07 -07:00
2021-01-27 09:44:31 -08:00
// Fleeing fix: opposite to above, check if our target has travelled through us already this turn.
CFixedVector2D tempPos = out - ( out - cmpTargetPosition - > GetPreviousPosition2D ( ) ) ;
if ( m_MoveRequest . m_MinRange > fixed : : Zero ( ) & &
( out - cmpPosition - > GetPosition2D ( ) ) . RelativeOrientation ( tempPos - cmpPosition - > GetPosition2D ( ) ) < 0 )
2019-09-10 11:11:07 -07:00
out = tempPos ;
}
2019-07-03 11:09:31 -07:00
}
2010-11-30 04:31:54 -08:00
return true ;
2010-04-29 16:36:05 -07:00
}
2010-02-10 11:28:46 -08:00
2019-07-02 00:29:31 -07:00
bool CCmpUnitMotion : : TryGoingStraightToTarget ( const CFixedVector2D & from )
2010-04-29 16:36:05 -07:00
{
2010-11-30 04:31:54 -08:00
CFixedVector2D targetPos ;
if ( ! ComputeTargetPosition ( targetPos ) )
return false ;
// Fail if the target is too far away
if ( ( targetPos - from ) . CompareLength ( DIRECT_PATH_RANGE ) > 0 )
return false ;
2013-09-11 13:41:53 -07:00
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSystemEntity ( ) ) ;
2012-02-07 18:46:15 -08:00
if ( ! cmpPathfinder )
2010-11-30 04:31:54 -08:00
return false ;
// Move the goal to match the target entity's new position
2019-07-10 11:57:53 -07:00
PathGoal goal ;
2019-07-12 09:16:13 -07:00
if ( ! ComputeGoal ( goal , m_MoveRequest ) )
return false ;
2010-11-30 04:31:54 -08:00
goal . x = targetPos . X ;
goal . z = targetPos . Y ;
// (we ignore changes to the target's rotation, since only buildings are
// square and buildings don't move)
// Find the point on the goal shape that we should head towards
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
CFixedVector2D goalPos = goal . NearestPointOnGoal ( from ) ;
2010-11-30 04:31:54 -08:00
UnitMotion - Fix a rare pathfinding issues where units tried going straight through walls, and make sure a long path is computed even when the target is within short path or direct path range.
This fixes a regression introduced by 055c848c1a: when an entity is
ordered to move to a target within short path distance (or direct path
distance), it no longer computed at least one long path, which meant it
could be stuck forever if the target was not actually reachable (such as
behind a wall).
To fix this, compute a long path after 3 failed computations, which
should result in a delay of 1-3 turns. The previous code did this after
1 failed try - the decision to make it 3 is mostly based on the idea
that in most cases, being stuck means we ran into units, not that we
were ordered somewhere close. Should there be complaints, it could be
lowered to 2 or 1.
This fixes a second issue, reported in #4473: units sometimes get stuck,
particularly when trying to garrison a turret from 'inside' the walls.
The issue is that the turret is not accessible via the inside as its
obstruction + garrison range is blocked by the surrounding walls.
However, as introduced by 6e05a00929, TryGoingStraightToTargetEntity
ignores all entities with the obstruction group of the target (the
reason for this being that otherwise it would never succeed, since the
line towards the target would likely go through the target).
For walls and formations, this means ignoring possibly too many
entities, and in the case of #4473, ignoring wall pieces. The unit thus
mistakenly thought it could direct-path to the turret, and got stuck.
To fix this, we can ignore specifically the targeted entity's
obstruction tag. This can be considered a fix to 6e05a00929.
temple accepted an earlier version of this patch (specifically elexis'
version).
Fixes #4473
Based on a patch by: elexis
Differential Revision: https://code.wildfiregames.com/D1424
This was SVN commit r22533.
2019-07-22 23:18:07 -07:00
// Check if there's any collisions on that route.
// For entity goals, skip only the specific obstruction tag or with e.g. walls we might ignore too many entities.
ICmpObstructionManager : : tag_t specificIgnore ;
if ( m_MoveRequest . m_Type = = MoveRequest : : ENTITY )
{
CmpPtr < ICmpObstruction > cmpTargetObstruction ( GetSimContext ( ) , m_MoveRequest . m_Entity ) ;
if ( cmpTargetObstruction )
specificIgnore = cmpTargetObstruction - > GetObstruction ( ) ;
}
if ( specificIgnore . valid ( ) )
{
if ( ! cmpPathfinder - > CheckMovement ( SkipTagObstructionFilter ( specificIgnore ) , from . X , from . Y , goalPos . X , goalPos . Y , m_Clearance , m_PassClass ) )
return false ;
}
else if ( ! cmpPathfinder - > CheckMovement ( GetObstructionFilter ( ) , from . X , from . Y , goalPos . X , goalPos . Y , m_Clearance , m_PassClass ) )
2010-11-30 04:31:54 -08:00
return false ;
UnitMotion - Fix a rare pathfinding issues where units tried going straight through walls, and make sure a long path is computed even when the target is within short path or direct path range.
This fixes a regression introduced by 055c848c1a: when an entity is
ordered to move to a target within short path distance (or direct path
distance), it no longer computed at least one long path, which meant it
could be stuck forever if the target was not actually reachable (such as
behind a wall).
To fix this, compute a long path after 3 failed computations, which
should result in a delay of 1-3 turns. The previous code did this after
1 failed try - the decision to make it 3 is mostly based on the idea
that in most cases, being stuck means we ran into units, not that we
were ordered somewhere close. Should there be complaints, it could be
lowered to 2 or 1.
This fixes a second issue, reported in #4473: units sometimes get stuck,
particularly when trying to garrison a turret from 'inside' the walls.
The issue is that the turret is not accessible via the inside as its
obstruction + garrison range is blocked by the surrounding walls.
However, as introduced by 6e05a00929, TryGoingStraightToTargetEntity
ignores all entities with the obstruction group of the target (the
reason for this being that otherwise it would never succeed, since the
line towards the target would likely go through the target).
For walls and formations, this means ignoring possibly too many
entities, and in the case of #4473, ignoring wall pieces. The unit thus
mistakenly thought it could direct-path to the turret, and got stuck.
To fix this, we can ignore specifically the targeted entity's
obstruction tag. This can be considered a fix to 6e05a00929.
temple accepted an earlier version of this patch (specifically elexis'
version).
Fixes #4473
Based on a patch by: elexis
Differential Revision: https://code.wildfiregames.com/D1424
This was SVN commit r22533.
2019-07-22 23:18:07 -07:00
2010-11-30 04:31:54 -08:00
// That route is okay, so update our path
m_LongPath . m_Waypoints . clear ( ) ;
m_ShortPath . m_Waypoints . clear ( ) ;
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
m_ShortPath . m_Waypoints . emplace_back ( Waypoint { goalPos . X , goalPos . Y } ) ;
2010-11-30 04:31:54 -08:00
return true ;
}
2019-07-10 11:57:53 -07:00
bool CCmpUnitMotion : : PathingUpdateNeeded ( const CFixedVector2D & from ) const
2010-11-30 04:31:54 -08:00
{
2019-07-03 11:06:53 -07:00
if ( m_MoveRequest . m_Type = = MoveRequest : : NONE )
return false ;
2010-11-30 04:31:54 -08:00
CFixedVector2D targetPos ;
if ( ! ComputeTargetPosition ( targetPos ) )
return false ;
2021-01-27 11:13:29 -08:00
if ( m_FollowKnownImperfectPathCountdown > 0 & & ( ! m_LongPath . m_Waypoints . empty ( ) | | ! m_ShortPath . m_Waypoints . empty ( ) ) )
2019-07-14 04:08:15 -07:00
return false ;
2019-07-03 11:06:53 -07:00
if ( PossiblyAtDestination ( ) )
2010-11-30 04:31:54 -08:00
return false ;
2019-07-03 11:06:53 -07:00
// Get the obstruction shape and translate it where we estimate the target to be.
ICmpObstructionManager : : ObstructionSquare estimatedTargetShape ;
if ( m_MoveRequest . m_Type = = MoveRequest : : ENTITY )
{
CmpPtr < ICmpObstruction > cmpTargetObstruction ( GetSimContext ( ) , m_MoveRequest . m_Entity ) ;
if ( cmpTargetObstruction )
cmpTargetObstruction - > GetObstructionSquare ( estimatedTargetShape ) ;
}
estimatedTargetShape . x = targetPos . X ;
estimatedTargetShape . z = targetPos . Y ;
CmpPtr < ICmpObstruction > cmpObstruction ( GetEntityHandle ( ) ) ;
ICmpObstructionManager : : ObstructionSquare shape ;
if ( cmpObstruction )
cmpObstruction - > GetObstructionSquare ( shape ) ;
2010-11-30 04:31:54 -08:00
2019-07-03 11:06:53 -07:00
// Translate our own obstruction shape to our last waypoint or our current position, lacking that.
if ( m_LongPath . m_Waypoints . empty ( ) & & m_ShortPath . m_Waypoints . empty ( ) )
2011-06-24 05:35:15 -07:00
{
2019-07-03 11:06:53 -07:00
shape . x = from . X ;
shape . z = from . Y ;
}
else
{
2019-07-14 04:08:15 -07:00
const Waypoint & lastWaypoint = m_LongPath . m_Waypoints . empty ( ) ? m_ShortPath . m_Waypoints . front ( ) : m_LongPath . m_Waypoints . front ( ) ;
2019-07-03 11:06:53 -07:00
shape . x = lastWaypoint . x ;
shape . z = lastWaypoint . z ;
2011-06-24 05:35:15 -07:00
}
2019-07-03 11:06:53 -07:00
CmpPtr < ICmpObstructionManager > cmpObstructionManager ( GetSystemEntity ( ) ) ;
ENSURE ( cmpObstructionManager ) ;
2019-07-14 04:08:15 -07:00
// Increase the ranges with distance, to avoid recomputing every turn against units that are moving and far-away for example.
entity_pos_t distance = ( from - CFixedVector2D ( estimatedTargetShape . x , estimatedTargetShape . z ) ) . Length ( ) ;
// TODO: it could be worth computing this based on time to collision instead of linear distance.
entity_pos_t minRange = std : : max ( m_MoveRequest . m_MinRange - distance / TARGET_UNCERTAINTY_MULTIPLIER , entity_pos_t : : Zero ( ) ) ;
entity_pos_t maxRange = m_MoveRequest . m_MaxRange < entity_pos_t : : Zero ( ) ? m_MoveRequest . m_MaxRange :
m_MoveRequest . m_MaxRange + distance / TARGET_UNCERTAINTY_MULTIPLIER ;
if ( cmpObstructionManager - > AreShapesInRange ( shape , estimatedTargetShape , minRange , maxRange , false ) )
2019-07-03 11:06:53 -07:00
return false ;
2010-11-30 04:31:54 -08:00
return true ;
}
2011-06-09 12:44:40 -07:00
void CCmpUnitMotion : : FaceTowardsPoint ( entity_pos_t x , entity_pos_t z )
{
2013-09-11 13:41:53 -07:00
CmpPtr < ICmpPosition > cmpPosition ( GetEntityHandle ( ) ) ;
2012-02-07 18:46:15 -08:00
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
2011-06-09 12:44:40 -07:00
return ;
CFixedVector2D pos = cmpPosition - > GetPosition2D ( ) ;
FaceTowardsPointFromPos ( pos , x , z ) ;
}
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
void CCmpUnitMotion : : FaceTowardsPointFromPos ( const CFixedVector2D & pos , entity_pos_t x , entity_pos_t z )
2010-11-30 04:31:54 -08:00
{
CFixedVector2D target ( x , z ) ;
CFixedVector2D offset = target - pos ;
if ( ! offset . IsZero ( ) )
2010-01-09 11:20:14 -08:00
{
2010-04-29 16:36:05 -07:00
entity_angle_t angle = atan2_approx ( offset . X , offset . Y ) ;
2010-01-30 05:11:58 -08:00
2013-09-11 13:41:53 -07:00
CmpPtr < ICmpPosition > cmpPosition ( GetEntityHandle ( ) ) ;
2012-02-07 18:46:15 -08:00
if ( ! cmpPosition )
2010-01-30 05:11:58 -08:00
return ;
2010-04-29 16:36:05 -07:00
cmpPosition - > TurnTo ( angle ) ;
}
}
2010-01-30 05:11:58 -08:00
2019-07-10 11:41:17 -07:00
// The pathfinder cannot go to "rounded rectangles" goals, which are what happens with square targets and a non-null range.
// Depending on what the best approximation is, we either pretend the target is a circle or a square.
// One needs to be careful that the approximated geometry will be in the range.
bool CCmpUnitMotion : : ShouldTreatTargetAsCircle ( entity_pos_t range , entity_pos_t circleRadius ) const
{
// Given a square, plus a target range we should reach, the shape at that distance
// is a round-cornered square which we can approximate as either a circle or as a square.
// Previously, we used the shape that minimized the worst-case error.
// However that is unsage in some situations. So let's be less clever and
// just check if our range is at least three times bigger than the circleradius
return ( range > circleRadius * 3 ) ;
}
bool CCmpUnitMotion : : ComputeGoal ( PathGoal & out , const MoveRequest & moveRequest ) const
{
if ( moveRequest . m_Type = = MoveRequest : : NONE )
return false ;
CmpPtr < ICmpPosition > cmpPosition ( GetEntityHandle ( ) ) ;
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
return false ;
CFixedVector2D pos = cmpPosition - > GetPosition2D ( ) ;
CFixedVector2D targetPosition ;
2019-07-10 11:57:53 -07:00
if ( ! ComputeTargetPosition ( targetPosition , moveRequest ) )
2019-07-10 11:41:17 -07:00
return false ;
ICmpObstructionManager : : ObstructionSquare targetObstruction ;
if ( moveRequest . m_Type = = MoveRequest : : ENTITY )
{
CmpPtr < ICmpObstruction > cmpTargetObstruction ( GetSimContext ( ) , moveRequest . m_Entity ) ;
if ( cmpTargetObstruction )
cmpTargetObstruction - > GetObstructionSquare ( targetObstruction ) ;
}
targetObstruction . x = targetPosition . X ;
targetObstruction . z = targetPosition . Y ;
ICmpObstructionManager : : ObstructionSquare obstruction ;
CmpPtr < ICmpObstruction > cmpObstruction ( GetEntityHandle ( ) ) ;
if ( cmpObstruction )
cmpObstruction - > GetObstructionSquare ( obstruction ) ;
else
{
obstruction . x = pos . X ;
obstruction . z = pos . Y ;
}
CmpPtr < ICmpObstructionManager > cmpObstructionManager ( GetSystemEntity ( ) ) ;
ENSURE ( cmpObstructionManager ) ;
entity_pos_t distance = cmpObstructionManager - > DistanceBetweenShapes ( obstruction , targetObstruction ) ;
out . x = targetObstruction . x ;
out . z = targetObstruction . z ;
out . hw = targetObstruction . hw ;
out . hh = targetObstruction . hh ;
out . u = targetObstruction . u ;
out . v = targetObstruction . v ;
if ( moveRequest . m_MinRange > fixed : : Zero ( ) | | moveRequest . m_MaxRange > fixed : : Zero ( ) | |
targetObstruction . hw > fixed : : Zero ( ) )
out . type = PathGoal : : SQUARE ;
else
{
out . type = PathGoal : : POINT ;
return true ;
}
2019-07-17 11:08:47 -07:00
entity_pos_t circleRadius = CFixedVector2D ( targetObstruction . hw , targetObstruction . hh ) . Length ( ) ;
// TODO: because we cannot move to rounded rectangles, we have to make conservative approximations.
// This means we might end up in a situation where cons(max-range) < min range < max range < cons(min-range)
// When going outside of the min-range or inside the max-range, the unit will still go through the correct range
// but if it moves fast enough, this might not be picked up by PossiblyAtDestination().
// Fixing this involves moving to rounded rectangles, or checking more often in PerformMove().
// In the meantime, one should avoid that 'Speed over a turn' > MaxRange - MinRange, in case where
// min-range is not 0 and max-range is not infinity.
2019-07-10 11:41:17 -07:00
if ( distance < moveRequest . m_MinRange )
{
2020-11-26 07:51:46 -08:00
// Distance checks are nearest edge to nearest edge, so we need to account for our clearance
// and we must make sure diagonals also fit so multiply by slightly more than sqrt(2)
entity_pos_t goalDistance = moveRequest . m_MinRange + m_Clearance * 3 / 2 ;
2019-07-10 11:41:17 -07:00
if ( ShouldTreatTargetAsCircle ( moveRequest . m_MinRange , circleRadius ) )
{
2019-07-17 11:08:47 -07:00
// We are safely away from the obstruction itself if we are away from the circumscribing circle
2019-07-10 11:41:17 -07:00
out . type = PathGoal : : INVERTED_CIRCLE ;
2020-11-26 07:51:46 -08:00
out . hw = circleRadius + goalDistance ;
2019-07-10 11:41:17 -07:00
}
else
{
out . type = PathGoal : : INVERTED_SQUARE ;
out . hw = targetObstruction . hw + goalDistance ;
out . hh = targetObstruction . hh + goalDistance ;
}
}
else if ( moveRequest . m_MaxRange > = fixed : : Zero ( ) & & distance > moveRequest . m_MaxRange )
{
if ( ShouldTreatTargetAsCircle ( moveRequest . m_MaxRange , circleRadius ) )
{
entity_pos_t goalDistance = moveRequest . m_MaxRange ;
2019-07-17 11:08:47 -07:00
// We must go in-range of the inscribed circle, not the circumscribing circle.
circleRadius = std : : min ( targetObstruction . hw , targetObstruction . hh ) ;
2019-07-10 11:41:17 -07:00
out . type = PathGoal : : CIRCLE ;
out . hw = circleRadius + goalDistance ;
}
else
{
// The target is large relative to our range, so treat it as a square and
// get close enough that the diagonals come within range
entity_pos_t goalDistance = moveRequest . m_MaxRange * 2 / 3 ; // multiply by slightly less than 1/sqrt(2)
out . type = PathGoal : : SQUARE ;
entity_pos_t delta = std : : max ( goalDistance , m_Clearance + entity_pos_t : : FromInt ( TERRAIN_TILE_SIZE ) / 16 ) ; // ensure it's far enough to not intersect the building itself
out . hw = targetObstruction . hw + delta ;
out . hh = targetObstruction . hh + delta ;
}
}
// Do nothing in particular in case we are already in range.
return true ;
}
2010-11-30 04:31:54 -08:00
2019-07-28 03:51:12 -07:00
void CCmpUnitMotion : : ComputePathToGoal ( const CFixedVector2D & from , const PathGoal & goal )
2010-11-30 04:31:54 -08:00
{
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
# if DISABLE_PATHFINDER
{
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSimContext ( ) , SYSTEM_ENTITY ) ;
2015-10-16 10:14:39 -07:00
CFixedVector2D goalPos = m_FinalGoal . NearestPointOnGoal ( from ) ;
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
m_LongPath . m_Waypoints . clear ( ) ;
m_ShortPath . m_Waypoints . clear ( ) ;
m_ShortPath . m_Waypoints . emplace_back ( Waypoint { goalPos . X , goalPos . Y } ) ;
return ;
}
# endif
2019-07-02 00:29:31 -07:00
// If the target is close and we can reach it in a straight line,
// then we'll just go along the straight line instead of computing a path.
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
if ( ! ShouldAlternatePathfinder ( ) & & TryGoingStraightToTarget ( from ) )
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
return ;
2010-11-30 04:31:54 -08:00
// Otherwise we need to compute a path.
2015-09-05 11:20:08 -07:00
// If it's close then just do a short path, not a long path
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
// TODO: If it's close on the opposite side of a river then we really
// need a long path, so we shouldn't simply check linear distance
2015-11-12 09:23:50 -08:00
// the check is arbitrary but should be a reasonably small distance.
2020-12-13 07:25:16 -08:00
// We want to occasionally compute a long path if we're computing short-paths, because the short path domain
// is bounded and thus it can't around very large static obstacles.
// Likewise, we want to compile a short-path occasionally when the target is far because we might be stuck
// on a navcell surrounded by impassable navcells, but the short-pathfinder could move us out of there.
bool shortPath = InShortPathRange ( goal , from ) ;
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
if ( ShouldAlternatePathfinder ( ) )
2020-12-13 07:25:16 -08:00
shortPath = ! shortPath ;
if ( shortPath )
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
{
m_LongPath . m_Waypoints . clear ( ) ;
2021-01-19 11:09:55 -08:00
// Extend the range so that our first path is probably valid.
2015-11-11 11:06:07 -08:00
RequestShortPath ( from , goal , true ) ;
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
}
else
2019-08-04 01:16:09 -07:00
{
m_ShortPath . m_Waypoints . clear ( ) ;
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
RequestLongPath ( from , goal ) ;
2019-08-04 01:16:09 -07:00
}
2010-11-30 04:31:54 -08:00
}
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
void CCmpUnitMotion : : RequestLongPath ( const CFixedVector2D & from , const PathGoal & goal )
2010-11-30 04:31:54 -08:00
{
2013-09-11 13:41:53 -07:00
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSystemEntity ( ) ) ;
2012-02-07 18:46:15 -08:00
if ( ! cmpPathfinder )
2010-11-30 04:31:54 -08:00
return ;
2015-11-11 04:49:24 -08:00
// this is by how much our waypoints will be apart at most.
// this value here seems sensible enough.
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
PathGoal improvedGoal = goal ;
2015-11-11 04:49:24 -08:00
improvedGoal . maxdist = SHORT_PATH_MIN_SEARCH_RANGE - entity_pos_t : : FromInt ( 1 ) ;
2010-11-30 04:31:54 -08:00
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
cmpPathfinder - > SetDebugPath ( from . X , from . Y , improvedGoal , m_PassClass ) ;
2019-07-12 07:49:32 -07:00
m_ExpectedPathTicket . m_Type = Ticket : : LONG_PATH ;
m_ExpectedPathTicket . m_Ticket = cmpPathfinder - > ComputePathAsync ( from . X , from . Y , improvedGoal , m_PassClass , GetEntityId ( ) ) ;
2010-11-30 04:31:54 -08:00
}
2021-01-19 11:09:55 -08:00
void CCmpUnitMotion : : RequestShortPath ( const CFixedVector2D & from , const PathGoal & goal , bool extendRange )
2010-11-30 04:31:54 -08:00
{
2013-09-11 13:41:53 -07:00
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSystemEntity ( ) ) ;
2012-02-07 18:46:15 -08:00
if ( ! cmpPathfinder )
2010-11-30 04:31:54 -08:00
return ;
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
entity_pos_t searchRange = ShortPathSearchRange ( ) ;
2021-01-19 11:09:55 -08:00
if ( extendRange )
{
CFixedVector2D dist ( from . X - goal . x , from . Y - goal . z ) ;
if ( dist . CompareLength ( searchRange - entity_pos_t : : FromInt ( 1 ) ) > = 0 )
searchRange = dist . Length ( ) + fixed : : FromInt ( 1 ) ;
}
2016-03-12 05:44:51 -08:00
2019-07-12 07:49:32 -07:00
m_ExpectedPathTicket . m_Type = Ticket : : SHORT_PATH ;
2021-01-19 11:09:55 -08:00
m_ExpectedPathTicket . m_Ticket = cmpPathfinder - > ComputeShortPathAsync ( from . X , from . Y , m_Clearance , searchRange , goal , m_PassClass , true , GetGroup ( ) , GetEntityId ( ) ) ;
2010-11-30 04:31:54 -08:00
}
2019-07-28 03:51:12 -07:00
bool CCmpUnitMotion : : MoveTo ( MoveRequest request )
2010-11-30 04:31:54 -08:00
{
2019-07-28 03:51:12 -07:00
PROFILE ( " MoveTo " ) ;
2019-07-10 11:57:53 -07:00
2020-12-02 10:11:02 -08:00
if ( request . m_MinRange = = request . m_MaxRange & & ! request . m_MinRange . IsZero ( ) )
LOGWARNING ( " MaxRange must be larger than MinRange; See CCmpUnitMotion.cpp for more information " ) ;
2013-09-11 13:41:53 -07:00
CmpPtr < ICmpPosition > cmpPosition ( GetEntityHandle ( ) ) ;
2012-02-07 18:46:15 -08:00
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
2010-04-29 16:36:05 -07:00
return false ;
2019-07-10 11:57:53 -07:00
PathGoal goal ;
2019-07-28 03:51:12 -07:00
if ( ! ComputeGoal ( goal , request ) )
2019-07-10 11:57:53 -07:00
return false ;
2019-07-28 03:51:12 -07:00
m_MoveRequest = request ;
Improve UnitMotion behaviour when working around obstructions.
This improves behaviour when units need to go around a concave obstacle.
They would tend to clump inside the 'dead-end' before realising they
needed to go around. This was rather easy to trigger on Acropolis. See
included Unit Motion Integration Test.
The cause is the logic that removed the next long waypoint when
obstructed. While that behaviour is desirable, removing too many
waypoints means the unit tries to short-path, using a small domain
range, to a goal that's impassable, meaning they go as close as they can
in Euclidian distance, i.e. towards the dead end.
This changes that behaviour by only deleting waypoints within a certain
distance from the entity, scaling with search-space range. It's tricky
to find a good compromise between performance and behaviour here, but
the values I've picked seem OK.
However, the fact that the entity would ultimately remove all waypoints
and thus trigger a full path recomputation was actually a feature,
inherited from D2754 / 892f97743b. This diff therefore handles that
explicitly, doing so on a more regular basis to behave better overall.
As a further cleanup, "m_FailedPathComputations" is incremented in
HandleObstructedMove, as it is quite possible to never increment it in
PathResult despite not getting actionnable paths. This thus renames it
to m_FailedMovements, and uses the opportunity to clean up PathResult(),
by only having one path for both short and long-range paths. Further,
PathResult now does not immediately request new paths, leaving that to
Move(), to avoid requesting transient paths that aren't actionnable.
This also makes it possible to revert 9e41ff39fc. It requires increasing
the MAX_FAILED variable, or more units get stuck as they reach the max
more often.
The search-space expansion is slightly slowed, and with a little more
delay, as a performance optimisation. From testing, this doesn't impact
real movement much as units short paths tend to be invalidated by the
next turn, as other units move, anyways.
Clarify comment around the vertex-pathfinder search-space bounds hack,
and ensure it isn't used for the very worst cases of units being stuck,
as it could be a pessimisation then.
Finally, this explicits a 2011 hack where if the long-pathfinder fails
to return a valid path the goal's center is used directly. This happens
when the goal is unreachable to the long-pathfinder, which may be
because it is actually unreachable or because only the short-pathfinder
can reach it. In those situations, the hack allows a last-ditch attempt
at reaching it before failing to move entirely. Performance wise, this
is faster overall for actually unreachable goals, since it skips all the
intermediate steps. For reachable goals, it might be occasionally
slower, but that case is quite rare (certainly rarer than unreachable
goals).
Reported By: Angen
Fixes #5795
Differential Revision: https://code.wildfiregames.com/D3203
This was SVN commit r24429.
2020-12-19 02:45:07 -08:00
m_FailedMovements = 0 ;
2020-08-03 05:02:24 -07:00
m_FollowKnownImperfectPathCountdown = 0 ;
2010-04-29 16:36:05 -07:00
2019-07-28 03:51:12 -07:00
ComputePathToGoal ( cmpPosition - > GetPosition2D ( ) , goal ) ;
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
return true ;
2010-07-18 08:19:49 -07:00
}
2010-04-29 16:36:05 -07:00
2020-08-03 05:02:24 -07:00
bool CCmpUnitMotion : : IsTargetRangeReachable ( entity_id_t target , entity_pos_t minRange , entity_pos_t maxRange )
{
CmpPtr < ICmpPosition > cmpPosition ( GetEntityHandle ( ) ) ;
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
return false ;
MoveRequest request ( target , minRange , maxRange ) ;
PathGoal goal ;
if ( ! ComputeGoal ( goal , request ) )
return false ;
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSimContext ( ) , SYSTEM_ENTITY ) ;
CFixedVector2D pos = cmpPosition - > GetPosition2D ( ) ;
return cmpPathfinder - > IsGoalReachable ( pos . X , pos . Y , goal , m_PassClass ) ;
}
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
void CCmpUnitMotion : : RenderPath ( const WaypointPath & path , std : : vector < SOverlayLine > & lines , CColor color )
2010-04-29 16:36:05 -07:00
{
2010-05-27 16:23:53 -07:00
bool floating = false ;
2013-09-11 13:41:53 -07:00
CmpPtr < ICmpPosition > cmpPosition ( GetEntityHandle ( ) ) ;
2012-02-07 18:46:15 -08:00
if ( cmpPosition )
2017-09-18 02:54:54 -07:00
floating = cmpPosition - > CanFloat ( ) ;
2010-05-27 16:23:53 -07:00
2010-04-29 16:36:05 -07:00
lines . clear ( ) ;
std : : vector < float > waypointCoords ;
for ( size_t i = 0 ; i < path . m_Waypoints . size ( ) ; + + i )
{
float x = path . m_Waypoints [ i ] . x . ToFloat ( ) ;
float z = path . m_Waypoints [ i ] . z . ToFloat ( ) ;
waypointCoords . push_back ( x ) ;
waypointCoords . push_back ( z ) ;
lines . push_back ( SOverlayLine ( ) ) ;
lines . back ( ) . m_Color = color ;
2010-05-27 16:23:53 -07:00
SimRender : : ConstructSquareOnGround ( GetSimContext ( ) , x , z , 1.0f , 1.0f , 0.0f , lines . back ( ) , floating ) ;
2010-04-29 16:36:05 -07:00
}
2016-02-15 11:30:17 -08:00
float x = cmpPosition - > GetPosition2D ( ) . X . ToFloat ( ) ;
float z = cmpPosition - > GetPosition2D ( ) . Y . ToFloat ( ) ;
waypointCoords . push_back ( x ) ;
waypointCoords . push_back ( z ) ;
2010-04-29 16:36:05 -07:00
lines . push_back ( SOverlayLine ( ) ) ;
lines . back ( ) . m_Color = color ;
2010-05-27 16:23:53 -07:00
SimRender : : ConstructLineOnGround ( GetSimContext ( ) , waypointCoords , lines . back ( ) , floating ) ;
2010-04-29 16:36:05 -07:00
}
2010-05-01 02:48:39 -07:00
void CCmpUnitMotion : : RenderSubmit ( SceneCollector & collector )
2010-04-29 16:36:05 -07:00
{
if ( ! m_DebugOverlayEnabled )
return ;
2015-03-15 16:59:48 -07:00
RenderPath ( m_LongPath , m_DebugOverlayLongPathLines , OVERLAY_COLOR_LONG_PATH ) ;
RenderPath ( m_ShortPath , m_DebugOverlayShortPathLines , OVERLAY_COLOR_SHORT_PATH ) ;
2010-11-30 04:31:54 -08:00
for ( size_t i = 0 ; i < m_DebugOverlayLongPathLines . size ( ) ; + + i )
collector . Submit ( & m_DebugOverlayLongPathLines [ i ] ) ;
2010-04-29 16:36:05 -07:00
for ( size_t i = 0 ; i < m_DebugOverlayShortPathLines . size ( ) ; + + i )
collector . Submit ( & m_DebugOverlayShortPathLines [ i ] ) ;
2010-01-09 11:20:14 -08:00
}