1
0
Fork 0

Fix #12228, Fix #12231, Fix #12333: Ship pathfinder always considers reversing

pull/12335/head
Koen Bussemaker 2024-03-18 23:10:52 +01:00
parent e8c78df39e
commit a757935d82
3 changed files with 77 additions and 71 deletions

View File

@ -22,6 +22,8 @@ public:
protected:
TileIndex m_orgTile; ///< origin tile
TrackdirBits m_orgTrackdirs; ///< origin trackdir mask
TrackdirBits m_orgReverseTrackdirs; ///< origin reverse trackdir mask
int m_reverse_penalty; ///< penalty applied to reverse directions
/** to access inherited path finder */
inline Tpf &Yapf()
@ -31,22 +33,32 @@ protected:
public:
/** Set origin tile / trackdir mask */
void SetOrigin(TileIndex tile, TrackdirBits trackdirs)
void SetOrigin(TileIndex tile, TrackdirBits trackdirs, TrackdirBits reverse_trackdirs = TRACKDIR_BIT_NONE, int reverse_penalty = 0)
{
assert((trackdirs & reverse_trackdirs) == TRACKDIR_BIT_NONE); // Can't set single trackdirs as both regular and reverse origin.
m_orgTile = tile;
m_orgTrackdirs = trackdirs;
m_orgReverseTrackdirs = reverse_trackdirs;
m_reverse_penalty = reverse_penalty;
}
/** Called when YAPF needs to place origin nodes into open list */
void PfSetStartupNodes()
{
bool is_choice = (KillFirstBit(m_orgTrackdirs) != TRACKDIR_BIT_NONE);
for (TrackdirBits tdb = m_orgTrackdirs; tdb != TRACKDIR_BIT_NONE; tdb = KillFirstBit(tdb)) {
Trackdir td = (Trackdir)FindFirstBit(tdb);
const bool is_choice = CountBits(m_orgTrackdirs | m_orgReverseTrackdirs) > 1;
for (Trackdir td : SetTrackdirBitIterator(m_orgTrackdirs)) {
Node &n1 = Yapf().CreateNewNode();
n1.Set(nullptr, m_orgTile, td, is_choice);
Yapf().AddStartupNode(n1);
}
for (Trackdir td : SetTrackdirBitIterator(m_orgReverseTrackdirs)) {
Node &n2 = Yapf().CreateNewNode();
n2.Set(nullptr, m_orgTile, td, is_choice);
n2.m_cost = m_reverse_penalty;
Yapf().AddStartupNode(n2);
}
}
};

View File

@ -21,7 +21,7 @@
constexpr int NUMBER_OR_WATER_REGIONS_LOOKAHEAD = 4;
constexpr int MAX_SHIP_PF_NODES = (NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1) * WATER_REGION_NUMBER_OF_TILES * 4; // 4 possible exit dirs per tile.
constexpr int SHIP_REVERSAL_PENALTY = YAPF_TILE_LENGTH * 10;
constexpr int SHIP_LOST_PATH_LENGTH = 8; // The length of the (aimless) path assigned when a ship is lost.
template <class Types>
@ -172,6 +172,14 @@ public:
return 'w';
}
/** Returns a random trackdir out of a set of trackdirs. */
static Trackdir GetRandomTrackdir(TrackdirBits trackdirs)
{
const int strip_amount = RandomRange(CountBits(trackdirs));
for (int s = 0; s < strip_amount; ++s) RemoveFirstTrackdir(&trackdirs);
return FindFirstTrackdir(trackdirs);
}
/** Returns a random tile/trackdir that can be reached from the current tile/trackdir, or tile/INVALID_TRACK if none is available. */
static std::pair<TileIndex, Trackdir> GetRandomFollowUpTileTrackdir(const Ship *v, TileIndex tile, Trackdir dir)
{
@ -180,17 +188,15 @@ public:
TrackdirBits dirs = follower.m_new_td_bits;
const TrackdirBits dirs_without_90_degree = dirs & ~TrackdirCrossesTrackdirs(dir);
if (dirs_without_90_degree != TRACKDIR_BIT_NONE) dirs = dirs_without_90_degree;
const int strip_amount = RandomRange(CountBits(dirs));
for (int s = 0; s < strip_amount; ++s) RemoveFirstTrackdir(&dirs);
return { follower.m_new_tile, FindFirstTrackdir(dirs) };
return { follower.m_new_tile, GetRandomTrackdir(dirs) };
}
return { follower.m_new_tile, INVALID_TRACKDIR };
}
/** Creates a random path, avoids 90 degree turns. */
static Trackdir CreateRandomPath(const Ship *v, Trackdir dir, ShipPathCache &path_cache, int path_length)
static Trackdir CreateRandomPath(const Ship *v, ShipPathCache &path_cache, int path_length)
{
std::pair<TileIndex, Trackdir> tile_dir = { v->tile, dir };
std::pair<TileIndex, Trackdir> tile_dir = { v->tile, v->GetVehicleTrackdir()};
for (int i = 0; i < path_length; ++i) {
tile_dir = GetRandomFollowUpTileTrackdir(v, tile_dir.first, tile_dir.second);
if (tile_dir.second == INVALID_TRACKDIR) break;
@ -204,19 +210,17 @@ public:
return result;
}
static Trackdir ChooseShipTrack(const Ship *v, TileIndex tile, bool &path_found, ShipPathCache &path_cache)
static Trackdir ChooseShipTrack(const Ship *v, TileIndex tile, TrackdirBits origin_dirs, TrackdirBits reverse_origin_dirs,
bool &path_found, ShipPathCache &path_cache, Trackdir &best_origin_dir)
{
const Trackdir trackdir = v->GetVehicleTrackdir();
assert(IsValidTrackdir(trackdir));
/* Convert origin trackdir to TrackdirBits. */
const TrackdirBits trackdirs = TrackdirToTrackdirBits(trackdir);
const std::vector<WaterRegionPatchDesc> high_level_path = YapfShipFindWaterRegionPath(v, tile, NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1);
if (high_level_path.empty()) {
path_found = false;
/* Make the ship move around aimlessly. This prevents repeated pathfinder calls and clearly indicates that the ship is lost. */
return CreateRandomPath(v, trackdir, path_cache, SHIP_LOST_PATH_LENGTH);
return CreateRandomPath(v, path_cache, SHIP_LOST_PATH_LENGTH);
}
/* Try one time without restricting the search area, which generally results in better and more natural looking paths.
@ -226,7 +230,7 @@ public:
Tpf pf(MAX_SHIP_PF_NODES);
/* Set origin and destination nodes */
pf.SetOrigin(v->tile, trackdirs);
pf.SetOrigin(v->tile, origin_dirs, reverse_origin_dirs, SHIP_REVERSAL_PENALTY);
pf.SetDestination(v);
const bool is_intermediate_destination = static_cast<int>(high_level_path.size()) >= NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1;
if (is_intermediate_destination) pf.SetIntermediateDestination(high_level_path.back());
@ -239,14 +243,16 @@ public:
path_found = pf.FindPath(v);
Node *node = pf.GetBestNode();
if (attempt == 0 && !path_found) continue; // Try again with restricted search area.
if (!path_found || node == nullptr) return GetRandomFollowUpTileTrackdir(v, v->tile, trackdir).second;
/* Make the ship move around aimlessly. This prevents repeated pathfinder calls and clearly indicates that the ship is lost. */
if (!path_found) return CreateRandomPath(v, path_cache, SHIP_LOST_PATH_LENGTH);
/* Return only the path within the current water region if an intermediate destination was returned. If not, cache the entire path
* to the final destination tile. The low-level pathfinder might actually prefer a different docking tile in a nearby region. Without
* caching the full path the ship can get stuck in a loop. */
const WaterRegionPatchDesc end_water_patch = GetWaterRegionPatchInfo(node->GetTile());
const WaterRegionPatchDesc start_water_patch = GetWaterRegionPatchInfo(tile);
assert(start_water_patch == high_level_path.front());
assert(GetWaterRegionPatchInfo(tile) == high_level_path.front() || GetWaterRegionPatchInfo(v->tile) == high_level_path.front());
const WaterRegionPatchDesc start_water_patch = high_level_path.front();
while (node->m_parent) {
const WaterRegionPatchDesc node_water_patch = GetWaterRegionPatchInfo(node->GetTile());
@ -262,10 +268,18 @@ public:
}
node = node->m_parent;
}
assert(node->GetTile() == v->tile);
/* If the ship has to turn around, return INVALID_TRACKDIR to trigger this. */
best_origin_dir = node->GetTrackdir();
if ((TrackdirToTrackdirBits(best_origin_dir) & origin_dirs) == TRACKDIR_BIT_NONE) {
path_cache.clear();
return INVALID_TRACKDIR;
}
/* A empty path means we are already at the destination. The pathfinder shouldn't have been called at all.
* Return a random reachable trackdir to hopefully nudge the ship out of this strange situation. */
if (path_cache.empty()) return GetRandomFollowUpTileTrackdir(v, v->tile, trackdir).second;
if (path_cache.empty()) return CreateRandomPath(v, path_cache, 1);
/* Take out the last trackdir as the result. */
const Trackdir result = path_cache.front();
@ -284,52 +298,30 @@ public:
* Check whether a ship should reverse to reach its destination.
* Called when leaving depot.
* @param v Ship.
* @param tile Current position.
* @param td1 Forward direction.
* @param td2 Reverse direction.
* @param trackdir [out] the best of all possible reversed trackdirs.
* @return true if the reverse direction is better.
*/
static bool CheckShipReverse(const Ship *v, TileIndex tile, Trackdir td1, Trackdir td2, Trackdir *trackdir)
static bool CheckShipReverse(const Ship *v, Trackdir *trackdir)
{
const std::vector<WaterRegionPatchDesc> high_level_path = YapfShipFindWaterRegionPath(v, tile, NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1);
if (high_level_path.empty()) {
if (trackdir) *trackdir = INVALID_TRACKDIR;
return false;
}
bool path_found = false;
ShipPathCache dummy_cache;
Trackdir best_origin_dir = INVALID_TRACKDIR;
/* Create pathfinder instance. */
Tpf pf(MAX_SHIP_PF_NODES);
/* Set origin and destination nodes. */
if (trackdir == nullptr) {
pf.SetOrigin(tile, TrackdirToTrackdirBits(td1) | TrackdirToTrackdirBits(td2));
/* The normal case, typically called when ships leave a dock. */
const Trackdir reverse_dir = ReverseTrackdir(v->GetVehicleTrackdir());
const TrackdirBits origin_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir());
const TrackdirBits reverse_origin_dirs = TrackdirToTrackdirBits(reverse_dir);
(void)ChooseShipTrack(v, v->tile, origin_dirs, reverse_origin_dirs, path_found, dummy_cache, best_origin_dir);
return path_found && best_origin_dir == reverse_dir;
} else {
DiagDirection entry = ReverseDiagDir(VehicleExitDir(v->direction, v->state));
TrackdirBits rtds = DiagdirReachesTrackdirs(entry) & TrackStatusToTrackdirBits(GetTileTrackStatus(tile, TRANSPORT_WATER, 0, entry));
pf.SetOrigin(tile, rtds);
/* This gets called when a ship suddenly can't move forward, e.g. due to terraforming. */
const DiagDirection entry = ReverseDiagDir(VehicleExitDir(v->direction, v->state));
const TrackdirBits reverse_origin_dirs = DiagdirReachesTrackdirs(entry) & TrackStatusToTrackdirBits(GetTileTrackStatus(v->tile, TRANSPORT_WATER, 0, entry));
(void)ChooseShipTrack(v, v->tile, TRACKDIR_BIT_NONE, reverse_origin_dirs, path_found, dummy_cache, best_origin_dir);
*trackdir = path_found ? best_origin_dir : GetRandomTrackdir(reverse_origin_dirs);
return true;
}
pf.SetDestination(v);
if (high_level_path.size() > 1) pf.SetIntermediateDestination(high_level_path.back());
pf.RestrictSearch(high_level_path);
/* Find best path. */
if (!pf.FindPath(v)) return false;
Node *pNode = pf.GetBestNode();
if (pNode == nullptr) return false;
/* Path was found, walk through the path back to the origin. */
while (pNode->m_parent != nullptr) {
pNode = pNode->m_parent;
}
Trackdir best_trackdir = pNode->GetTrackdir();
if (trackdir != nullptr) {
*trackdir = best_trackdir;
} else {
assert(best_trackdir == td1 || best_trackdir == td2);
}
return best_trackdir != td1;
}
};
@ -437,14 +429,14 @@ struct CYapfShip : CYapfT<CYapfShip_TypesT<CYapfShip, CFollowTrackWater, CShipNo
/** Ship controller helper - path finder invoker. */
Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipPathCache &path_cache)
{
Trackdir td_ret = CYapfShip::ChooseShipTrack(v, tile, path_found, path_cache);
Trackdir best_origin_dir = INVALID_TRACKDIR;
const TrackdirBits origin_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir());
const TrackdirBits reverse_origin_dirs = TrackdirToTrackdirBits(ReverseTrackdir(v->GetVehicleTrackdir()));
const Trackdir td_ret = CYapfShip::ChooseShipTrack(v, tile, origin_dirs, reverse_origin_dirs, path_found, path_cache, best_origin_dir);
return (td_ret != INVALID_TRACKDIR) ? TrackdirToTrack(td_ret) : INVALID_TRACK;
}
bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir)
{
Trackdir td = v->GetVehicleTrackdir();
Trackdir td_rev = ReverseTrackdir(td);
TileIndex tile = v->tile;
return CYapfShip::CheckShipReverse(v, tile, td, td_rev, trackdir);
return CYapfShip::CheckShipReverse(v, trackdir);
}

View File

@ -132,11 +132,13 @@ public:
protected:
Key m_dest;
Key m_reverse_dest;
public:
void SetDestination(const WaterRegionPatchDesc &water_region_patch)
void SetDestination(const WaterRegionPatchDesc &water_region_patch, const WaterRegionPatchDesc &reverse_water_region_patch)
{
m_dest.Set(water_region_patch);
m_reverse_dest.Set(reverse_water_region_patch);
}
protected:
@ -145,7 +147,7 @@ protected:
public:
inline bool PfDetectDestination(Node &n) const
{
return n.m_key == m_dest;
return n.m_key == m_dest || n.m_key == m_reverse_dest;
}
inline bool PfCalcEstimate(Node &n)
@ -191,11 +193,12 @@ public:
static std::vector<WaterRegionPatchDesc> FindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length)
{
const WaterRegionPatchDesc start_water_region_patch = GetWaterRegionPatchInfo(start_tile);
const WaterRegionPatchDesc reverse_start_water_region_patch = GetWaterRegionPatchInfo(v->tile);
/* We reserve 4 nodes (patches) per water region. The vast majority of water regions have 1 or 2 regions so this should be a pretty
* safe limit. We cap the limit at 65536 which is at a region size of 16x16 is equivalent to one node per region for a 4096x4096 map. */
Tpf pf(std::min(static_cast<int>(Map::Size() * NODES_PER_REGION) / WATER_REGION_NUMBER_OF_TILES, MAX_NUMBER_OF_NODES));
pf.SetDestination(start_water_region_patch);
pf.SetDestination(start_water_region_patch, reverse_start_water_region_patch);
if (v->current_order.IsType(OT_GOTO_STATION)) {
DestinationID station_id = v->current_order.GetDestination();
@ -212,15 +215,14 @@ public:
pf.AddOrigin(GetWaterRegionPatchInfo(tile));
}
/* If origin and destination are the same we simply return that water patch. */
std::vector<WaterRegionPatchDesc> path = { start_water_region_patch };
path.reserve(max_returned_path_length);
if (pf.HasOrigin(start_water_region_patch)) return path;
/* Find best path. */
if (!pf.FindPath(v)) return {}; // Path not found.
Node *node = pf.GetBestNode();
std::vector<WaterRegionPatchDesc> path;
path.reserve(max_returned_path_length);
path.push_back(node->m_key.m_water_region_patch);
for (int i = 0; i < max_returned_path_length - 1; ++i) {
if (node != nullptr) {
node = node->m_parent;