New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #5713: Use pathfinder to find closest ship depot #6928

Open
wants to merge 4 commits into
base: master
from
File filter...
Filter file types
Jump to file or symbol
Failed to load files and symbols.
+339 −64
Diff settings

Always

Just for now

Copy path View file
@@ -534,8 +534,17 @@ static int32 NPFFindDepot(AyStar *as, OpenListNode *current)
AyStarUserData *user = (AyStarUserData *)as->user_data;
/* It's not worth caching the result with NPF_FLAG_IS_TARGET here as below,
* since checking the cache not that much faster than the actual check */
return IsDepotTypeTile(current->path.node.tile, user->type) ?
AYSTAR_FOUND_END_NODE : AYSTAR_DONE;
if (user->type != TRANSPORT_WATER) {
return IsDepotTypeTile(current->path.node.tile, user->type) ?
AYSTAR_FOUND_END_NODE : AYSTAR_DONE;
} else {
/* The tile where the ship actually "enters" the depot is the north tile.
* Ship depots are also valid water tracks for ships from any company,
* unlike the other depot types, so the pathfinder has to do a blind
* search for a tile that matches these conditions. */
return IsShipDepotTile(current->path.node.tile) && GetShipDepotPart(current->path.node.tile) == DEPOT_PART_NORTH && IsTileOwner(current->path.node.tile, user->owner) ?
This conversation was marked as resolved by nielsmh

This comment has been minimized.

@nielsmh

nielsmh Jan 12, 2019

Contributor

Please add a comment in the code describing what situation these extra checks are required for.

This comment has been minimized.

@SamuXarick

SamuXarick Jan 13, 2019

Contributor

These checks are required because ship depots are 2 tiles. The tile where the ship actually "enters" the depot is the north tile. Ship depots are also valid water tracks for ships from any company, unlike the other depot types. Previously, these checks weren't required, because the depot tile was passed to pathfinders directly from ship_cmd.cpp. Pathfinder only had to compute the path to that tile. But now, the pathfinder is not passed any destination, it has to do the "blind" search for a tile that matches these conditions.

AYSTAR_FOUND_END_NODE : AYSTAR_DONE;
}
}

/** Find any safe and free tile. */
@@ -1154,6 +1163,20 @@ Trackdir NPFRoadVehicleChooseTrack(const RoadVehicle *v, TileIndex tile, DiagDir

/*** Ships ***/

FindDepotData NPFShipFindNearestDepot(const Ship *v, int max_penalty)
{
Trackdir trackdir = v->GetVehicleTrackdir();
Trackdir trackdir_rev = ReverseTrackdir(trackdir);

AyStarUserData user = { v->owner, TRANSPORT_WATER, INVALID_RAILTYPES, ROADTYPES_NONE };
NPFFoundTargetData ftd = NPFRouteToDepotBreadthFirstTwoWay(v->tile, trackdir, false, v->tile, trackdir_rev, false, NULL, &user, 0, max_penalty);
if (ftd.best_bird_dist != 0) return FindDepotData();

/* Found target */
/* Our caller wants the distance to depot. We provide it in distance manhattan */
return FindDepotData(ftd.node.tile, DistanceManhattan(v->tile, ftd.node.tile), NPFGetFlag(&ftd.node, NPF_FLAG_REVERSE));
}

Track NPFShipChooseTrack(const Ship *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool &path_found)
{
NPFFindStationOrTileData fstd;
Copy path View file
@@ -38,6 +38,16 @@ FindDepotData NPFRoadVehicleFindNearestDepot(const RoadVehicle *v, int max_penal
*/
Trackdir NPFRoadVehicleChooseTrack(const RoadVehicle *v, TileIndex tile, DiagDirection enterdir, TrackdirBits trackdirs, bool &path_found);

/**
* Used when user sends ship to the nearest depot or if ship needs servicing using NPF
* @param v ship that needs to go to some depot
* @param max_penalty max distance (in pathfinder penalty) from the current ship position
* (used also as optimization - the pathfinder can stop path finding if max_penalty
* was reached and no depot was seen)
* @return the data about the depot
*/
FindDepotData NPFShipFindNearestDepot(const Ship *v, int max_penalty);

/**
* Finds the best path for given ship using NPF.
* @param v the ship that needs to find a path
Copy path View file
@@ -14,9 +14,13 @@
#include "../../tunnelbridge.h"
#include "../../ship.h"
#include "../../core/random_func.hpp"
#include "../pathfinder_type.h"
#include "../../depot_base.h"

#include "../../safeguards.h"

static const uint OPF_MAX_LENGTH = 50; ///< The default maximum path length

struct RememberData {
uint16 cur_length;
byte depth;
@@ -28,29 +32,43 @@ struct TrackPathFinder {
TileIndex dest_coords;
uint best_bird_dist;
uint best_length;
uint max_path_length;
RememberData rd;
TrackdirByte the_dir;
Owner ship_owner;
};

static bool ShipTrackFollower(TileIndex tile, TrackPathFinder *pfs, uint length)
static bool ShipTrackFollower(TileIndex tile, TrackPathFinder *pfs, uint length, bool depot)
{
/* Found dest? */
if (tile == pfs->dest_coords) {
pfs->best_bird_dist = 0;
if (!depot) {
/* Found dest? */
if (tile == pfs->dest_coords) {
pfs->best_bird_dist = 0;

pfs->best_length = minu(pfs->best_length, length);
return true;
}
pfs->best_length = minu(pfs->best_length, length);
return true;
}

/* Skip this tile in the calculation */
if (tile != pfs->skiptile) {
pfs->best_bird_dist = minu(pfs->best_bird_dist, DistanceMaxPlusManhattan(pfs->dest_coords, tile));
}
/* Skip this tile in the calculation */
if (tile != pfs->skiptile) {
pfs->best_bird_dist = minu(pfs->best_bird_dist, DistanceMaxPlusManhattan(pfs->dest_coords, tile));
}

return false;
} else {
/* Found depot? */
if (IsShipDepotTile(tile) && GetShipDepotPart(tile) == DEPOT_PART_NORTH && IsTileOwner(tile, pfs->ship_owner)) {
pfs->best_bird_dist = 0;
pfs->dest_coords = tile;
pfs->best_length = minu(pfs->best_length, length);
return true;
}

return false;
return false;
}
}

static void TPFModeShip(TrackPathFinder *tpf, TileIndex tile, DiagDirection direction)
static void TPFModeShip(TrackPathFinder *tpf, TileIndex tile, DiagDirection direction, bool depot)
{
if (IsTileType(tile, MP_TUNNELBRIDGE)) {
/* wrong track type */
@@ -75,7 +93,8 @@ static void TPFModeShip(TrackPathFinder *tpf, TileIndex tile, DiagDirection dire
* tile, and then this tile will be in the sentinel row/col, so GetTileTrackStatus will fail. */
tile = TILE_MASK(tile + TileOffsByDiagDir(direction));

if (++tpf->rd.cur_length > 50) return;
/* Finish if we already exceeded the maximum path cost */
if (++tpf->rd.cur_length > tpf->max_path_length) return;

TrackBits bits = TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_WATER, 0)) & DiagdirReachesTracks(direction);
if (bits == TRACK_BIT_NONE) return;
@@ -99,15 +118,15 @@ static void TPFModeShip(TrackPathFinder *tpf, TileIndex tile, DiagDirection dire

tpf->the_dir = TrackEnterdirToTrackdir(track, direction);

if (!ShipTrackFollower(tile, tpf, tpf->rd.cur_length)) {
TPFModeShip(tpf, tile, TrackdirToExitdir(tpf->the_dir));
if (!ShipTrackFollower(tile, tpf, tpf->rd.cur_length, depot)) {
TPFModeShip(tpf, tile, TrackdirToExitdir(tpf->the_dir), depot);
}

tpf->rd = rd;
} while (bits != TRACK_BIT_NONE);
}

static void OPFShipFollowTrack(TileIndex tile, DiagDirection direction, TrackPathFinder *tpf)
static void OPFShipFollowTrack(TileIndex tile, DiagDirection direction, TrackPathFinder *tpf, bool depot)
{
assert(IsValidDiagDirection(direction));

@@ -116,8 +135,8 @@ static void OPFShipFollowTrack(TileIndex tile, DiagDirection direction, TrackPat
tpf->rd.depth = 0;
tpf->rd.last_choosen_track = INVALID_TRACK;

ShipTrackFollower(tile, tpf, 0);
TPFModeShip(tpf, tile, direction);
ShipTrackFollower(tile, tpf, 0, depot);
TPFModeShip(tpf, tile, direction, depot);
}

/** Directions to search towards given track bits and the ship's enter direction. */
@@ -133,17 +152,26 @@ static const DiagDirection _ship_search_directions[6][4] = {
/** Track to "direction (& 3)" mapping. */
static const byte _pick_shiptrack_table[6] = {DIR_NE, DIR_SE, DIR_E, DIR_E, DIR_N, DIR_N};

static uint FindShipTrack(const Ship *v, TileIndex tile, DiagDirection dir, TrackBits bits, TileIndex skiptile, Track *track)
struct ShipTrackData {
Track best_track;
uint best_bird_dist;
uint best_length;
TileIndex dest_coords;
};

static void FindShipTrack(const Ship *v, TileIndex tile, DiagDirection dir, TrackBits bits, TileIndex skiptile, ShipTrackData *fstd, uint max_path_length = OPF_MAX_LENGTH, bool depot = false)
{
TrackPathFinder pfs;
uint best_bird_dist = 0;
uint best_length = 0;
fstd->best_track = INVALID_TRACK;
fstd->best_bird_dist = 0;
fstd->best_length = 0;
fstd->dest_coords = depot ? INVALID_TILE : v->dest_tile;
byte ship_dir = v->direction & 3;

pfs.dest_coords = v->dest_tile;
TrackPathFinder pfs;
pfs.dest_coords = fstd->dest_coords;
pfs.skiptile = skiptile;

Track best_track = INVALID_TRACK;
pfs.max_path_length = max_path_length;
pfs.ship_owner = v->owner;

assert(bits != TRACK_BIT_NONE);
do {
@@ -152,35 +180,33 @@ static uint FindShipTrack(const Ship *v, TileIndex tile, DiagDirection dir, Trac
pfs.best_bird_dist = UINT_MAX;
pfs.best_length = UINT_MAX;

OPFShipFollowTrack(tile, _ship_search_directions[i][dir], &pfs);
OPFShipFollowTrack(tile, _ship_search_directions[i][dir], &pfs, depot);

if (best_track != INVALID_TRACK) {
if (fstd->best_track != INVALID_TRACK) {
if (pfs.best_bird_dist != 0) {
/* neither reached the destination, pick the one with the smallest bird dist */
if (pfs.best_bird_dist > best_bird_dist) goto bad;
if (pfs.best_bird_dist < best_bird_dist) goto good;
if (pfs.best_bird_dist > fstd->best_bird_dist) goto bad;
if (pfs.best_bird_dist < fstd->best_bird_dist) goto good;
} else {
if (pfs.best_length > best_length) goto bad;
if (pfs.best_length < best_length) goto good;
if (pfs.best_length > fstd->best_length) goto bad;
if (pfs.best_length < fstd->best_length) goto good;
}

/* if we reach this position, there's two paths of equal value so far.
* pick one randomly. */
uint r = GB(Random(), 0, 8);
if (_pick_shiptrack_table[i] == ship_dir) r += 80;
if (_pick_shiptrack_table[best_track] == ship_dir) r -= 80;
if (_pick_shiptrack_table[fstd->best_track] == ship_dir) r -= 80;
if (r <= 127) goto bad;
}
good:;
best_track = i;
best_bird_dist = pfs.best_bird_dist;
best_length = pfs.best_length;
fstd->best_track = i;
fstd->best_bird_dist = pfs.best_bird_dist;
fstd->best_length = pfs.best_length;
fstd->dest_coords = pfs.dest_coords;
bad:;

} while (bits != TRACK_BIT_NONE);

*track = best_track;
return best_bird_dist;
}

/**
@@ -198,7 +224,7 @@ Track OPFShipChooseTrack(const Ship *v, TileIndex tile, DiagDirection enterdir,
assert(IsValidDiagDirection(enterdir));

TileIndex tile2 = TILE_ADD(tile, -TileOffsByDiagDir(enterdir));
Track track;
ShipTrackData fstd;

/* Let's find out how far it would be if we would reverse first */
uint rev_dist = UINT_MAX; // distance if we reverse
@@ -208,15 +234,71 @@ Track OPFShipChooseTrack(const Ship *v, TileIndex tile, DiagDirection enterdir,
DiagdirReachesTracks(rev_enterdir);

if (HasTrack(rev_tracks, cur_track)) {
rev_dist = FindShipTrack(v, tile2, rev_enterdir, TrackToTrackBits(cur_track), tile, &track);
FindShipTrack(v, tile2, rev_enterdir, TrackToTrackBits(cur_track), tile, &fstd);
rev_dist = fstd.best_bird_dist;
if (rev_dist != UINT_MAX) rev_dist++; // penalty for reversing
}

/* And if we would not reverse? */
uint dist = FindShipTrack(v, tile, enterdir, tracks, 0, &track);
FindShipTrack(v, tile, enterdir, tracks, 0, &fstd);
uint dist = fstd.best_bird_dist;

/* Due to the way this pathfinder works we cannot determine whether we're lost or not. */
path_found = true;
if (dist <= rev_dist) return track;
if (dist <= rev_dist) return fstd.best_track;
return INVALID_TRACK; // We could better reverse
}

FindDepotData OPFShipFindNearestDepot(const Ship *v, int max_distance)
{
FindDepotData fdd;

Trackdir trackdir = v->GetVehicleTrackdir();

/* Argument values for FindShipTrack below, for the current ship direction */
DiagDirection enterdir = TrackdirToExitdir(trackdir);
TileIndex tile = TILE_ADD(v->tile, TileOffsByDiagDir(enterdir));
TrackBits tracks = TrackdirBitsToTrackBits(DiagdirReachesTrackdirs(enterdir));

/* Argument values for FindShipTrack below, for the reversed ship direction */
DiagDirection enterdir_rev = ReverseDiagDir(enterdir);
TileIndex tile_rev = v->tile;
TrackBits tracks_rev = TrackStatusToTrackBits(GetTileTrackStatus(tile_rev, TRANSPORT_WATER, 0)) & DiagdirReachesTracks(enterdir_rev) & TrackdirBitsToTrackBits(TrackdirToTrackdirBits(trackdir));

ShipTrackData fstd;
if (max_distance == 0) max_distance = OPF_MAX_LENGTH;

/* Let's find the length it would be if we would reverse first */
uint length_rev = UINT_MAX;
TileIndex dest_tile_rev = INVALID_TILE;
if (tracks_rev != 0) {
FindShipTrack(v, tile_rev, enterdir_rev, tracks_rev, tile, &fstd, max_distance, true);
length_rev = fstd.best_length;
tile_rev = fstd.dest_coords;
if (length_rev != UINT_MAX) length_rev++; // penalty for reversing
}

/* And if we would not reverse? */
FindShipTrack(v, tile, enterdir, tracks, 0, &fstd, max_distance, true);
uint length = fstd.best_length;
TileIndex dest_tile = fstd.dest_coords;

if (IsValidTile(dest_tile_rev)) {
if (IsValidTile(dest_tile)) {
fdd.tile = length_rev < length ? dest_tile_rev : dest_tile; // tile location of ship depot
fdd.best_length = DistanceManhattan(v->tile, fdd.tile); // distance manhattan from ship to depot
fdd.reverse = !(length <= length_rev);
}
fdd.tile = dest_tile_rev;
fdd.best_length = DistanceManhattan(v->tile, fdd.tile); // distance manhattan from ship to depot
fdd.reverse = true;
} else {
if (IsValidTile(dest_tile)) {
fdd.tile = dest_tile;
fdd.best_length = DistanceManhattan(v->tile, fdd.tile); // distance manhattan from ship to depot
fdd.reverse = false;
}
}

return fdd;
}
Copy path View file
@@ -28,4 +28,14 @@
*/
Track OPFShipChooseTrack(const Ship *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool &path_found);

/**
* Used when user sends ship to the nearest depot or if ship needs servicing using OPF.
* @param v vehicle that needs to go to some depot
* @param max_distance max distance (in pathfinder penalty) from the current ship position
* (used also as optimization - the pathfinder can stop path finding if max_penalty
* was reached and no depot was seen)
* @return the data about the depot
*/
FindDepotData OPFShipFindNearestDepot(const Ship *v, int max_distance);

#endif /* OPF_SHIP_H */
Copy path View file
@@ -35,6 +35,16 @@ Track YapfShipChooseTrack(const Ship *v, TileIndex tile, DiagDirection enterdir,
*/
bool YapfShipCheckReverse(const Ship *v);

/**
* Used when user sends ship to the nearest depot or if ship needs servicing using YAPF.
* @param v vehicle that needs to go to some depot
* @param max_penalty max distance (in pathfinder penalty) from the current ship position
* (used also as optimization - the pathfinder can stop path finding if max_penalty
* was reached and no depot was seen)
* @return the data about the depot
*/
FindDepotData YapfShipFindNearestDepot(const Ship *v, int max_penalty);

/**
* Finds the best path for given road vehicle using YAPF.
* @param v the RV that needs to find a path
Oops, something went wrong.
ProTip! Use n and p to navigate between commits in a pull request.