1
0
Fork 0

Compare commits

...

9 Commits

Author SHA1 Message Date
SamuXarick 1d21eb2285
Merge 8031d68d01 into 1d38cbafcb 2025-07-13 23:10:25 +00:00
Peter Nelson 1d38cbafcb Codechange: Use unique_ptr for ScriptInfo instances.
Replaces raw pointers, slightly.
2025-07-14 00:10:14 +01:00
Peter Nelson 992d58d799 Codechange: Pass ScriptInfo by reference to IsSameScript. 2025-07-14 00:10:14 +01:00
Peter Nelson 8f34b7a821 Codechange: Keep Squirrel engine in unique_ptr. 2025-07-14 00:10:14 +01:00
Peter Nelson bf6d0c4934
Codechange: Don't pre-fill font metrics when loading fonts. (#14436)
Each font cache implementation sets its own metrics based on the loaded font, so there is no need to pre-fill with (unscaled, invalid) default metrics.
2025-07-13 23:38:31 +01:00
Michael Lutz 3c4fb21a5e
Fix: [Win32] Link failure with newer Windows SDK version due to WinRT changes. (#14432) 2025-07-13 22:34:32 +01:00
SamuXarick 8031d68d01 Change: Ships may reverse on find closest depot order
Ships may be allowed to reverse when:
- manually ordered to find the closest ship depot.
- executing an updated order to find the closest ship depot after departing from a station.
2025-05-24 18:08:46 +01:00
SamuXarick 7df9426000 Change: Find nearest depot to entry edge in intermediate region
When using an intermediate region, find the depot closest to the edge where it entered the region from.
2025-05-24 18:08:46 +01:00
Samu 0b44deedd0 Fix #5713: Use pathfinder to find closest ship depot
When ships are asked to find the closest depot, the depot that is provided is not always reachable. This patch provides the closest reachable ship depot, by utilizing the pathfinder.
2025-05-24 18:05:59 +01:00
27 changed files with 350 additions and 138 deletions

View File

@ -90,7 +90,7 @@ template <> SQInteger PushClassName<AIInfo, ScriptType::AI>(HSQUIRRELVM vm) { sq
/* Remove the link to the real instance, else it might get deleted by RegisterAI() */ /* Remove the link to the real instance, else it might get deleted by RegisterAI() */
sq_setinstanceup(vm, 2, nullptr); sq_setinstanceup(vm, 2, nullptr);
/* Register the AI to the base system */ /* Register the AI to the base system */
info->GetScanner()->RegisterScript(info); info->GetScanner()->RegisterScript(std::unique_ptr<AIInfo>{info});
return 0; return 0;
} }
@ -136,22 +136,21 @@ bool AIInfo::CanLoadFromVersion(int version) const
/* static */ SQInteger AILibrary::Constructor(HSQUIRRELVM vm) /* static */ SQInteger AILibrary::Constructor(HSQUIRRELVM vm)
{ {
/* Create a new library */ /* Create a new library */
AILibrary *library = new AILibrary(); auto library = std::make_unique<AILibrary>();
SQInteger res = ScriptInfo::Constructor(vm, *library); SQInteger res = ScriptInfo::Constructor(vm, *library);
if (res != 0) { if (res != 0) {
delete library;
return res; return res;
} }
/* Cache the category */ /* Cache the category */
if (!library->CheckMethod("GetCategory") || !library->engine->CallStringMethod(library->SQ_instance, "GetCategory", &library->category, MAX_GET_OPS)) { if (!library->CheckMethod("GetCategory") || !library->engine->CallStringMethod(library->SQ_instance, "GetCategory", &library->category, MAX_GET_OPS)) {
delete library;
return SQ_ERROR; return SQ_ERROR;
} }
/* Register the Library to the base system */ /* Register the Library to the base system */
library->GetScanner()->RegisterScript(library); ScriptScanner *scanner = library->GetScanner();
scanner->RegisterScript(std::move(library));
return 0; return 0;
} }

View File

@ -29,7 +29,7 @@ void AIScannerInfo::Initialize()
{ {
ScriptScanner::Initialize("AIScanner"); ScriptScanner::Initialize("AIScanner");
ScriptAllocatorScope alloc_scope(this->engine); ScriptAllocatorScope alloc_scope(this->engine.get());
/* Create the dummy AI */ /* Create the dummy AI */
this->main_script = "%_dummy"; this->main_script = "%_dummy";

View File

@ -110,7 +110,7 @@ struct Aircraft final : public SpecializedVehicle<Aircraft, VEH_AIRCRAFT> {
uint Crash(bool flooded = false) override; uint Crash(bool flooded = false) override;
TileIndex GetOrderStationLocation(StationID station) override; TileIndex GetOrderStationLocation(StationID station) override;
TileIndex GetCargoTile() const override { return this->First()->tile; } 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 * Check if the aircraft type is a normal flying device; eg

View File

@ -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); const Station *st = GetTargetAirportIfValid(this);
/* If the station is not a valid airport or if it has no hangars */ /* If the station is not a valid airport or if it has no hangars */

View File

@ -22,9 +22,10 @@
#include "safeguards.h" #include "safeguards.h"
/** Default heights for the different sizes of fonts. */ /** Default unscaled heights for the different sizes of fonts. */
static const int _default_font_height[FS_END] = {10, 6, 18, 10}; /* static */ const int FontCache::DEFAULT_FONT_HEIGHT[FS_END] = {10, 6, 18, 10};
static const int _default_font_ascender[FS_END] = { 8, 5, 15, 8}; /** Default unscaled ascenders for the different sizes of fonts. */
/* static */ const int FontCache::DEFAULT_FONT_ASCENDER[FS_END] = {8, 5, 15, 8};
FontCacheSettings _fcsettings; FontCacheSettings _fcsettings;
@ -32,8 +33,7 @@ FontCacheSettings _fcsettings;
* Create a new font cache. * Create a new font cache.
* @param fs The size of the font. * @param fs The size of the font.
*/ */
FontCache::FontCache(FontSize fs) : parent(FontCache::Get(fs)), fs(fs), height(_default_font_height[fs]), FontCache::FontCache(FontSize fs) : parent(FontCache::Get(fs)), fs(fs)
ascender(_default_font_ascender[fs]), descender(_default_font_ascender[fs] - _default_font_height[fs])
{ {
assert(this->parent == nullptr || this->fs == this->parent->fs); assert(this->parent == nullptr || this->fs == this->parent->fs);
FontCache::caches[this->fs] = this; FontCache::caches[this->fs] = this;
@ -50,7 +50,7 @@ FontCache::~FontCache()
int FontCache::GetDefaultFontHeight(FontSize fs) int FontCache::GetDefaultFontHeight(FontSize fs)
{ {
return _default_font_height[fs]; return FontCache::DEFAULT_FONT_HEIGHT[fs];
} }
/** /**

View File

@ -23,9 +23,9 @@ protected:
static FontCache *caches[FS_END]; ///< All the font caches. static FontCache *caches[FS_END]; ///< All the font caches.
FontCache *parent; ///< The parent of this font cache. FontCache *parent; ///< The parent of this font cache.
const FontSize fs; ///< The size of the font. const FontSize fs; ///< The size of the font.
int height; ///< The height of the font. int height = 0; ///< The height of the font.
int ascender; ///< The ascender value of the font. int ascender = 0; ///< The ascender value of the font.
int descender; ///< The descender value of the font. int descender = 0; ///< The descender value of the font.
public: public:
FontCache(FontSize fs); FontCache(FontSize fs);
@ -33,6 +33,11 @@ public:
static void InitializeFontCaches(); static void InitializeFontCaches();
/** Default unscaled font heights. */
static const int DEFAULT_FONT_HEIGHT[FS_END];
/** Default unscaled font ascenders. */
static const int DEFAULT_FONT_ASCENDER[FS_END];
static int GetDefaultFontHeight(FontSize fs); static int GetDefaultFontHeight(FontSize fs);
/** /**

View File

@ -78,7 +78,7 @@ template <> SQInteger PushClassName<GameInfo, ScriptType::GS>(HSQUIRRELVM vm) {
/* Remove the link to the real instance, else it might get deleted by RegisterGame() */ /* Remove the link to the real instance, else it might get deleted by RegisterGame() */
sq_setinstanceup(vm, 2, nullptr); sq_setinstanceup(vm, 2, nullptr);
/* Register the Game to the base system */ /* Register the Game to the base system */
info->GetScanner()->RegisterScript(info); info->GetScanner()->RegisterScript(std::unique_ptr<GameInfo>{info});
return 0; return 0;
} }
@ -106,22 +106,21 @@ bool GameInfo::CanLoadFromVersion(int version) const
/* static */ SQInteger GameLibrary::Constructor(HSQUIRRELVM vm) /* static */ SQInteger GameLibrary::Constructor(HSQUIRRELVM vm)
{ {
/* Create a new library */ /* Create a new library */
GameLibrary *library = new GameLibrary(); auto library = std::make_unique<GameLibrary>();
SQInteger res = ScriptInfo::Constructor(vm, *library); SQInteger res = ScriptInfo::Constructor(vm, *library);
if (res != 0) { if (res != 0) {
delete library;
return res; return res;
} }
/* Cache the category */ /* Cache the category */
if (!library->CheckMethod("GetCategory") || !library->engine->CallStringMethod(library->SQ_instance, "GetCategory", &library->category, MAX_GET_OPS)) { if (!library->CheckMethod("GetCategory") || !library->engine->CallStringMethod(library->SQ_instance, "GetCategory", &library->category, MAX_GET_OPS)) {
delete library;
return SQ_ERROR; return SQ_ERROR;
} }
/* Register the Library to the base system */ /* Register the Library to the base system */
library->GetScanner()->RegisterScript(library); ScriptScanner *scanner = library->GetScanner();
scanner->RegisterScript(std::move(library));
return 0; return 0;
} }

View File

@ -92,8 +92,8 @@ GameLibrary *GameScannerLibrary::FindLibrary(const std::string &library, int ver
std::string library_name = fmt::format("{}.{}", library, version); std::string library_name = fmt::format("{}.{}", library, version);
/* Check if the library + version exists */ /* Check if the library + version exists */
ScriptInfoList::iterator it = this->info_list.find(library_name); auto it = this->info_list.find(library_name);
if (it == this->info_list.end()) return nullptr; if (it == this->info_list.end()) return nullptr;
return static_cast<GameLibrary *>((*it).second); return static_cast<GameLibrary *>(it->second);
} }

View File

@ -1896,10 +1896,11 @@ VehicleOrderID ProcessConditionalOrder(const Order *order, const Vehicle *v)
* Update the vehicle's destination tile from an order. * Update the vehicle's destination tile from an order.
* @param order the order the vehicle currently has * @param order the order the vehicle currently has
* @param v the vehicle to update * @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 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. * @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()) { if (conditional_depth > v->GetNumOrders()) {
v->current_order.Free(); 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; if (v->dest_tile == 0 && TimerGameEconomy::date_fract != (v->index % Ticks::DAY_TICKS)) break;
/* We need to search for the nearest depot (hangar). */ /* 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) { if (closest_depot.found) {
/* PBS reservations cannot reverse */ /* PBS reservations cannot reverse */
@ -2018,7 +2019,7 @@ bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth, bool
} }
v->current_order = *order; 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; break;
} }
return UpdateOrderDest(v, order) && may_reverse; return UpdateOrderDest(v, order, may_reverse) && may_reverse;
} }
/** /**

View File

@ -20,7 +20,7 @@ void InvalidateVehicleOrder(const Vehicle *v, int data);
void CheckOrders(const Vehicle*); void CheckOrders(const Vehicle*);
void DeleteVehicleOrders(Vehicle *v, bool keep_orderlist = false, bool reset_order_indices = true); void DeleteVehicleOrders(Vehicle *v, bool keep_orderlist = false, bool reset_order_indices = true);
bool ProcessOrders(Vehicle *v); 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); VehicleOrderID ProcessConditionalOrder(const Order *order, const Vehicle *v);
uint GetOrderDistance(VehicleOrderID prev, VehicleOrderID cur, const Vehicle *v, int conditional_depth = 0); uint GetOrderDistance(VehicleOrderID prev, VehicleOrderID cur, const Vehicle *v, int conditional_depth = 0);

View File

@ -430,3 +430,83 @@ void PrintWaterRegionDebugInfo(TileIndex tile)
{ {
GetUpdatedWaterRegion(tile).PrintDebugInfo(); 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 &current_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;
}

View File

@ -64,4 +64,8 @@ void AllocateWaterRegions();
void PrintWaterRegionDebugInfo(TileIndex tile); 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 */ #endif /* WATER_REGIONS_H */

View File

@ -34,6 +34,17 @@ Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipP
*/ */
bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir); 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. * Finds the best path for given road vehicle using YAPF.
* @param v the RV that needs to find a path * @param v the RV that needs to find a path

View File

@ -36,6 +36,7 @@ protected:
TileIndex dest_tile; TileIndex dest_tile;
TrackdirBits dest_trackdirs; TrackdirBits dest_trackdirs;
StationID dest_station; StationID dest_station;
bool any_ship_depot = false;
bool has_intermediate_dest = false; bool has_intermediate_dest = false;
TileIndex intermediate_dest_tile; TileIndex intermediate_dest_tile;
@ -55,6 +56,11 @@ public:
} }
} }
void SetAnyShipDepotDestination()
{
this->any_ship_depot = true;
}
void SetIntermediateDestination(const WaterRegionPatchDesc &water_region_patch) void SetIntermediateDestination(const WaterRegionPatchDesc &water_region_patch)
{ {
this->has_intermediate_dest = true; this->has_intermediate_dest = true;
@ -69,10 +75,16 @@ protected:
return *static_cast<Tpf*>(this); 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: public:
/** Called by YAPF to detect if node ends in the desired destination. */ /** Called by YAPF to detect if node ends in the desired destination. */
inline bool PfDetectDestination(Node &n) 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); 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); 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 * 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. * 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_x_offs[] = { -1, 0, 1, 0 };
static const int dg_dir_to_y_offs[] = { 0, 1, 0, -1 }; 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; n.estimate = n.cost;
return true; return true;
} }
@ -158,7 +175,7 @@ public:
} }
/** Restricts the search by creating corridor or water regions through which the ship is allowed to travel. */ /** 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(); this->water_region_corridor.clear();
for (const WaterRegionPatchDesc &path_entry : path) this->water_region_corridor.push_back(path_entry); for (const WaterRegionPatchDesc &path_entry : path) this->water_region_corridor.push_back(path_entry);
@ -211,16 +228,20 @@ public:
return result; 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) 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()) { if (high_level_path.empty()) {
path_found = false; path_found = false;
/* Make the ship move around aimlessly. This prevents repeated pathfinder calls and clearly indicates that the ship is lost. */ /* 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); 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. /* 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. * 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. */ * 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 */ /* Set origin and destination nodes */
pf.SetOrigin(v->tile, forward_dirs | reverse_dirs); pf.SetOrigin(v->tile, forward_dirs | reverse_dirs);
if (find_closest_depot) {
pf.SetAnyShipDepotDestination();
} else {
pf.SetDestination(v); 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()); 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 /* 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. */ * 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. */ /* Find best path. */
path_found = pf.FindPath(v); 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. */ /* 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); 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 /* 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 * 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. */ * caching the full path the ship can get stuck in a loop. */
@ -254,7 +296,7 @@ public:
while (node->parent) { while (node->parent) {
const WaterRegionPatchDesc node_water_patch = GetWaterRegionPatchInfo(node->GetTile()); 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; 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. /* 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; bool path_found = false;
ShipPathCache dummy_cache; ShipPathCache dummy_cache;
TileIndex tile = v->tile;
Trackdir best_origin_dir = INVALID_TRACKDIR; Trackdir best_origin_dir = INVALID_TRACKDIR;
if (trackdir == nullptr) { if (trackdir == nullptr) {
@ -310,17 +353,45 @@ public:
const Trackdir reverse_dir = ReverseTrackdir(v->GetVehicleTrackdir()); const Trackdir reverse_dir = ReverseTrackdir(v->GetVehicleTrackdir());
const TrackdirBits forward_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir()); const TrackdirBits forward_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir());
const TrackdirBits reverse_dirs = TrackdirToTrackdirBits(reverse_dir); 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; return path_found && best_origin_dir == reverse_dir;
} else { } else {
/* This gets called when a ship suddenly can't move forward, e.g. due to terraforming. */ /* 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 DiagDirection entry = ReverseDiagDir(VehicleExitDir(v->direction, v->state));
const TrackdirBits reverse_dirs = DiagdirReachesTrackdirs(entry) & TrackStatusToTrackdirBits(GetTileTrackStatus(v->tile, TRANSPORT_WATER, 0, entry)); 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); *trackdir = path_found && best_origin_dir != INVALID_TRACKDIR ? best_origin_dir : GetRandomTrackdir(reverse_dirs);
return true; 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. */ /** 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 Types::NodeList::Item Node; ///< this will be our node type.
typedef typename Node::Key Key; ///< key to hash tables. typedef typename Node::Key Key; ///< key to hash tables.
protected:
int max_cost;
CYapfCostShipT() : max_cost(0) {}
/** to access inherited path finder */ /** to access inherited path finder */
Tpf &Yapf() Tpf &Yapf()
{ {
@ -340,6 +416,11 @@ public:
} }
public: public:
inline void SetMaxCost(int cost)
{
this->max_cost = cost;
}
inline int CurveCost(Trackdir td1, Trackdir td2) inline int CurveCost(Trackdir td1, Trackdir td2)
{ {
assert(IsValidTrackdir(td1)); 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; 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); 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. */ /* Apply it. */
n.cost = n.parent->cost + c; n.cost = n.parent->cost + c;
return true; return true;
@ -422,7 +507,7 @@ Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipP
{ {
Trackdir best_origin_dir = INVALID_TRACKDIR; Trackdir best_origin_dir = INVALID_TRACKDIR;
const TrackdirBits origin_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir()); 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; 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); return CYapfShip::CheckShipReverse(v, trackdir);
} }
FindDepotData YapfShipFindNearestDepot(const Ship *v, int max_penalty, bool may_reverse)
{
return CYapfShip::FindNearestDepot(v, max_penalty, may_reverse);
}

View File

@ -119,6 +119,7 @@ public:
protected: protected:
Key dest; Key dest;
bool any_ship_depot = false;
public: public:
void SetDestination(const WaterRegionPatchDesc &water_region_patch) void SetDestination(const WaterRegionPatchDesc &water_region_patch)
@ -126,18 +127,32 @@ public:
this->dest.Set(water_region_patch); this->dest.Set(water_region_patch);
} }
void SetAnyShipDepotDestination()
{
this->any_ship_depot = true;
}
protected: 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); } Tpf &Yapf() { return *static_cast<Tpf*>(this); }
public: 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; return n.key == this->dest;
} }
inline bool PfCalcEstimate(Node &n) inline bool PfCalcEstimate(Node &n)
{ {
if (this->PfDetectDestination(n)) { if (this->any_ship_depot || this->PfDetectDestination(n)) {
n.estimate = n.cost; n.estimate = n.cost;
return true; return true;
} }
@ -218,6 +233,31 @@ public:
assert(!path.empty()); assert(!path.empty());
return path; 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. */ /** 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) 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); return CYapfRegionWater::FindWaterRegionPath(v, start_tile, max_returned_path_length);
} }

View File

@ -16,5 +16,6 @@
struct Ship; struct Ship;
std::vector<WaterRegionPatchDesc> YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length); 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 */ #endif /* YAPF_SHIP_REGIONS_H */

View File

@ -132,7 +132,7 @@ struct RoadVehicle final : public GroundVehicle<RoadVehicle, VEH_ROAD> {
uint Crash(bool flooded = false) override; uint Crash(bool flooded = false) override;
Trackdir GetVehicleTrackdir() const override; Trackdir GetVehicleTrackdir() const override;
TileIndex GetOrderStationLocation(StationID station) override; TileIndex GetOrderStationLocation(StationID station) override;
ClosestDepot FindClosestDepot() override; ClosestDepot FindClosestDepot(bool may_reverse = false) override;
bool IsBus() const; bool IsBus() const;

View File

@ -346,7 +346,7 @@ static FindDepotData FindClosestRoadDepot(const RoadVehicle *v, int max_distance
return YapfRoadVehicleFindNearestDepot(v, max_distance); return YapfRoadVehicleFindNearestDepot(v, max_distance);
} }
ClosestDepot RoadVehicle::FindClosestDepot() ClosestDepot RoadVehicle::FindClosestDepot([[maybe_unused]] bool may_reverse)
{ {
FindDepotData rfdd = FindClosestRoadDepot(this, 0); FindDepotData rfdd = FindClosestRoadDepot(this, 0);
if (rfdd.best_length == UINT_MAX) return ClosestDepot(); if (rfdd.best_length == UINT_MAX) return ClosestDepot();

View File

@ -44,10 +44,7 @@ bool ScriptScanner::AddFile(const std::string &filename, size_t, const std::stri
return true; return true;
} }
ScriptScanner::ScriptScanner() : ScriptScanner::ScriptScanner() = default;
engine(nullptr)
{
}
void ScriptScanner::ResetEngine() void ScriptScanner::ResetEngine()
{ {
@ -58,7 +55,7 @@ void ScriptScanner::ResetEngine()
void ScriptScanner::Initialize(std::string_view name) void ScriptScanner::Initialize(std::string_view name)
{ {
this->engine = new Squirrel(name); this->engine = std::make_unique<Squirrel>(name);
this->RescanDir(); this->RescanDir();
@ -68,8 +65,6 @@ void ScriptScanner::Initialize(std::string_view name)
ScriptScanner::~ScriptScanner() ScriptScanner::~ScriptScanner()
{ {
this->Reset(); this->Reset();
delete this->engine;
} }
void ScriptScanner::RescanDir() void ScriptScanner::RescanDir()
@ -83,15 +78,12 @@ void ScriptScanner::RescanDir()
void ScriptScanner::Reset() void ScriptScanner::Reset()
{ {
for (const auto &item : this->info_list) {
delete item.second;
}
this->info_list.clear(); this->info_list.clear();
this->info_single_list.clear(); this->info_single_list.clear();
this->info_vector.clear();
} }
void ScriptScanner::RegisterScript(ScriptInfo *info) void ScriptScanner::RegisterScript(std::unique_ptr<ScriptInfo> &&info)
{ {
std::string script_original_name = this->GetScriptName(*info); std::string script_original_name = this->GetScriptName(*info);
std::string script_name = fmt::format("{}.{}", script_original_name, info->GetVersion()); std::string script_name = fmt::format("{}.{}", script_original_name, info->GetVersion());
@ -99,7 +91,6 @@ void ScriptScanner::RegisterScript(ScriptInfo *info)
/* Check if GetShortName follows the rules */ /* Check if GetShortName follows the rules */
if (info->GetShortName().size() != 4) { if (info->GetShortName().size() != 4) {
Debug(script, 0, "The script '{}' returned a string from GetShortName() which is not four characters. Unable to load the script.", info->GetName()); Debug(script, 0, "The script '{}' returned a string from GetShortName() which is not four characters. Unable to load the script.", info->GetName());
delete info;
return; return;
} }
@ -111,7 +102,6 @@ void ScriptScanner::RegisterScript(ScriptInfo *info)
#else #else
if (it->second->GetMainScript() == info->GetMainScript()) { if (it->second->GetMainScript() == info->GetMainScript()) {
#endif #endif
delete info;
return; return;
} }
@ -120,20 +110,20 @@ void ScriptScanner::RegisterScript(ScriptInfo *info)
Debug(script, 1, " 2: {}", info->GetMainScript()); Debug(script, 1, " 2: {}", info->GetMainScript());
Debug(script, 1, "The first is taking precedence."); Debug(script, 1, "The first is taking precedence.");
delete info;
return; return;
} }
this->info_list[script_name] = info; ScriptInfo *script_info = this->info_vector.emplace_back(std::move(info)).get();
this->info_list[script_name] = script_info;
if (!info->IsDeveloperOnly() || _settings_client.gui.ai_developer_tools) { if (!script_info->IsDeveloperOnly() || _settings_client.gui.ai_developer_tools) {
/* Add the script to the 'unique' script list, where only the highest version /* Add the script to the 'unique' script list, where only the highest version
* of the script is registered. */ * of the script is registered. */
auto it = this->info_single_list.find(script_original_name); auto it = this->info_single_list.find(script_original_name);
if (it == this->info_single_list.end()) { if (it == this->info_single_list.end()) {
this->info_single_list[script_original_name] = info; this->info_single_list[script_original_name] = script_info;
} else if (it->second->GetVersion() < info->GetVersion()) { } else if (it->second->GetVersion() < script_info->GetVersion()) {
it->second = info; it->second = script_info;
} }
} }
} }
@ -195,17 +185,17 @@ struct ScriptFileChecksumCreator : FileScanner {
* @param info The script to get the shortname and md5 sum from. * @param info The script to get the shortname and md5 sum from.
* @return True iff they're the same. * @return True iff they're the same.
*/ */
static bool IsSameScript(const ContentInfo &ci, bool md5sum, ScriptInfo *info, Subdirectory dir) static bool IsSameScript(const ContentInfo &ci, bool md5sum, const ScriptInfo &info, Subdirectory dir)
{ {
uint32_t id = 0; uint32_t id = 0;
auto str = std::string_view{info->GetShortName()}.substr(0, 4); auto str = std::string_view{info.GetShortName()}.substr(0, 4);
for (size_t j = 0; j < str.size(); j++) id |= static_cast<uint8_t>(str[j]) << (8 * j); for (size_t j = 0; j < str.size(); j++) id |= static_cast<uint8_t>(str[j]) << (8 * j);
if (id != ci.unique_id) return false; if (id != ci.unique_id) return false;
if (!md5sum) return true; if (!md5sum) return true;
ScriptFileChecksumCreator checksum(dir); ScriptFileChecksumCreator checksum(dir);
const auto &tar_filename = info->GetTarFile(); const auto &tar_filename = info.GetTarFile();
TarList::iterator iter; TarList::iterator iter;
if (!tar_filename.empty() && (iter = _tar_list[dir].find(tar_filename)) != _tar_list[dir].end()) { if (!tar_filename.empty() && (iter = _tar_list[dir].find(tar_filename)) != _tar_list[dir].end()) {
/* The main script is in a tar file, so find all files that /* The main script is in a tar file, so find all files that
@ -224,7 +214,7 @@ static bool IsSameScript(const ContentInfo &ci, bool md5sum, ScriptInfo *info, S
/* There'll always be at least 1 path separator character in a script /* There'll always be at least 1 path separator character in a script
* main script name as the search algorithm requires the main script to * main script name as the search algorithm requires the main script to
* be in a subdirectory of the script directory; so <dir>/<path>/main.nut. */ * be in a subdirectory of the script directory; so <dir>/<path>/main.nut. */
const std::string &main_script = info->GetMainScript(); const std::string &main_script = info.GetMainScript();
std::string path = main_script.substr(0, main_script.find_last_of(PATHSEPCHAR)); std::string path = main_script.substr(0, main_script.find_last_of(PATHSEPCHAR));
checksum.Scan(".nut", path); checksum.Scan(".nut", path);
} }
@ -235,7 +225,7 @@ static bool IsSameScript(const ContentInfo &ci, bool md5sum, ScriptInfo *info, S
bool ScriptScanner::HasScript(const ContentInfo &ci, bool md5sum) bool ScriptScanner::HasScript(const ContentInfo &ci, bool md5sum)
{ {
for (const auto &item : this->info_list) { for (const auto &item : this->info_list) {
if (IsSameScript(ci, md5sum, item.second, this->GetDirectory())) return true; if (IsSameScript(ci, md5sum, *item.second, this->GetDirectory())) return true;
} }
return false; return false;
} }
@ -243,7 +233,7 @@ bool ScriptScanner::HasScript(const ContentInfo &ci, bool md5sum)
std::optional<std::string_view> ScriptScanner::FindMainScript(const ContentInfo &ci, bool md5sum) std::optional<std::string_view> ScriptScanner::FindMainScript(const ContentInfo &ci, bool md5sum)
{ {
for (const auto &item : this->info_list) { for (const auto &item : this->info_list) {
if (IsSameScript(ci, md5sum, item.second, this->GetDirectory())) return item.second->GetMainScript(); if (IsSameScript(ci, md5sum, *item.second, this->GetDirectory())) return item.second->GetMainScript();
} }
return std::nullopt; return std::nullopt;
} }

View File

@ -13,7 +13,7 @@
#include "../fileio_func.h" #include "../fileio_func.h"
#include "../string_func.h" #include "../string_func.h"
typedef std::map<std::string, class ScriptInfo *, CaseInsensitiveComparator> ScriptInfoList; ///< Type for the list of scripts. using ScriptInfoList = std::map<std::string, class ScriptInfo *, CaseInsensitiveComparator>; ///< Type for the list of scripts.
/** Scanner to help finding scripts. */ /** Scanner to help finding scripts. */
class ScriptScanner : public FileScanner { class ScriptScanner : public FileScanner {
@ -26,7 +26,7 @@ public:
/** /**
* Get the engine of the main squirrel handler (it indexes all available scripts). * Get the engine of the main squirrel handler (it indexes all available scripts).
*/ */
class Squirrel *GetEngine() { return this->engine; } class Squirrel *GetEngine() { return this->engine.get(); }
/** /**
* Get the current main script the ScanDir is currently tracking. * Get the current main script the ScanDir is currently tracking.
@ -51,7 +51,7 @@ public:
/** /**
* Register a ScriptInfo to the scanner. * Register a ScriptInfo to the scanner.
*/ */
void RegisterScript(class ScriptInfo *info); void RegisterScript(std::unique_ptr<class ScriptInfo> &&info);
/** /**
* Get the list of registered scripts to print on the console. * Get the list of registered scripts to print on the console.
@ -84,10 +84,12 @@ public:
void RescanDir(); void RescanDir();
protected: protected:
class Squirrel *engine; ///< The engine we're scanning with. std::unique_ptr<class Squirrel> engine; ///< The engine we're scanning with.
std::string main_script; ///< The full path of the script. std::string main_script; ///< The full path of the script.
std::string tar_file; ///< If, which tar file the script was in. std::string tar_file; ///< If, which tar file the script was in.
std::vector<std::unique_ptr<ScriptInfo>> info_vector;
ScriptInfoList info_list; ///< The list of all script. ScriptInfoList info_list; ///< The list of all script.
ScriptInfoList info_single_list; ///< The list of all unique script. The best script (highest version) is shown. ScriptInfoList info_single_list; ///< The list of all unique script. The best script (highest version) is shown.

View File

@ -57,7 +57,7 @@ struct Ship final : public SpecializedVehicle<Ship, VEH_SHIP> {
void OnNewEconomyDay() override; void OnNewEconomyDay() override;
Trackdir GetVehicleTrackdir() const override; Trackdir GetVehicleTrackdir() const override;
TileIndex GetOrderStationLocation(StationID station) override; TileIndex GetOrderStationLocation(StationID station) override;
ClosestDepot FindClosestDepot() override; ClosestDepot FindClosestDepot(bool may_reverse = false) override;
void UpdateCache(); void UpdateCache();
void SetDestTile(TileIndex tile) override; void SetDestTile(TileIndex tile) override;
}; };

View File

@ -17,7 +17,6 @@
#include "station_base.h" #include "station_base.h"
#include "newgrf_engine.h" #include "newgrf_engine.h"
#include "pathfinder/yapf/yapf.h" #include "pathfinder/yapf/yapf.h"
#include "pathfinder/yapf/yapf_ship_regions.h"
#include "newgrf_sound.h" #include "newgrf_sound.h"
#include "spritecache.h" #include "spritecache.h"
#include "strings_func.h" #include "strings_func.h"
@ -39,13 +38,8 @@
#include "table/strings.h" #include "table/strings.h"
#include <unordered_set>
#include "safeguards.h" #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. * Determine the effective #WaterClass for a ship travelling on a tile.
* @param tile Tile of interest * @param tile Tile of interest
@ -148,57 +142,15 @@ void Ship::GetImage(Direction direction, EngineImageType image_type, VehicleSpri
result->Set(_ship_sprites[spritenum] + direction); 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; FindDepotData sfdd = YapfShipFindNearestDepot(Ship::From(v), max_distance, may_reverse);
static std::deque<WaterRegionPatchDesc> patches_to_search;
visited_patch_hashes.clear();
patches_to_search.clear();
/* Step 1: find a set of reachable Water Region Patches using BFS. */ if (sfdd.tile == INVALID_TILE) return nullptr;
const WaterRegionPatchDesc start_patch = GetWaterRegionPatchInfo(v->tile); return Depot::GetByTile(sfdd.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;
} }
static void CheckIfShipNeedsService(Vehicle *v) static void CheckIfShipNeedsService(Vehicle *v)
@ -209,7 +161,7 @@ static void CheckIfShipNeedsService(Vehicle *v)
return; 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); const Depot *depot = FindClosestShipDepot(v, max_distance);
@ -954,9 +906,9 @@ CommandCost CmdBuildShip(DoCommandFlags flags, TileIndex tile, const Engine *e,
return CommandCost(); 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(); if (depot == nullptr) return ClosestDepot();
return ClosestDepot(depot->xy, depot->index); return ClosestDepot(depot->xy, depot->index);

View File

@ -129,7 +129,7 @@ struct Train final : public GroundVehicle<Train, VEH_TRAIN> {
uint Crash(bool flooded = false) override; uint Crash(bool flooded = false) override;
Trackdir GetVehicleTrackdir() const override; Trackdir GetVehicleTrackdir() const override;
TileIndex GetOrderStationLocation(StationID station) override; TileIndex GetOrderStationLocation(StationID station) override;
ClosestDepot FindClosestDepot() override; ClosestDepot FindClosestDepot(bool may_reverse = false) override;
void ReserveTrackUnderConsist() const; void ReserveTrackUnderConsist() const;

View File

@ -2194,7 +2194,7 @@ static FindDepotData FindClosestTrainDepot(Train *v, int max_distance)
return YapfTrainFindNearestDepot(v, max_distance); return YapfTrainFindNearestDepot(v, max_distance);
} }
ClosestDepot Train::FindClosestDepot() ClosestDepot Train::FindClosestDepot([[maybe_unused]] bool may_reverse)
{ {
FindDepotData tfdd = FindClosestTrainDepot(this, 0); FindDepotData tfdd = FindClosestTrainDepot(this, 0);
if (tfdd.best_length == UINT_MAX) return ClosestDepot(); if (tfdd.best_length == UINT_MAX) return ClosestDepot();

View File

@ -2583,7 +2583,7 @@ CommandCost Vehicle::SendToDepot(DoCommandFlags flags, DepotCommandFlags command
return CommandCost(); 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}; 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]); if (!closest_depot.found) return CommandCost(no_depot[this->type]);

View File

@ -792,9 +792,10 @@ public:
/** /**
* Find the closest depot for this vehicle and tell us the location, * Find the closest depot for this vehicle and tell us the location,
* DestinationID and whether we should reverse. * 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. * @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; } virtual void SetDestTile(TileIndex tile) { this->dest_tile = tile; }

View File

@ -401,6 +401,40 @@ static void CancelIMEComposition(HWND hwnd)
HandleTextInput({}, true); HandleTextInput({}, true);
} }
#if defined(_MSC_VER) && defined(NTDDI_WIN10_RS4)
/* We only use WinRT functions on Windows 10 or later. Unfortunately, newer Windows SDKs are now
* linking the two functions below directly instead of using dynamic linking as previously.
* To avoid any runtime linking errors on Windows 7 or older, we stub in our own dynamic
* linking trampoline. */
static LibraryLoader _combase("combase.dll");
extern "C" int32_t __stdcall WINRT_IMPL_RoOriginateLanguageException(int32_t error, void *message, void *languageException) noexcept
{
typedef BOOL(WINAPI *PFNRoOriginateLanguageException)(int32_t, void *, void *);
static PFNRoOriginateLanguageException RoOriginateLanguageException = _combase.GetFunction("RoOriginateLanguageException");
if (RoOriginateLanguageException != nullptr) {
return RoOriginateLanguageException(error, message, languageException);
} else {
return TRUE;
}
}
extern "C" int32_t __stdcall WINRT_IMPL_RoGetActivationFactory(void *classId, winrt::guid const &iid, void **factory) noexcept
{
typedef BOOL(WINAPI *PFNRoGetActivationFactory)(void *, winrt::guid const &, void **);
static PFNRoGetActivationFactory RoGetActivationFactory = _combase.GetFunction("RoGetActivationFactory");
if (RoGetActivationFactory != nullptr) {
return RoGetActivationFactory(classId, iid, factory);
} else {
*factory = nullptr;
return winrt::impl::error_class_not_available;
}
}
#endif
static bool IsDarkModeEnabled() static bool IsDarkModeEnabled()
{ {
/* Only build if SDK is Windows 10 1803 or later. */ /* Only build if SDK is Windows 10 1803 or later. */