mirror of https://github.com/OpenTTD/OpenTTD
Merge 8031d68d01
into bd2430dc94
commit
16276085a6
|
@ -110,7 +110,7 @@ struct Aircraft final : public SpecializedVehicle<Aircraft, VEH_AIRCRAFT> {
|
|||
uint Crash(bool flooded = false) override;
|
||||
TileIndex GetOrderStationLocation(StationID station) override;
|
||||
TileIndex GetCargoTile() const override { return this->First()->tile; }
|
||||
ClosestDepot FindClosestDepot() override;
|
||||
ClosestDepot FindClosestDepot(bool may_reverse = false) override;
|
||||
|
||||
/**
|
||||
* Check if the aircraft type is a normal flying device; eg
|
||||
|
|
|
@ -396,7 +396,7 @@ CommandCost CmdBuildAircraft(DoCommandFlags flags, TileIndex tile, const Engine
|
|||
}
|
||||
|
||||
|
||||
ClosestDepot Aircraft::FindClosestDepot()
|
||||
ClosestDepot Aircraft::FindClosestDepot([[maybe_unused]] bool may_reverse)
|
||||
{
|
||||
const Station *st = GetTargetAirportIfValid(this);
|
||||
/* If the station is not a valid airport or if it has no hangars */
|
||||
|
|
|
@ -1896,10 +1896,11 @@ VehicleOrderID ProcessConditionalOrder(const Order *order, const Vehicle *v)
|
|||
* Update the vehicle's destination tile from an order.
|
||||
* @param order the order the vehicle currently has
|
||||
* @param v the vehicle to update
|
||||
* @param may_reverse Whether the vehicle is allowed to reverse when executing the updated order.
|
||||
* @param conditional_depth the depth (amount of steps) to go with conditional orders. This to prevent infinite loops.
|
||||
* @param pbs_look_ahead Whether we are forecasting orders for pbs reservations in advance. If true, the order indices must not be modified.
|
||||
*/
|
||||
bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth, bool pbs_look_ahead)
|
||||
bool UpdateOrderDest(Vehicle *v, const Order *order, bool may_reverse, int conditional_depth, bool pbs_look_ahead)
|
||||
{
|
||||
if (conditional_depth > v->GetNumOrders()) {
|
||||
v->current_order.Free();
|
||||
|
@ -1926,7 +1927,7 @@ bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth, bool
|
|||
if (v->dest_tile == 0 && TimerGameEconomy::date_fract != (v->index % Ticks::DAY_TICKS)) break;
|
||||
|
||||
/* We need to search for the nearest depot (hangar). */
|
||||
ClosestDepot closest_depot = v->FindClosestDepot();
|
||||
ClosestDepot closest_depot = v->FindClosestDepot(may_reverse);
|
||||
|
||||
if (closest_depot.found) {
|
||||
/* PBS reservations cannot reverse */
|
||||
|
@ -2018,7 +2019,7 @@ bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth, bool
|
|||
}
|
||||
|
||||
v->current_order = *order;
|
||||
return UpdateOrderDest(v, order, conditional_depth + 1, pbs_look_ahead);
|
||||
return UpdateOrderDest(v, order, may_reverse, conditional_depth + 1, pbs_look_ahead);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2116,7 +2117,7 @@ bool ProcessOrders(Vehicle *v)
|
|||
break;
|
||||
}
|
||||
|
||||
return UpdateOrderDest(v, order) && may_reverse;
|
||||
return UpdateOrderDest(v, order, may_reverse) && may_reverse;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,7 +20,7 @@ void InvalidateVehicleOrder(const Vehicle *v, int data);
|
|||
void CheckOrders(const Vehicle*);
|
||||
void DeleteVehicleOrders(Vehicle *v, bool keep_orderlist = false, bool reset_order_indices = true);
|
||||
bool ProcessOrders(Vehicle *v);
|
||||
bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth = 0, bool pbs_look_ahead = false);
|
||||
bool UpdateOrderDest(Vehicle *v, const Order *order, bool may_reverse = false, int conditional_depth = 0, bool pbs_look_ahead = false);
|
||||
VehicleOrderID ProcessConditionalOrder(const Order *order, const Vehicle *v);
|
||||
uint GetOrderDistance(VehicleOrderID prev, VehicleOrderID cur, const Vehicle *v, int conditional_depth = 0);
|
||||
|
||||
|
|
|
@ -430,3 +430,83 @@ void PrintWaterRegionDebugInfo(TileIndex tile)
|
|||
{
|
||||
GetUpdatedWaterRegion(tile).PrintDebugInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the provided callback function on all tiles of the water patch of the region
|
||||
* and returns true on the first tile that passes the callback test.
|
||||
* @param callback The test function that will be called for the water patch.
|
||||
* @param water_region_patch Water patch within the water region to test the callback.
|
||||
* @return true if it passes the callback test, or false if the callback failed.
|
||||
*/
|
||||
bool TestTileInWaterRegionPatch(const WaterRegionPatchDesc &water_region_patch, TestTileIndexCallBack &callback)
|
||||
{
|
||||
const WaterRegion region = GetUpdatedWaterRegion(water_region_patch.x, water_region_patch.y);
|
||||
|
||||
/* Check if the region has a tile which passes the callback test. */
|
||||
for (const TileIndex tile : region) {
|
||||
if (region.GetLabel(tile) != water_region_patch.label || !callback(tile)) continue;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the provided callback function on all tiles of the current water patch of the region, collects the
|
||||
* tiles which passed the callback and returns the tile closest to the edge from where the region is entered from.
|
||||
* @param high_level_path A span containing at least current and parent water patches.
|
||||
* @param callback The test function that will be called for each tile in the water patch.
|
||||
* @return The tile closest to the edge from where it came from that passed the callback test, or INVALID_TILE if no tile passed.
|
||||
*/
|
||||
TileIndex FindClosestEnteringTile(const std::span<WaterRegionPatchDesc> high_level_path, TestTileIndexCallBack &callback)
|
||||
{
|
||||
assert(high_level_path.size() > 1);
|
||||
|
||||
const WaterRegionPatchDesc ¤t_water_region_patch = high_level_path.back();
|
||||
const WaterRegion current_region = GetUpdatedWaterRegion(current_water_region_patch.x, current_water_region_patch.y);
|
||||
|
||||
/* Check if the current region has a tile which passes the callback test. */
|
||||
std::vector<TileIndex> tile_list;
|
||||
for (const TileIndex tile : current_region) {
|
||||
if (current_region.GetLabel(tile) != current_water_region_patch.label || !callback(tile)) continue;
|
||||
|
||||
/* We collect the tiles when we know which region we came from for further evaluation. */
|
||||
tile_list.push_back(tile);
|
||||
}
|
||||
|
||||
/* If there aren't any tiles that passed the callback, return with an invalid tile. */
|
||||
if (tile_list.empty()) return INVALID_TILE;
|
||||
|
||||
/* If there's only one, just return it. */
|
||||
if (tile_list.size() == 1) return tile_list.front();
|
||||
|
||||
const TileIndex top_tile = current_region.begin();
|
||||
const TileIndex bot_tile = TileAddXY(top_tile, WATER_REGION_EDGE_LENGTH - 1, WATER_REGION_EDGE_LENGTH - 1);
|
||||
|
||||
/* Get the side from which the current region is entered from. */
|
||||
const WaterRegionPatchDesc &parent_water_region_patch = high_level_path[high_level_path.size() - 2];
|
||||
const WaterRegion parent_region = GetUpdatedWaterRegion(parent_water_region_patch.x, parent_water_region_patch.y);
|
||||
const DiagDirection side = DiagdirBetweenTiles(top_tile, parent_region.begin());
|
||||
|
||||
/* Depending on the side, determine which corner tile to use to extract their x or y coordinates. */
|
||||
const bool is_at_top = side == DIAGDIR_NE || side == DIAGDIR_NW;
|
||||
const TileIndex edge_tile = is_at_top ? top_tile : bot_tile;
|
||||
const bool is_axis_x = DiagDirToAxis(side) == AXIS_X;
|
||||
const int x_or_y_edge = is_axis_x ? TileX(edge_tile) : TileY(edge_tile);
|
||||
|
||||
/* With more than one tile passing the callback, calculate the tile that is closest to the edge from whence it came. */
|
||||
TileIndex best_tile = INVALID_TILE;
|
||||
int best_dist = WATER_REGION_EDGE_LENGTH;
|
||||
for (const TileIndex &tile : tile_list) {
|
||||
const int x_or_y_tile = is_axis_x ? TileX(tile) : TileY(tile);
|
||||
const int dist_to_edge = std::abs(x_or_y_tile - x_or_y_edge);
|
||||
assert(dist_to_edge < WATER_REGION_EDGE_LENGTH);
|
||||
if (dist_to_edge >= best_dist) continue;
|
||||
|
||||
best_dist = dist_to_edge;
|
||||
best_tile = tile;
|
||||
}
|
||||
|
||||
return best_tile;
|
||||
}
|
||||
|
|
|
@ -64,4 +64,8 @@ void AllocateWaterRegions();
|
|||
|
||||
void PrintWaterRegionDebugInfo(TileIndex tile);
|
||||
|
||||
using TestTileIndexCallBack = std::function<bool(const TileIndex)>;
|
||||
bool TestTileInWaterRegionPatch(const WaterRegionPatchDesc &water_region_patch, TestTileIndexCallBack &callback);
|
||||
TileIndex FindClosestEnteringTile(const std::span<WaterRegionPatchDesc> high_level_path, TestTileIndexCallBack &callback);
|
||||
|
||||
#endif /* WATER_REGIONS_H */
|
||||
|
|
|
@ -34,6 +34,17 @@ Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipP
|
|||
*/
|
||||
bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir);
|
||||
|
||||
/**
|
||||
* Used when user sends ship to the nearest depot or if ship needs servicing using YAPF.
|
||||
* @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)
|
||||
* @param may_reverse whether the ship is allowed to reverse
|
||||
* @return the data about the depot
|
||||
*/
|
||||
FindDepotData YapfShipFindNearestDepot(const Ship *v, int max_penalty, bool may_reverse);
|
||||
|
||||
/**
|
||||
* Finds the best path for given road vehicle using YAPF.
|
||||
* @param v the RV that needs to find a path
|
||||
|
|
|
@ -36,6 +36,7 @@ protected:
|
|||
TileIndex dest_tile;
|
||||
TrackdirBits dest_trackdirs;
|
||||
StationID dest_station;
|
||||
bool any_ship_depot = false;
|
||||
|
||||
bool has_intermediate_dest = false;
|
||||
TileIndex intermediate_dest_tile;
|
||||
|
@ -55,6 +56,11 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void SetAnyShipDepotDestination()
|
||||
{
|
||||
this->any_ship_depot = true;
|
||||
}
|
||||
|
||||
void SetIntermediateDestination(const WaterRegionPatchDesc &water_region_patch)
|
||||
{
|
||||
this->has_intermediate_dest = true;
|
||||
|
@ -69,10 +75,16 @@ protected:
|
|||
return *static_cast<Tpf*>(this);
|
||||
}
|
||||
|
||||
TestTileIndexCallBack detect_ship_depot = [&](const TileIndex tile)
|
||||
{
|
||||
return IsShipDepotTile(tile) && GetShipDepotPart(tile) == DEPOT_PART_NORTH && IsTileOwner(tile, Yapf().GetVehicle()->owner);
|
||||
};
|
||||
|
||||
public:
|
||||
/** Called by YAPF to detect if node ends in the desired destination. */
|
||||
inline bool PfDetectDestination(Node &n)
|
||||
{
|
||||
if (this->any_ship_depot) return this->detect_ship_depot(n.key.tile);
|
||||
return this->PfDetectDestinationTile(n.segment_last_tile, n.segment_last_td);
|
||||
}
|
||||
|
||||
|
@ -89,6 +101,11 @@ public:
|
|||
return tile == this->dest_tile && ((this->dest_trackdirs & TrackdirToTrackdirBits(trackdir)) != TRACKDIR_BIT_NONE);
|
||||
}
|
||||
|
||||
inline TileIndex GetShipDepotDestination(const std::span<WaterRegionPatchDesc> high_level_path)
|
||||
{
|
||||
return FindClosestEnteringTile(high_level_path, this->detect_ship_depot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by YAPF to calculate cost estimate. Calculates distance to the destination
|
||||
* adds it to the actual cost from origin and stores the sum to the Node::estimate.
|
||||
|
@ -99,7 +116,7 @@ public:
|
|||
|
||||
static const int dg_dir_to_x_offs[] = { -1, 0, 1, 0 };
|
||||
static const int dg_dir_to_y_offs[] = { 0, 1, 0, -1 };
|
||||
if (this->PfDetectDestination(n)) {
|
||||
if (this->any_ship_depot || this->PfDetectDestination(n)) {
|
||||
n.estimate = n.cost;
|
||||
return true;
|
||||
}
|
||||
|
@ -158,7 +175,7 @@ public:
|
|||
}
|
||||
|
||||
/** Restricts the search by creating corridor or water regions through which the ship is allowed to travel. */
|
||||
inline void RestrictSearch(const std::vector<WaterRegionPatchDesc> &path)
|
||||
inline void RestrictSearch(const std::span<WaterRegionPatchDesc> &path)
|
||||
{
|
||||
this->water_region_corridor.clear();
|
||||
for (const WaterRegionPatchDesc &path_entry : path) this->water_region_corridor.push_back(path_entry);
|
||||
|
@ -211,16 +228,20 @@ public:
|
|||
return result;
|
||||
}
|
||||
|
||||
static Trackdir ChooseShipTrack(const Ship *v, TileIndex tile, TrackdirBits forward_dirs, TrackdirBits reverse_dirs,
|
||||
static Trackdir ChooseShipTrack(const Ship *v, TileIndex &tile, TrackdirBits forward_dirs, TrackdirBits reverse_dirs, int max_penalty,
|
||||
bool &path_found, ShipPathCache &path_cache, Trackdir &best_origin_dir)
|
||||
{
|
||||
const std::vector<WaterRegionPatchDesc> high_level_path = YapfShipFindWaterRegionPath(v, tile, NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1);
|
||||
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, path_cache, SHIP_LOST_PATH_LENGTH);
|
||||
}
|
||||
|
||||
const bool find_closest_depot = tile == INVALID_TILE;
|
||||
if (find_closest_depot) tile = v->tile;
|
||||
const bool automatic_servicing = find_closest_depot && max_penalty != 0;
|
||||
|
||||
/* Try one time without restricting the search area, which generally results in better and more natural looking paths.
|
||||
* However the pathfinder can hit the node limit in certain situations such as long aqueducts or maze-like terrain.
|
||||
* If that happens we run the pathfinder again, but restricted only to the regions provided by the region pathfinder. */
|
||||
|
@ -229,13 +250,28 @@ public:
|
|||
|
||||
/* Set origin and destination nodes */
|
||||
pf.SetOrigin(v->tile, forward_dirs | reverse_dirs);
|
||||
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());
|
||||
if (find_closest_depot) {
|
||||
pf.SetAnyShipDepotDestination();
|
||||
} else {
|
||||
pf.SetDestination(v);
|
||||
}
|
||||
pf.SetMaxCost(max_penalty);
|
||||
|
||||
const std::span<WaterRegionPatchDesc> high_level_path_span(high_level_path.data(), std::min<size_t>(high_level_path.size(), NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1));
|
||||
const bool is_intermediate_destination = static_cast<int>(high_level_path_span.size()) >= NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1;
|
||||
if (is_intermediate_destination) {
|
||||
if (automatic_servicing) {
|
||||
/* Automatic servicing requires a valid path cost from start to end.
|
||||
* However, when an intermediate destination is set, the resulting cost
|
||||
* cannot be used to determine if it falls within the maximum allowed penalty. */
|
||||
return INVALID_TRACKDIR;
|
||||
}
|
||||
pf.SetIntermediateDestination(high_level_path_span.back());
|
||||
}
|
||||
|
||||
/* Restrict the search area to prevent the low level pathfinder from expanding too many nodes. This can happen
|
||||
* when the terrain is very "maze-like" or when the high level path "teleports" via a very long aqueduct. */
|
||||
if (attempt > 0) pf.RestrictSearch(high_level_path);
|
||||
if (attempt > 0) pf.RestrictSearch(high_level_path_span);
|
||||
|
||||
/* Find best path. */
|
||||
path_found = pf.FindPath(v);
|
||||
|
@ -245,6 +281,12 @@ public:
|
|||
/* 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 early when only searching for the closest depot tile. */
|
||||
if (find_closest_depot) {
|
||||
tile = is_intermediate_destination ? pf.GetShipDepotDestination(high_level_path) : node->GetTile();
|
||||
return INVALID_TRACKDIR;
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
|
@ -254,7 +296,7 @@ public:
|
|||
while (node->parent) {
|
||||
const WaterRegionPatchDesc node_water_patch = GetWaterRegionPatchInfo(node->GetTile());
|
||||
|
||||
const bool node_water_patch_on_high_level_path = std::ranges::find(high_level_path, node_water_patch) != high_level_path.end();
|
||||
const bool node_water_patch_on_high_level_path = std::ranges::find(high_level_path_span, node_water_patch) != high_level_path_span.end();
|
||||
const bool add_full_path = !is_intermediate_destination && node_water_patch != end_water_patch;
|
||||
|
||||
/* The cached path must always lead to a region patch that's on the high level path.
|
||||
|
@ -303,6 +345,7 @@ public:
|
|||
{
|
||||
bool path_found = false;
|
||||
ShipPathCache dummy_cache;
|
||||
TileIndex tile = v->tile;
|
||||
Trackdir best_origin_dir = INVALID_TRACKDIR;
|
||||
|
||||
if (trackdir == nullptr) {
|
||||
|
@ -310,17 +353,45 @@ public:
|
|||
const Trackdir reverse_dir = ReverseTrackdir(v->GetVehicleTrackdir());
|
||||
const TrackdirBits forward_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir());
|
||||
const TrackdirBits reverse_dirs = TrackdirToTrackdirBits(reverse_dir);
|
||||
(void)ChooseShipTrack(v, v->tile, forward_dirs, reverse_dirs, path_found, dummy_cache, best_origin_dir);
|
||||
(void)ChooseShipTrack(v, tile, forward_dirs, reverse_dirs, 0, path_found, dummy_cache, best_origin_dir);
|
||||
return path_found && best_origin_dir == reverse_dir;
|
||||
} else {
|
||||
/* 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_dirs = DiagdirReachesTrackdirs(entry) & TrackStatusToTrackdirBits(GetTileTrackStatus(v->tile, TRANSPORT_WATER, 0, entry));
|
||||
(void)ChooseShipTrack(v, v->tile, TRACKDIR_BIT_NONE, reverse_dirs, path_found, dummy_cache, best_origin_dir);
|
||||
(void)ChooseShipTrack(v, tile, TRACKDIR_BIT_NONE, reverse_dirs, 0, path_found, dummy_cache, best_origin_dir);
|
||||
*trackdir = path_found && best_origin_dir != INVALID_TRACKDIR ? best_origin_dir : GetRandomTrackdir(reverse_dirs);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the best depot for a ship.
|
||||
* @param v Ship
|
||||
* @param max_penalty maximum pathfinder cost.
|
||||
* @param may_reverse whether the ship is allowed to reverse.
|
||||
* @return FindDepotData with the best depot tile, cost and whether to reverse.
|
||||
*/
|
||||
static inline FindDepotData FindNearestDepot(const Ship *v, int max_penalty, bool may_reverse)
|
||||
{
|
||||
FindDepotData depot;
|
||||
|
||||
bool path_found = false;
|
||||
ShipPathCache dummy_cache;
|
||||
TileIndex tile = INVALID_TILE;
|
||||
Trackdir best_origin_dir = INVALID_TRACKDIR;
|
||||
const bool search_both_ways = may_reverse && max_penalty == 0;
|
||||
const Trackdir forward_dir = v->GetVehicleTrackdir();
|
||||
const Trackdir reverse_dir = ReverseTrackdir(forward_dir);
|
||||
const TrackdirBits forward_dirs = TrackdirToTrackdirBits(forward_dir);
|
||||
const TrackdirBits reverse_dirs = search_both_ways ? TrackdirToTrackdirBits(reverse_dir) : TRACKDIR_BIT_NONE;
|
||||
(void)ChooseShipTrack(v, tile, forward_dirs, reverse_dirs, max_penalty, path_found, dummy_cache, best_origin_dir);
|
||||
if (path_found) {
|
||||
assert(tile != INVALID_TILE);
|
||||
depot.tile = tile;
|
||||
}
|
||||
return depot;
|
||||
}
|
||||
};
|
||||
|
||||
/** Cost Provider module of YAPF for ships. */
|
||||
|
@ -333,6 +404,11 @@ public:
|
|||
typedef typename Types::NodeList::Item Node; ///< this will be our node type.
|
||||
typedef typename Node::Key Key; ///< key to hash tables.
|
||||
|
||||
protected:
|
||||
int max_cost;
|
||||
|
||||
CYapfCostShipT() : max_cost(0) {}
|
||||
|
||||
/** to access inherited path finder */
|
||||
Tpf &Yapf()
|
||||
{
|
||||
|
@ -340,6 +416,11 @@ public:
|
|||
}
|
||||
|
||||
public:
|
||||
inline void SetMaxCost(int cost)
|
||||
{
|
||||
this->max_cost = cost;
|
||||
}
|
||||
|
||||
inline int CurveCost(Trackdir td1, Trackdir td2)
|
||||
{
|
||||
assert(IsValidTrackdir(td1));
|
||||
|
@ -384,6 +465,10 @@ public:
|
|||
uint8_t speed_frac = (GetEffectiveWaterClass(n.GetTile()) == WATER_CLASS_SEA) ? svi->ocean_speed_frac : svi->canal_speed_frac;
|
||||
if (speed_frac > 0) c += YAPF_TILE_LENGTH * (1 + tf->tiles_skipped) * speed_frac / (256 - speed_frac);
|
||||
|
||||
/* Finish if we already exceeded the maximum path cost (i.e. when
|
||||
* searching for the nearest depot). */
|
||||
if (this->max_cost > 0 && (n.parent->cost + c) > this->max_cost) return false;
|
||||
|
||||
/* Apply it. */
|
||||
n.cost = n.parent->cost + c;
|
||||
return true;
|
||||
|
@ -422,7 +507,7 @@ Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipP
|
|||
{
|
||||
Trackdir best_origin_dir = INVALID_TRACKDIR;
|
||||
const TrackdirBits origin_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir());
|
||||
const Trackdir td_ret = CYapfShip::ChooseShipTrack(v, tile, origin_dirs, TRACKDIR_BIT_NONE, path_found, path_cache, best_origin_dir);
|
||||
const Trackdir td_ret = CYapfShip::ChooseShipTrack(v, tile, origin_dirs, TRACKDIR_BIT_NONE, 0, path_found, path_cache, best_origin_dir);
|
||||
return (td_ret != INVALID_TRACKDIR) ? TrackdirToTrack(td_ret) : INVALID_TRACK;
|
||||
}
|
||||
|
||||
|
@ -430,3 +515,8 @@ bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir)
|
|||
{
|
||||
return CYapfShip::CheckShipReverse(v, trackdir);
|
||||
}
|
||||
|
||||
FindDepotData YapfShipFindNearestDepot(const Ship *v, int max_penalty, bool may_reverse)
|
||||
{
|
||||
return CYapfShip::FindNearestDepot(v, max_penalty, may_reverse);
|
||||
}
|
||||
|
|
|
@ -119,6 +119,7 @@ public:
|
|||
|
||||
protected:
|
||||
Key dest;
|
||||
bool any_ship_depot = false;
|
||||
|
||||
public:
|
||||
void SetDestination(const WaterRegionPatchDesc &water_region_patch)
|
||||
|
@ -126,18 +127,32 @@ public:
|
|||
this->dest.Set(water_region_patch);
|
||||
}
|
||||
|
||||
void SetAnyShipDepotDestination()
|
||||
{
|
||||
this->any_ship_depot = true;
|
||||
}
|
||||
|
||||
protected:
|
||||
TestTileIndexCallBack detect_ship_depot = [&](const TileIndex tile)
|
||||
{
|
||||
return IsShipDepotTile(tile) && GetShipDepotPart(tile) == DEPOT_PART_NORTH && IsTileOwner(tile, Yapf().GetVehicle()->owner);
|
||||
};
|
||||
|
||||
Tpf &Yapf() { return *static_cast<Tpf*>(this); }
|
||||
|
||||
public:
|
||||
inline bool PfDetectDestination(Node &n) const
|
||||
inline bool PfDetectDestination(Node &n)
|
||||
{
|
||||
if (this->any_ship_depot) {
|
||||
return TestTileInWaterRegionPatch(n.key.water_region_patch, this->detect_ship_depot);
|
||||
}
|
||||
|
||||
return n.key == this->dest;
|
||||
}
|
||||
|
||||
inline bool PfCalcEstimate(Node &n)
|
||||
{
|
||||
if (this->PfDetectDestination(n)) {
|
||||
if (this->any_ship_depot || this->PfDetectDestination(n)) {
|
||||
n.estimate = n.cost;
|
||||
return true;
|
||||
}
|
||||
|
@ -218,6 +233,31 @@ public:
|
|||
assert(!path.empty());
|
||||
return path;
|
||||
}
|
||||
|
||||
static std::vector<WaterRegionPatchDesc> FindShipDepotRegionPath(const Ship *v)
|
||||
{
|
||||
const WaterRegionPatchDesc 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.AddOrigin(start_water_region_patch);
|
||||
pf.SetAnyShipDepotDestination();
|
||||
|
||||
/* Find best path. */
|
||||
if (!pf.FindPath(v)) return {}; // Path not found.
|
||||
|
||||
std::vector<WaterRegionPatchDesc> path;
|
||||
Node *node = pf.GetBestNode();
|
||||
while (node != nullptr) {
|
||||
path.push_back(node->key.water_region_patch);
|
||||
node = node->parent;
|
||||
}
|
||||
|
||||
assert(!path.empty());
|
||||
std::ranges::reverse(path);
|
||||
return path;
|
||||
}
|
||||
};
|
||||
|
||||
/** Cost Provider of YAPF for water regions. */
|
||||
|
@ -296,5 +336,8 @@ struct CYapfRegionWater : CYapfT<CYapfRegion_TypesT<CYapfRegionWater, CRegionNod
|
|||
*/
|
||||
std::vector<WaterRegionPatchDesc> YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length)
|
||||
{
|
||||
const bool find_closest_depot = start_tile == INVALID_TILE;
|
||||
|
||||
if (find_closest_depot) return CYapfRegionWater::FindShipDepotRegionPath(v);
|
||||
return CYapfRegionWater::FindWaterRegionPath(v, start_tile, max_returned_path_length);
|
||||
}
|
||||
|
|
|
@ -16,5 +16,6 @@
|
|||
struct Ship;
|
||||
|
||||
std::vector<WaterRegionPatchDesc> YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length);
|
||||
std::vector<WaterRegionPatchDesc> YapfFindShipDepotRegionPath(const Ship *v);
|
||||
|
||||
#endif /* YAPF_SHIP_REGIONS_H */
|
||||
|
|
|
@ -132,7 +132,7 @@ struct RoadVehicle final : public GroundVehicle<RoadVehicle, VEH_ROAD> {
|
|||
uint Crash(bool flooded = false) override;
|
||||
Trackdir GetVehicleTrackdir() const override;
|
||||
TileIndex GetOrderStationLocation(StationID station) override;
|
||||
ClosestDepot FindClosestDepot() override;
|
||||
ClosestDepot FindClosestDepot(bool may_reverse = false) override;
|
||||
|
||||
bool IsBus() const;
|
||||
|
||||
|
|
|
@ -346,7 +346,7 @@ static FindDepotData FindClosestRoadDepot(const RoadVehicle *v, int max_distance
|
|||
return YapfRoadVehicleFindNearestDepot(v, max_distance);
|
||||
}
|
||||
|
||||
ClosestDepot RoadVehicle::FindClosestDepot()
|
||||
ClosestDepot RoadVehicle::FindClosestDepot([[maybe_unused]] bool may_reverse)
|
||||
{
|
||||
FindDepotData rfdd = FindClosestRoadDepot(this, 0);
|
||||
if (rfdd.best_length == UINT_MAX) return ClosestDepot();
|
||||
|
|
|
@ -57,7 +57,7 @@ struct Ship final : public SpecializedVehicle<Ship, VEH_SHIP> {
|
|||
void OnNewEconomyDay() override;
|
||||
Trackdir GetVehicleTrackdir() const override;
|
||||
TileIndex GetOrderStationLocation(StationID station) override;
|
||||
ClosestDepot FindClosestDepot() override;
|
||||
ClosestDepot FindClosestDepot(bool may_reverse = false) override;
|
||||
void UpdateCache();
|
||||
void SetDestTile(TileIndex tile) override;
|
||||
};
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
#include "station_base.h"
|
||||
#include "newgrf_engine.h"
|
||||
#include "pathfinder/yapf/yapf.h"
|
||||
#include "pathfinder/yapf/yapf_ship_regions.h"
|
||||
#include "newgrf_sound.h"
|
||||
#include "spritecache.h"
|
||||
#include "strings_func.h"
|
||||
|
@ -39,13 +38,8 @@
|
|||
|
||||
#include "table/strings.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#include "safeguards.h"
|
||||
|
||||
/** Max distance in tiles (as the crow flies) to search for depots when user clicks "go to depot". */
|
||||
constexpr int MAX_SHIP_DEPOT_SEARCH_DISTANCE = 80;
|
||||
|
||||
/**
|
||||
* Determine the effective #WaterClass for a ship travelling on a tile.
|
||||
* @param tile Tile of interest
|
||||
|
@ -148,57 +142,15 @@ void Ship::GetImage(Direction direction, EngineImageType image_type, VehicleSpri
|
|||
result->Set(_ship_sprites[spritenum] + direction);
|
||||
}
|
||||
|
||||
static const Depot *FindClosestShipDepot(const Vehicle *v, uint max_distance)
|
||||
static const Depot *FindClosestShipDepot(const Vehicle *v, uint max_distance, bool may_reverse = false)
|
||||
{
|
||||
const int max_region_distance = (max_distance / WATER_REGION_EDGE_LENGTH) + 1;
|
||||
const TileIndex tile = v->tile;
|
||||
if (IsShipDepotTile(tile) && IsTileOwner(tile, v->owner)) return Depot::GetByTile(tile);
|
||||
|
||||
static std::unordered_set<int> visited_patch_hashes;
|
||||
static std::deque<WaterRegionPatchDesc> patches_to_search;
|
||||
visited_patch_hashes.clear();
|
||||
patches_to_search.clear();
|
||||
FindDepotData sfdd = YapfShipFindNearestDepot(Ship::From(v), max_distance, may_reverse);
|
||||
|
||||
/* Step 1: find a set of reachable Water Region Patches using BFS. */
|
||||
const WaterRegionPatchDesc start_patch = GetWaterRegionPatchInfo(v->tile);
|
||||
patches_to_search.push_back(start_patch);
|
||||
visited_patch_hashes.insert(CalculateWaterRegionPatchHash(start_patch));
|
||||
|
||||
while (!patches_to_search.empty()) {
|
||||
/* Remove first patch from the queue and make it the current patch. */
|
||||
const WaterRegionPatchDesc current_node = patches_to_search.front();
|
||||
patches_to_search.pop_front();
|
||||
|
||||
/* Add neighbours of the current patch to the search queue. */
|
||||
VisitWaterRegionPatchCallback visit_func = [&](const WaterRegionPatchDesc &water_region_patch) {
|
||||
/* Note that we check the max distance per axis, not the total distance. */
|
||||
if (std::abs(water_region_patch.x - start_patch.x) > max_region_distance ||
|
||||
std::abs(water_region_patch.y - start_patch.y) > max_region_distance) return;
|
||||
|
||||
const int hash = CalculateWaterRegionPatchHash(water_region_patch);
|
||||
if (visited_patch_hashes.count(hash) == 0) {
|
||||
visited_patch_hashes.insert(hash);
|
||||
patches_to_search.push_back(water_region_patch);
|
||||
}
|
||||
};
|
||||
|
||||
VisitWaterRegionPatchNeighbours(current_node, visit_func);
|
||||
}
|
||||
|
||||
/* Step 2: Find the closest depot within the reachable Water Region Patches. */
|
||||
const Depot *best_depot = nullptr;
|
||||
uint best_dist_sq = std::numeric_limits<uint>::max();
|
||||
for (const Depot *depot : Depot::Iterate()) {
|
||||
const TileIndex tile = depot->xy;
|
||||
if (IsShipDepotTile(tile) && IsTileOwner(tile, v->owner)) {
|
||||
const uint dist_sq = DistanceSquare(tile, v->tile);
|
||||
if (dist_sq < best_dist_sq && dist_sq <= max_distance * max_distance &&
|
||||
visited_patch_hashes.count(CalculateWaterRegionPatchHash(GetWaterRegionPatchInfo(tile))) > 0) {
|
||||
best_dist_sq = dist_sq;
|
||||
best_depot = depot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return best_depot;
|
||||
if (sfdd.tile == INVALID_TILE) return nullptr;
|
||||
return Depot::GetByTile(sfdd.tile);
|
||||
}
|
||||
|
||||
static void CheckIfShipNeedsService(Vehicle *v)
|
||||
|
@ -209,7 +161,7 @@ static void CheckIfShipNeedsService(Vehicle *v)
|
|||
return;
|
||||
}
|
||||
|
||||
uint max_distance = _settings_game.pf.yapf.maximum_go_to_depot_penalty / YAPF_TILE_LENGTH;
|
||||
uint max_distance = _settings_game.pf.yapf.maximum_go_to_depot_penalty;
|
||||
|
||||
const Depot *depot = FindClosestShipDepot(v, max_distance);
|
||||
|
||||
|
@ -954,9 +906,9 @@ CommandCost CmdBuildShip(DoCommandFlags flags, TileIndex tile, const Engine *e,
|
|||
return CommandCost();
|
||||
}
|
||||
|
||||
ClosestDepot Ship::FindClosestDepot()
|
||||
ClosestDepot Ship::FindClosestDepot(bool may_reverse)
|
||||
{
|
||||
const Depot *depot = FindClosestShipDepot(this, MAX_SHIP_DEPOT_SEARCH_DISTANCE);
|
||||
const Depot *depot = FindClosestShipDepot(this, 0, may_reverse);
|
||||
if (depot == nullptr) return ClosestDepot();
|
||||
|
||||
return ClosestDepot(depot->xy, depot->index);
|
||||
|
|
|
@ -129,7 +129,7 @@ struct Train final : public GroundVehicle<Train, VEH_TRAIN> {
|
|||
uint Crash(bool flooded = false) override;
|
||||
Trackdir GetVehicleTrackdir() const override;
|
||||
TileIndex GetOrderStationLocation(StationID station) override;
|
||||
ClosestDepot FindClosestDepot() override;
|
||||
ClosestDepot FindClosestDepot(bool may_reverse = false) override;
|
||||
|
||||
void ReserveTrackUnderConsist() const;
|
||||
|
||||
|
|
|
@ -2194,7 +2194,7 @@ static FindDepotData FindClosestTrainDepot(Train *v, int max_distance)
|
|||
return YapfTrainFindNearestDepot(v, max_distance);
|
||||
}
|
||||
|
||||
ClosestDepot Train::FindClosestDepot()
|
||||
ClosestDepot Train::FindClosestDepot([[maybe_unused]] bool may_reverse)
|
||||
{
|
||||
FindDepotData tfdd = FindClosestTrainDepot(this, 0);
|
||||
if (tfdd.best_length == UINT_MAX) return ClosestDepot();
|
||||
|
|
|
@ -2583,7 +2583,7 @@ CommandCost Vehicle::SendToDepot(DoCommandFlags flags, DepotCommandFlags command
|
|||
return CommandCost();
|
||||
}
|
||||
|
||||
ClosestDepot closest_depot = this->FindClosestDepot();
|
||||
ClosestDepot closest_depot = this->FindClosestDepot(true);
|
||||
static const StringID no_depot[] = {STR_ERROR_UNABLE_TO_FIND_ROUTE_TO, STR_ERROR_UNABLE_TO_FIND_LOCAL_DEPOT, STR_ERROR_UNABLE_TO_FIND_LOCAL_DEPOT, STR_ERROR_CAN_T_SEND_AIRCRAFT_TO_HANGAR};
|
||||
if (!closest_depot.found) return CommandCost(no_depot[this->type]);
|
||||
|
||||
|
|
|
@ -792,9 +792,10 @@ public:
|
|||
/**
|
||||
* Find the closest depot for this vehicle and tell us the location,
|
||||
* DestinationID and whether we should reverse.
|
||||
* @param may_reverse Whether the vehicle is allowed to reverse.
|
||||
* @return A structure with information about the closest depot, if found.
|
||||
*/
|
||||
virtual ClosestDepot FindClosestDepot() { return {}; }
|
||||
virtual ClosestDepot FindClosestDepot([[maybe_unused]] bool may_reverse = false) { return {}; }
|
||||
|
||||
virtual void SetDestTile(TileIndex tile) { this->dest_tile = tile; }
|
||||
|
||||
|
|
Loading…
Reference in New Issue