diff --git a/src/3rdparty/squirrel/include/squirrel.h b/src/3rdparty/squirrel/include/squirrel.h index 6a8976abb8..d038f9f8a2 100644 --- a/src/3rdparty/squirrel/include/squirrel.h +++ b/src/3rdparty/squirrel/include/squirrel.h @@ -361,6 +361,15 @@ void sq_setdebughook(HSQUIRRELVM v); #define sq_isweakref(o) ((o)._type==OT_WEAKREF) #define sq_type(o) ((o)._type) +/* Limit the total number of ops that can be consumed by an operation */ +struct SQOpsLimiter { + SQOpsLimiter(HSQUIRRELVM v, SQInteger ops, const char *label); + ~SQOpsLimiter(); +private: + HSQUIRRELVM _v; + SQInteger _ops; +}; + /* deprecated */ #define sq_createslot(v,n) sq_newslot(v,n,SQFalse) diff --git a/src/3rdparty/squirrel/squirrel/sqapi.cpp b/src/3rdparty/squirrel/squirrel/sqapi.cpp index f68553b1ba..d241dceda1 100644 --- a/src/3rdparty/squirrel/squirrel/sqapi.cpp +++ b/src/3rdparty/squirrel/squirrel/sqapi.cpp @@ -1322,3 +1322,16 @@ void sq_free(void *p,SQUnsignedInteger size) SQ_FREE(p,size); } +SQOpsLimiter::SQOpsLimiter(HSQUIRRELVM v, SQInteger ops, const char *label) : _v(v) +{ + this->_ops = v->_ops_till_suspend_error_threshold; + if (this->_ops == INT64_MIN) { + v->_ops_till_suspend_error_threshold = v->_ops_till_suspend - ops; + v->_ops_till_suspend_error_label = label; + } +} + +SQOpsLimiter::~SQOpsLimiter() +{ + this->_v->_ops_till_suspend_error_threshold = this->_ops; +} diff --git a/src/genworld.cpp b/src/genworld.cpp index d2ff24a4a0..425c7ccb67 100644 --- a/src/genworld.cpp +++ b/src/genworld.cpp @@ -34,6 +34,7 @@ #include "string_func.h" #include "thread.h" #include "tgp.h" +#include "pathfinder/water_regions.h" #include "signal_func.h" #include "newgrf_industrytiles.h" #include "station_func.h" @@ -191,6 +192,8 @@ static void _GenerateWorld() } } + InitializeWaterRegions(); + BasePersistentStorageArray::SwitchMode(PSM_LEAVE_GAMELOOP); ResetObjectToPlace(); diff --git a/src/pathfinder/CMakeLists.txt b/src/pathfinder/CMakeLists.txt index 0616371622..adf896895d 100644 --- a/src/pathfinder/CMakeLists.txt +++ b/src/pathfinder/CMakeLists.txt @@ -5,4 +5,6 @@ add_files( follow_track.hpp pathfinder_func.h pathfinder_type.h + water_regions.h + water_regions.cpp ) diff --git a/src/pathfinder/water_regions.cpp b/src/pathfinder/water_regions.cpp new file mode 100644 index 0000000000..d8c4b8f596 --- /dev/null +++ b/src/pathfinder/water_regions.cpp @@ -0,0 +1,379 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + + /** @file water_regions.cpp Handles dividing the water in the map into square regions to assist pathfinding. */ + +#include "stdafx.h" +#include "map_func.h" +#include "water_regions.h" +#include "map_func.h" +#include "tilearea_type.h" +#include "track_func.h" +#include "transport_type.h" +#include "landscape.h" +#include "tunnelbridge_map.h" +#include "follow_track.hpp" +#include "ship.h" + +using TWaterRegionTraversabilityBits = uint16_t; +constexpr TWaterRegionPatchLabel FIRST_REGION_LABEL = 1; +constexpr TWaterRegionPatchLabel INVALID_WATER_REGION_PATCH = 0; + +static_assert(sizeof(TWaterRegionTraversabilityBits) * 8 == WATER_REGION_EDGE_LENGTH); + +static inline TrackBits GetWaterTracks(TileIndex tile) { return TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_WATER, 0)); } +static inline bool IsAqueductTile(TileIndex tile) { return IsBridgeTile(tile) && GetTunnelBridgeTransportType(tile) == TRANSPORT_WATER; } + +static inline int GetWaterRegionX(TileIndex tile) { return TileX(tile) / WATER_REGION_EDGE_LENGTH; } +static inline int GetWaterRegionY(TileIndex tile) { return TileY(tile) / WATER_REGION_EDGE_LENGTH; } + +static inline int GetWaterRegionMapSizeX() { return MapSizeX() / WATER_REGION_EDGE_LENGTH; } +static inline int GetWaterRegionMapSizeY() { return MapSizeY() / WATER_REGION_EDGE_LENGTH; } + +static inline TWaterRegionIndex GetWaterRegionIndex(int region_x, int region_y) { return GetWaterRegionMapSizeX() * region_y + region_x; } +static inline TWaterRegionIndex GetWaterRegionIndex(TileIndex tile) { return GetWaterRegionIndex(GetWaterRegionX(tile), GetWaterRegionY(tile)); } + +/** + * Represents a square section of the map of a fixed size. Within this square individual unconnected patches of water are + * identified using a Connected Component Labeling (CCL) algorithm. Note that all information stored in this class applies + * only to tiles within the square section, there is no knowledge about the rest of the map. This makes it easy to invalidate + * and update a water region if any changes are made to it, such as construction or terraforming. + */ +class WaterRegion +{ +private: + std::array edge_traversability_bits{}; + bool has_cross_region_aqueducts = false; + TWaterRegionPatchLabel number_of_patches = 0; // 0 = no water, 1 = one single patch of water, etc... + const OrthogonalTileArea tile_area; + std::array tile_patch_labels{}; + bool initialized = false; + + /** + * Returns the local index of the tile within the region. The N corner represents 0, + * the x direction is positive in the SW direction, and Y is positive in the SE direction. + * @param tile Tile within the water region. + * @returns The local index. + */ + inline int GetLocalIndex(TileIndex tile) const + { + assert(this->tile_area.Contains(tile)); + return (TileX(tile) - TileX(this->tile_area.tile)) + WATER_REGION_EDGE_LENGTH * (TileY(tile) - TileY(this->tile_area.tile)); + } + +public: + WaterRegion(int region_x, int region_y) + : tile_area(TileXY(region_x * WATER_REGION_EDGE_LENGTH, region_y * WATER_REGION_EDGE_LENGTH), WATER_REGION_EDGE_LENGTH, WATER_REGION_EDGE_LENGTH) + {} + + OrthogonalTileIterator begin() const { return this->tile_area.begin(); } + OrthogonalTileIterator end() const { return this->tile_area.end(); } + + bool IsInitialized() const { return this->initialized; } + + void Invalidate() { this->initialized = false; } + + /** + * Returns a set of bits indicating whether an edge tile on a particular side is traversable or not. These + * values can be used to determine whether a ship can enter/leave the region through a particular edge tile. + * @see GetLocalIndex() for a description of the coordinate system used. + * @param side Which side of the region we want to know the edge traversability of. + * @returns A value holding the edge traversability bits. + */ + TWaterRegionTraversabilityBits GetEdgeTraversabilityBits(DiagDirection side) const { return edge_traversability_bits[side]; } + + /** + * @returns The amount of individual water patches present within the water region. A value of + * 0 means there is no water present in the water region at all. + */ + int NumberOfPatches() const { return this->number_of_patches; } + + /** + * @returns Whether the water region contains aqueducts that cross the region boundaries. + */ + bool HasCrossRegionAqueducts() const { return this->has_cross_region_aqueducts; } + + /** + * Returns the patch label that was assigned to the tile. + * @param tile The tile of which we want to retrieve the label. + * @returns The label assigned to the tile. + */ + TWaterRegionPatchLabel GetLabel(TileIndex tile) const + { + assert(this->tile_area.Contains(tile)); + return this->tile_patch_labels[GetLocalIndex(tile)]; + } + + /** + * Performs the connected component labeling and other data gathering. + * @see WaterRegion + */ + void ForceUpdate() + { + this->has_cross_region_aqueducts = false; + + this->tile_patch_labels.fill(INVALID_WATER_REGION_PATCH); + + for (const TileIndex tile : this->tile_area) { + if (IsAqueductTile(tile)) { + const TileIndex other_aqueduct_end = GetOtherBridgeEnd(tile); + if (!tile_area.Contains(other_aqueduct_end)) { + this->has_cross_region_aqueducts = true; + break; + } + } + } + + TWaterRegionPatchLabel current_label = 1; + TWaterRegionPatchLabel highest_assigned_label = 0; + + /* Perform connected component labeling. This uses a flooding algorithm that expands until no + * additional tiles can be added. Only tiles inside the water region are considered. */ + for (const TileIndex start_tile : tile_area) { + static std::vector tiles_to_check; + tiles_to_check.clear(); + tiles_to_check.push_back(start_tile); + + bool increase_label = false; + while (!tiles_to_check.empty()) { + const TileIndex tile = tiles_to_check.back(); + tiles_to_check.pop_back(); + + const TrackdirBits valid_dirs = TrackBitsToTrackdirBits(GetWaterTracks(tile)); + if (valid_dirs == TRACKDIR_BIT_NONE) continue; + + if (this->tile_patch_labels[GetLocalIndex(tile)] != INVALID_WATER_REGION_PATCH) continue; + + this->tile_patch_labels[GetLocalIndex(tile)] = current_label; + highest_assigned_label = current_label; + increase_label = true; + + for (const Trackdir dir : SetTrackdirBitIterator(valid_dirs)) { + /* By using a TrackFollower we "play by the same rules" as the actual ship pathfinder */ + CFollowTrackWater ft; + if (ft.Follow(tile, dir) && this->tile_area.Contains(ft.m_new_tile)) tiles_to_check.push_back(ft.m_new_tile); + } + } + + if (increase_label) current_label++; + } + + this->number_of_patches = highest_assigned_label; + this->initialized = true; + + /* Calculate the traversability (whether the tile can be entered / exited) for all edges. Note that + * we always follow the same X and Y scanning direction, this is important for comparisons later on! */ + this->edge_traversability_bits.fill(0); + const int top_x = TileX(tile_area.tile); + const int top_y = TileY(tile_area.tile); + for (int i = 0; i < WATER_REGION_EDGE_LENGTH; ++i) { + if (GetWaterTracks(TileXY(top_x + i, top_y)) & TRACK_BIT_3WAY_NW) SetBit(this->edge_traversability_bits[DIAGDIR_NW], i); // NW edge + if (GetWaterTracks(TileXY(top_x + i, top_y + WATER_REGION_EDGE_LENGTH - 1)) & TRACK_BIT_3WAY_SE) SetBit(this->edge_traversability_bits[DIAGDIR_SE], i); // SE edge + if (GetWaterTracks(TileXY(top_x, top_y + i)) & TRACK_BIT_3WAY_NE) SetBit(this->edge_traversability_bits[DIAGDIR_NE], i); // NE edge + if (GetWaterTracks(TileXY(top_x + WATER_REGION_EDGE_LENGTH - 1, top_y + i)) & TRACK_BIT_3WAY_SW) SetBit(this->edge_traversability_bits[DIAGDIR_SW], i); // SW edge + } + } + + /** + * Updates the patch labels and other data, but only if the region is not yet initialized. + */ + inline void UpdateIfNotInitialized() + { + if (!this->initialized) ForceUpdate(); + } +}; + +std::vector _water_regions; + +TileIndex GetTileIndexFromLocalCoordinate(int region_x, int region_y, int local_x, int local_y) +{ + assert(local_x >= 0 && local_y < WATER_REGION_EDGE_LENGTH); + assert(local_y >= 0 && local_y < WATER_REGION_EDGE_LENGTH); + return TileXY(WATER_REGION_EDGE_LENGTH * region_x + local_x, WATER_REGION_EDGE_LENGTH * region_y + local_y); +} + +TileIndex GetEdgeTileCoordinate(int region_x, int region_y, DiagDirection side, int x_or_y) +{ + assert(x_or_y >= 0 && x_or_y < WATER_REGION_EDGE_LENGTH); + switch (side) { + case DIAGDIR_NE: return GetTileIndexFromLocalCoordinate(region_x, region_y, 0, x_or_y); + case DIAGDIR_SW: return GetTileIndexFromLocalCoordinate(region_x, region_y, WATER_REGION_EDGE_LENGTH - 1, x_or_y); + case DIAGDIR_NW: return GetTileIndexFromLocalCoordinate(region_x, region_y, x_or_y, 0); + case DIAGDIR_SE: return GetTileIndexFromLocalCoordinate(region_x, region_y, x_or_y, WATER_REGION_EDGE_LENGTH - 1); + default: NOT_REACHED(); + } +} + +WaterRegion &GetUpdatedWaterRegion(uint16_t region_x, uint16_t region_y) +{ + WaterRegion &result = _water_regions[GetWaterRegionIndex(region_x, region_y)]; + result.UpdateIfNotInitialized(); + return result; +} + +WaterRegion &GetUpdatedWaterRegion(TileIndex tile) +{ + WaterRegion &result = _water_regions[GetWaterRegionIndex(tile)]; + result.UpdateIfNotInitialized(); + return result; +} + +/** + * Returns the index of the water region + * @param water_region The Water region to return the index for + */ +TWaterRegionIndex GetWaterRegionIndex(const WaterRegionDesc &water_region) +{ + return GetWaterRegionIndex(water_region.x, water_region.y); +} + +/** + * Returns the center tile of a particular water region. + * @param water_region The water region to find the center tile for. + * @returns The center tile of the water region. + */ +TileIndex GetWaterRegionCenterTile(const WaterRegionDesc &water_region) +{ + return TileXY(water_region.x * WATER_REGION_EDGE_LENGTH + (WATER_REGION_EDGE_LENGTH / 2), water_region.y * WATER_REGION_EDGE_LENGTH + (WATER_REGION_EDGE_LENGTH / 2)); +} + +/** + * Returns basic water region information for the provided tile. + * @param tile The tile for which the information will be calculated. + */ +WaterRegionDesc GetWaterRegionInfo(TileIndex tile) +{ + return WaterRegionDesc{ GetWaterRegionX(tile), GetWaterRegionY(tile) }; +} + +/** + * Returns basic water region patch information for the provided tile. + * @param tile The tile for which the information will be calculated. + */ +WaterRegionPatchDesc GetWaterRegionPatchInfo(TileIndex tile) +{ + WaterRegion ®ion = GetUpdatedWaterRegion(tile); + return WaterRegionPatchDesc{ GetWaterRegionX(tile), GetWaterRegionY(tile), region.GetLabel(tile)}; +} + +/** + * Marks the water region that tile is part of as invalid. + * @param tile Tile within the water region that we wish to invalidate. + */ +void InvalidateWaterRegion(TileIndex tile) +{ + const int index = GetWaterRegionIndex(tile); + if (index > static_cast(_water_regions.size())) return; + _water_regions[index].Invalidate(); +} + +/** + * Calls the provided callback function for all water region patches + * accessible from one particular side of the starting patch. + * @param water_region_patch Water patch within the water region to start searching from + * @param side Side of the water region to look for neigboring patches of water + * @param callback The function that will be called for each neighbor that is found + */ +static inline void VisitAdjacentWaterRegionPatchNeighbors(const WaterRegionPatchDesc &water_region_patch, DiagDirection side, TVisitWaterRegionPatchCallBack &func) +{ + const WaterRegion ¤t_region = GetUpdatedWaterRegion(water_region_patch.x, water_region_patch.y); + + const TileIndexDiffC offset = TileIndexDiffCByDiagDir(side); + const int nx = water_region_patch.x + offset.x; + const int ny = water_region_patch.y + offset.y; + + if (nx < 0 || ny < 0 || nx >= GetWaterRegionMapSizeX() || ny >= GetWaterRegionMapSizeY()) return; + + const WaterRegion &neighboring_region = GetUpdatedWaterRegion(nx, ny); + const DiagDirection opposite_side = ReverseDiagDir(side); + + /* Indicates via which local x or y coordinates (depends on the "side" parameter) we can cross over into the adjacent region. */ + const TWaterRegionTraversabilityBits traversability_bits = current_region.GetEdgeTraversabilityBits(side) + & neighboring_region.GetEdgeTraversabilityBits(opposite_side); + if (traversability_bits == 0) return; + + if (current_region.NumberOfPatches() == 1 && neighboring_region.NumberOfPatches() == 1) { + func(WaterRegionPatchDesc{ nx, ny, FIRST_REGION_LABEL }); // No further checks needed because we know there is just one patch for both adjacent regions + return; + } + + /* Multiple water patches can be reached from the current patch. Check each edge tile individually. */ + static std::vector unique_labels; // static and vector-instead-of-map for performance reasons + unique_labels.clear(); + for (int x_or_y = 0; x_or_y < WATER_REGION_EDGE_LENGTH; ++x_or_y) { + if (!HasBit(traversability_bits, x_or_y)) continue; + + const TileIndex current_edge_tile = GetEdgeTileCoordinate(water_region_patch.x, water_region_patch.y, side, x_or_y); + const TWaterRegionPatchLabel current_label = current_region.GetLabel(current_edge_tile); + if (current_label != water_region_patch.label) continue; + + const TileIndex neighbor_edge_tile = GetEdgeTileCoordinate(nx, ny, opposite_side, x_or_y); + const TWaterRegionPatchLabel neighbor_label = neighboring_region.GetLabel(neighbor_edge_tile); + if (std::find(unique_labels.begin(), unique_labels.end(), neighbor_label) == unique_labels.end()) unique_labels.push_back(neighbor_label); + } + for (TWaterRegionPatchLabel unique_label : unique_labels) func(WaterRegionPatchDesc{ nx, ny, unique_label }); +} + +/** + * Calls the provided callback function on all accessible water region patches in + * each cardinal direction, plus any others that are reachable via aqueducts. + * @param water_region_patch Water patch within the water region to start searching from + * @param callback The function that will be called for each accessible water patch that is found + */ +void VisitWaterRegionPatchNeighbors(const WaterRegionPatchDesc &water_region_patch, TVisitWaterRegionPatchCallBack &callback) +{ + const WaterRegion ¤t_region = GetUpdatedWaterRegion(water_region_patch.x, water_region_patch.y); + + /* Visit adjacent water region patches in each cardinal direction */ + for (DiagDirection side = DIAGDIR_BEGIN; side < DIAGDIR_END; side++) VisitAdjacentWaterRegionPatchNeighbors(water_region_patch, side, callback); + + /* Visit neigboring water patches accessible via cross-region aqueducts */ + if (current_region.HasCrossRegionAqueducts()) { + for (const TileIndex tile : current_region) { + if (GetWaterRegionPatchInfo(tile) == water_region_patch && IsAqueductTile(tile)) { + const TileIndex other_end_tile = GetOtherBridgeEnd(tile); + if (GetWaterRegionIndex(tile) != GetWaterRegionIndex(other_end_tile)) callback(GetWaterRegionPatchInfo(other_end_tile)); + } + } + } +} + +std::vector GetWaterRegionSaveLoadInfo() +{ + std::vector result; + for (WaterRegion ®ion : _water_regions) result.push_back({ region.IsInitialized() }); + return result; +} + +void LoadWaterRegions(const std::vector &save_load_info) +{ + _water_regions.clear(); + _water_regions.reserve(save_load_info.size()); + TWaterRegionIndex index = 0; + for (const auto &loaded_region_info : save_load_info) { + const int region_x = index % GetWaterRegionMapSizeX(); + const int region_y = index / GetWaterRegionMapSizeX(); + WaterRegion ®ion = _water_regions.emplace_back(region_x, region_y); + if (loaded_region_info.initialized) region.ForceUpdate(); + index++; + } +} + +/** + * Initializes all water regions. All water tiles will be scanned and interconnected water patches within regions will be identified. + */ +void InitializeWaterRegions() +{ + _water_regions.clear(); + _water_regions.reserve(static_cast(GetWaterRegionMapSizeX()) * GetWaterRegionMapSizeY()); + + for (int region_y = 0; region_y < GetWaterRegionMapSizeY(); region_y++) { + for (int region_x = 0; region_x < GetWaterRegionMapSizeX(); region_x++) { + _water_regions.emplace_back(region_x, region_y).ForceUpdate(); + } + } +} diff --git a/src/pathfinder/water_regions.h b/src/pathfinder/water_regions.h new file mode 100644 index 0000000000..801c83b563 --- /dev/null +++ b/src/pathfinder/water_regions.h @@ -0,0 +1,73 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + + /** @file water_regions.h Handles dividing the water in the map into regions to assist pathfinding. */ + +#ifndef WATER_REGIONS_H +#define WATER_REGIONS_H + +#include "tile_type.h" +#include "map_func.h" + +using TWaterRegionPatchLabel = uint8_t; +using TWaterRegionIndex = uint; + +constexpr int WATER_REGION_EDGE_LENGTH = 16; +constexpr int WATER_REGION_NUMBER_OF_TILES = WATER_REGION_EDGE_LENGTH * WATER_REGION_EDGE_LENGTH; + +/** + * Describes a single interconnected patch of water within a particular water region. + */ +struct WaterRegionPatchDesc +{ + int x; ///< The X coordinate of the water region, i.e. X=2 is the 3rd water region along the X-axis + int y; ///< The Y coordinate of the water region, i.e. Y=2 is the 3rd water region along the Y-axis + TWaterRegionPatchLabel label; ///< Unique label identifying the patch within the region + + bool operator==(const WaterRegionPatchDesc &other) const { return x == other.x && y == other.y && label == other.label; } + bool operator!=(const WaterRegionPatchDesc &other) const { return !(*this == other); } +}; + + +/** + * Describes a single square water region. + */ +struct WaterRegionDesc +{ + int x; ///< The X coordinate of the water region, i.e. X=2 is the 3rd water region along the X-axis + int y; ///< The Y coordinate of the water region, i.e. Y=2 is the 3rd water region along the Y-axis + + WaterRegionDesc(const int x, const int y) : x(x), y(y) {} + WaterRegionDesc(const WaterRegionPatchDesc &water_region_patch) : x(water_region_patch.x), y(water_region_patch.y) {} + + bool operator==(const WaterRegionDesc &other) const { return x == other.x && y == other.y; } + bool operator!=(const WaterRegionDesc &other) const { return !(*this == other); } +}; + +TWaterRegionIndex GetWaterRegionIndex(const WaterRegionDesc &water_region); + +TileIndex GetWaterRegionCenterTile(const WaterRegionDesc &water_region); + +WaterRegionDesc GetWaterRegionInfo(TileIndex tile); +WaterRegionPatchDesc GetWaterRegionPatchInfo(TileIndex tile); + +void InvalidateWaterRegion(TileIndex tile); + +using TVisitWaterRegionPatchCallBack = std::function; +void VisitWaterRegionPatchNeighbors(const WaterRegionPatchDesc &water_region_patch, TVisitWaterRegionPatchCallBack &callback); + +void InitializeWaterRegions(); + +struct WaterRegionSaveLoadInfo +{ + bool initialized; +}; + +std::vector GetWaterRegionSaveLoadInfo(); +void LoadWaterRegions(const std::vector &save_load_info); + +#endif /* WATER_REGIONS_H */ diff --git a/src/pathfinder/yapf/CMakeLists.txt b/src/pathfinder/yapf/CMakeLists.txt index 170c1ad61d..6717233352 100644 --- a/src/pathfinder/yapf/CMakeLists.txt +++ b/src/pathfinder/yapf/CMakeLists.txt @@ -16,5 +16,7 @@ add_files( yapf_rail.cpp yapf_road.cpp yapf_ship.cpp + yapf_ship_regions.h + yapf_ship_regions.cpp yapf_type.hpp ) diff --git a/src/pathfinder/yapf/yapf_ship.cpp b/src/pathfinder/yapf/yapf_ship.cpp index cc07cfb974..2f20164f26 100644 --- a/src/pathfinder/yapf/yapf_ship.cpp +++ b/src/pathfinder/yapf/yapf_ship.cpp @@ -14,23 +14,34 @@ #include "yapf.hpp" #include "yapf_node_ship.hpp" +#include "yapf_ship_regions.h" +#include "../water_regions.h" #include "../../safeguards.h" +constexpr int NUMBER_OR_WATER_REGIONS_LOOKAHEAD = 4; +constexpr int MAX_SHIP_PF_NODES = (NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1) * WATER_REGION_NUMBER_OF_TILES * 4; // 4 possible exit dirs per tile. + +constexpr int SHIP_LOST_PATH_LENGTH = 8; // The length of the (aimless) path assigned when a ship is lost. + template class CYapfDestinationTileWaterT { public: - typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class) + typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class). typedef typename Types::TrackFollower TrackFollower; - typedef typename Types::NodeList::Titem Node; ///< this will be our node type - typedef typename Node::Key Key; ///< key to hash tables + typedef typename Types::NodeList::Titem Node; ///< this will be our node type. + typedef typename Node::Key Key; ///< key to hash tables. protected: TileIndex m_destTile; TrackdirBits m_destTrackdirs; StationID m_destStation; + bool m_has_intermediate_dest = false; + TileIndex m_intermediate_dest_tile; + WaterRegionPatchDesc m_intermediate_dest_region_patch; + public: void SetDestination(const Ship *v) { @@ -45,15 +56,22 @@ public: } } + void SetIntermediateDestination(const WaterRegionPatchDesc &water_region_patch) + { + m_has_intermediate_dest = true; + m_intermediate_dest_tile = GetWaterRegionCenterTile(water_region_patch); + m_intermediate_dest_region_patch = water_region_patch; + } + protected: - /** to access inherited path finder */ - inline Tpf &Yapf() + /** To access inherited path finder. */ + inline Tpf& Yapf() { return *static_cast(this); } 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) { return PfDetectDestinationTile(n.m_segment_last_tile, n.m_segment_last_td); @@ -61,21 +79,27 @@ public: inline bool PfDetectDestinationTile(TileIndex tile, Trackdir trackdir) { - if (m_destStation != INVALID_STATION) { - return IsDockingTile(tile) && IsShipDestinationTile(tile, m_destStation); + if (m_has_intermediate_dest) { + /* GetWaterRegionInfo is much faster than GetWaterRegionPatchInfo so we try that first. */ + if (GetWaterRegionInfo(tile) != m_intermediate_dest_region_patch) return false; + return GetWaterRegionPatchInfo(tile) == m_intermediate_dest_region_patch; } + if (m_destStation != INVALID_STATION) return IsDockingTile(tile) && IsShipDestinationTile(tile, m_destStation); + return tile == m_destTile && ((m_destTrackdirs & TrackdirToTrackdirBits(trackdir)) != TRACKDIR_BIT_NONE); } /** * 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::m_estimate + * adds it to the actual cost from origin and stores the sum to the Node::m_estimate. */ inline bool PfCalcEstimate(Node &n) { - static const int dg_dir_to_x_offs[] = {-1, 0, 1, 0}; - static const int dg_dir_to_y_offs[] = {0, 1, 0, -1}; + const TileIndex destination_tile = m_has_intermediate_dest ? m_intermediate_dest_tile : m_destTile; + + 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 (PfDetectDestination(n)) { n.m_estimate = n.m_cost; return true; @@ -85,8 +109,8 @@ public: DiagDirection exitdir = TrackdirToExitdir(n.m_segment_last_td); int x1 = 2 * TileX(tile) + dg_dir_to_x_offs[(int)exitdir]; int y1 = 2 * TileY(tile) + dg_dir_to_y_offs[(int)exitdir]; - int x2 = 2 * TileX(m_destTile); - int y2 = 2 * TileY(m_destTile); + int x2 = 2 * TileX(destination_tile); + int y2 = 2 * TileY(destination_tile); int dx = abs(x1 - x2); int dy = abs(y1 - y2); int dmin = std::min(dx, dy); @@ -98,24 +122,25 @@ public: } }; - /** Node Follower module of YAPF for ships */ template class CYapfFollowShipT { public: - typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class) + typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class). typedef typename Types::TrackFollower TrackFollower; - typedef typename Types::NodeList::Titem Node; ///< this will be our node type - typedef typename Node::Key Key; ///< key to hash tables + typedef typename Types::NodeList::Titem Node; ///< this will be our node type. + typedef typename Node::Key Key; ///< key to hash tables. protected: /** to access inherited path finder */ inline Tpf &Yapf() { - return *static_cast(this); + return *static_cast(this); } + std::vector m_water_region_corridor; + public: /** * Called by YAPF to move from the given node to the next tile. For each @@ -126,23 +151,57 @@ public: { TrackFollower F(Yapf().GetVehicle()); if (F.Follow(old_node.m_key.m_tile, old_node.m_key.m_td)) { - Yapf().AddMultipleNodes(&old_node, F); + if (m_water_region_corridor.empty() + || std::find(m_water_region_corridor.begin(), m_water_region_corridor.end(), + GetWaterRegionInfo(F.m_new_tile)) != m_water_region_corridor.end()) { + Yapf().AddMultipleNodes(&old_node, F); + } } } - /** return debug report character to identify the transportation type */ + /** Restricts the search by creating corridor or water regions through which the ship is allowed to travel. */ + inline void RestrictSearch(const std::vector &path) + { + m_water_region_corridor.clear(); + for (const WaterRegionPatchDesc &path_entry : path) m_water_region_corridor.push_back(path_entry); + } + + /** Return debug report character to identify the transportation type. */ inline char TransportTypeChar() const { return 'w'; } + /** Creates a random path, avoids 90 degree turns. */ + static Trackdir CreateRandomPath(const Ship *v, TileIndex tile, Trackdir dir, ShipPathCache &path_cache, int path_length) + { + for (int i = 0; i < path_length; ++i) { + TrackFollower F(v); + if (F.Follow(tile, dir)) { + tile = F.m_new_tile; + TrackdirBits dirs = F.m_new_td_bits & ~TrackdirCrossesTrackdirs(dir); + const int strip_amount = _random.Next(CountBits(dirs)); + for (int s = 0; s < strip_amount; ++s) RemoveFirstTrackdir(&dirs); + dir = FindFirstTrackdir(dirs); + if (dir == INVALID_TRACKDIR) break; + path_cache.push_back(dir); + } + } + + if (path_cache.empty()) return INVALID_TRACKDIR; + + const Trackdir result = path_cache.front(); + path_cache.pop_front(); + return result; + } + static Trackdir ChooseShipTrack(const Ship *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool &path_found, ShipPathCache &path_cache) { - /* handle special case - when next tile is destination tile */ + /* Handle special case - when next tile is destination tile. */ if (tile == v->dest_tile) { - /* convert tracks to trackdirs */ + /* Convert tracks to trackdirs */ TrackdirBits trackdirs = TrackBitsToTrackdirBits(tracks); - /* limit to trackdirs reachable from enterdir */ + /* Limit to trackdirs reachable from enterdir. */ trackdirs &= DiagdirReachesTrackdirs(enterdir); /* use vehicle's current direction if that's possible, otherwise use first usable one. */ @@ -150,68 +209,91 @@ public: return (HasTrackdir(trackdirs, veh_dir)) ? veh_dir : (Trackdir)FindFirstBit2x64(trackdirs); } - /* move back to the old tile/trackdir (where ship is coming from) */ + /* Move back to the old tile/trackdir (where ship is coming from). */ TileIndex src_tile = TileAddByDiagDir(tile, ReverseDiagDir(enterdir)); Trackdir trackdir = v->GetVehicleTrackdir(); dbg_assert(IsValidTrackdir(trackdir)); - /* convert origin trackdir to TrackdirBits */ + /* Convert origin trackdir to TrackdirBits. */ TrackdirBits trackdirs = TrackdirToTrackdirBits(trackdir); - /* create pathfinder instance */ - Tpf pf; - /* set origin and destination nodes */ - pf.SetOrigin(src_tile, trackdirs); - pf.SetDestination(v); - /* find best path */ - path_found = pf.FindPath(v); - - Trackdir next_trackdir = INVALID_TRACKDIR; // this would mean "path not found" + const std::vector 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, src_tile, trackdir, path_cache, SHIP_LOST_PATH_LENGTH); + } - Node *pNode = pf.GetBestNode(); - if (pNode != nullptr) { - uint steps = 0; - for (Node *n = pNode; n->m_parent != nullptr; n = n->m_parent) steps++; - uint skip = 0; - if (path_found) skip = SHIP_PATH_CACHE_LENGTH / 2; - - /* walk through the path back to the origin */ - Node *pPrevNode = nullptr; - while (pNode->m_parent != nullptr) { - steps--; - /* Skip tiles at end of path near destination. */ - if (skip > 0) skip--; - if (skip == 0 && steps > 0 && steps < SHIP_PATH_CACHE_LENGTH) { - path_cache.push_front(pNode->GetTrackdir()); + /* 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. */ + for (int attempt = 0; attempt < 2; ++attempt) { + Tpf pf(MAX_SHIP_PF_NODES); + + /* Set origin and destination nodes */ + pf.SetOrigin(src_tile, trackdirs); + pf.SetDestination(v); + const bool is_intermediate_destination = static_cast(high_level_path.size()) >= NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1; + if (is_intermediate_destination) pf.SetIntermediateDestination(high_level_path.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); + + /* Find best path. */ + path_found = pf.FindPath(v); + Node *node = pf.GetBestNode(); + if (attempt == 0 && !path_found) continue; // Try again with restricted search area. + if (!path_found || !node) 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. */ + const WaterRegionPatchDesc end_water_patch = GetWaterRegionPatchInfo(node->GetTile()); + const WaterRegionPatchDesc start_water_patch = GetWaterRegionPatchInfo(tile); + while (node->m_parent) { + const WaterRegionPatchDesc node_water_patch = GetWaterRegionPatchInfo(node->GetTile()); + if (node_water_patch == start_water_patch || (!is_intermediate_destination && node_water_patch != end_water_patch)) { + path_cache.push_front(node->GetTrackdir()); } - pPrevNode = pNode; - pNode = pNode->m_parent; + node = node->m_parent; } - /* return trackdir from the best next node (direct child of origin) */ - Node &best_next_node = *pPrevNode; - assert(best_next_node.GetTile() == tile); - next_trackdir = best_next_node.GetTrackdir(); - /* remove last element for the special case when tile == dest_tile */ - if (path_found && !path_cache.empty()) path_cache.pop_back(); + assert(!path_cache.empty()); + + /* Take out the last trackdir as the result. */ + const Trackdir result = path_cache.front(); + path_cache.pop_front(); + + /* Clear path cache when in final water region patch. This is to allow ships to spread over different docking tiles dynamically. */ + if (start_water_patch == end_water_patch) path_cache.clear(); + + return result; } - return next_trackdir; + + return INVALID_TRACKDIR; } /** * Check whether a ship should reverse to reach its destination. * Called when leaving depot. - * @param v Ship - * @param tile Current position - * @param td1 Forward direction - * @param td2 Reverse direction - * @param trackdir [out] the best of all possible reversed trackdirs - * @return true if the reverse direction is better + * @param v Ship. + * @param tile Current position. + * @param td1 Forward direction. + * @param td2 Reverse direction. + * @param trackdir [out] the best of all possible reversed trackdirs. + * @return true if the reverse direction is better. */ static bool CheckShipReverse(const Ship *v, TileIndex tile, Trackdir td1, Trackdir td2, Trackdir *trackdir) { - /* create pathfinder instance */ - Tpf pf; - /* set origin and destination nodes */ + const std::vector high_level_path = YapfShipFindWaterRegionPath(v, tile, NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1); + if (high_level_path.empty()) { + if (trackdir) *trackdir = INVALID_TRACKDIR; + return false; + } + + /* Create pathfinder instance. */ + Tpf pf(MAX_SHIP_PF_NODES); + /* Set origin and destination nodes. */ if (trackdir == nullptr) { pf.SetOrigin(tile, TrackdirToTrackdirBits(td1) | TrackdirToTrackdirBits(td2)); } else { @@ -220,14 +302,16 @@ public: pf.SetOrigin(tile, rtds); } pf.SetDestination(v); - /* find best path */ + if (high_level_path.size() > 1) pf.SetIntermediateDestination(high_level_path.back()); + pf.RestrictSearch(high_level_path); + + /* Find best path. */ if (!pf.FindPath(v)) return false; Node *pNode = pf.GetBestNode(); if (pNode == nullptr) return false; - /* path was found - * walk through the path back to the origin */ + /* Path was found, walk through the path back to the origin. */ while (pNode->m_parent != nullptr) { pNode = pNode->m_parent; } @@ -242,21 +326,20 @@ public: } }; -/** Cost Provider module of YAPF for ships */ +/** Cost Provider module of YAPF for ships. */ template class CYapfCostShipT { public: - typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class) + typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class). typedef typename Types::TrackFollower TrackFollower; - typedef typename Types::NodeList::Titem Node; ///< this will be our node type - typedef typename Node::Key Key; ///< key to hash tables + typedef typename Types::NodeList::Titem Node; ///< this will be our node type. + typedef typename Node::Key Key; ///< key to hash tables. -protected: /** to access inherited path finder */ Tpf &Yapf() { - return *static_cast(this); + return *static_cast(this); } public: @@ -266,10 +349,10 @@ public: dbg_assert(IsValidTrackdir(td2)); if (HasTrackdir(TrackdirCrossesTrackdirs(td1), td2)) { - /* 90-deg curve penalty */ + /* 90-deg curve penalty. */ return Yapf().PfGetSettings().ship_curve90_penalty; } else if (td2 != NextTrackdir(td1)) { - /* 45-deg curve penalty */ + /* 45-deg curve penalty. */ return Yapf().PfGetSettings().ship_curve45_penalty; } return 0; @@ -277,7 +360,7 @@ public: static Vehicle *CountShipProc(Vehicle *v, void *data) { - uint *count = (uint *)data; + uint *count = (uint*)data; /* Ignore other vehicles (aircraft) and ships inside depot. */ if ((v->vehstatus & VS_HIDDEN) == 0) (*count)++; @@ -286,18 +369,18 @@ public: /** * Called by YAPF to calculate the cost from the origin to the given node. - * Calculates only the cost of given node, adds it to the parent node cost - * and stores the result into Node::m_cost member + * Calculates only the cost of given node, adds it to the parent node cost + * and stores the result into Node::m_cost member. */ inline bool PfCalcCost(Node &n, const TrackFollower *tf) { - /* base tile cost depending on distance */ + /* Base tile cost depending on distance. */ int c = IsDiagonalTrackdir(n.GetTrackdir()) ? YAPF_TILE_LENGTH : YAPF_TILE_CORNER_LENGTH; - /* additional penalty for curves */ + /* Additional penalty for curves. */ c += CurveCost(n.m_parent->GetTrackdir(), n.GetTrackdir()); if (IsDockingTile(n.GetTile())) { - /* Check docking tile for occupancy */ + /* Check docking tile for occupancy. */ uint count = 0; HasVehicleOnPos(n.GetTile(), VEH_SHIP, &count, &CountShipProc); c += count * 3 * YAPF_TILE_LENGTH; @@ -311,7 +394,7 @@ public: byte 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->m_tiles_skipped) * speed_frac / (256 - speed_frac); - /* apply it */ + /* Apply it. */ n.m_cost = n.m_parent->m_cost + c; return true; } @@ -319,48 +402,35 @@ public: /** * Config struct of YAPF for ships. - * Defines all 6 base YAPF modules as classes providing services for CYapfBaseT. + * Defines all 6 base YAPF modules as classes providing services for CYapfBaseT. */ template struct CYapfShip_TypesT { - /** Types - shortcut for this struct type */ - typedef CYapfShip_TypesT Types; - - /** Tpf - pathfinder type */ - typedef Tpf_ Tpf; - /** track follower helper class */ - typedef Ttrack_follower TrackFollower; - /** node list type */ - typedef Tnode_list NodeList; - typedef Ship VehicleType; - /** pathfinder components (modules) */ - typedef CYapfBaseT PfBase; // base pathfinder class - typedef CYapfFollowShipT PfFollow; // node follower - typedef CYapfOriginTileT PfOrigin; // origin provider - typedef CYapfDestinationTileWaterT PfDestination; // destination/distance provider - typedef CYapfSegmentCostCacheNoneT PfCache; // segment cost cache provider - typedef CYapfCostShipT PfCost; // cost provider + typedef CYapfShip_TypesT Types; ///< Shortcut for this struct type. + typedef Tpf_ Tpf; ///< Pathfinder type. + typedef Ttrack_follower TrackFollower; ///< Track follower helper class. + typedef Tnode_list NodeList; + typedef Ship VehicleType; + + /** Pathfinder components (modules). */ + typedef CYapfBaseT PfBase; ///< Base pathfinder class. + typedef CYapfFollowShipT PfFollow; ///< Node follower. + typedef CYapfOriginTileT PfOrigin; ///< Origin provider. + typedef CYapfDestinationTileWaterT PfDestination; ///< Destination/distance provider. + typedef CYapfSegmentCostCacheNoneT PfCache; ///< Segment cost cache provider. + typedef CYapfCostShipT PfCost; ///< Cost provider. }; -/* YAPF type 1 - uses TileIndex/Trackdir as Node key */ -struct CYapfShip1 : CYapfT > {}; -/* YAPF type 2 - uses TileIndex/DiagDirection as Node key */ -struct CYapfShip2 : CYapfT > {}; +struct CYapfShip : CYapfT > +{ + explicit CYapfShip(int max_nodes) { m_max_search_nodes = max_nodes; } +}; -/** Ship controller helper - path finder invoker */ +/** Ship controller helper - path finder invoker. */ Track YapfShipChooseTrack(const Ship *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool &path_found, ShipPathCache &path_cache) { - /* default is YAPF type 2 */ - typedef Trackdir (*PfnChooseShipTrack)(const Ship*, TileIndex, DiagDirection, TrackBits, bool &path_found, ShipPathCache &path_cache); - PfnChooseShipTrack pfnChooseShipTrack = CYapfShip2::ChooseShipTrack; // default: ExitDir - - /* check if non-default YAPF type needed */ - if (_settings_game.pf.yapf.disable_node_optimization) { - pfnChooseShipTrack = &CYapfShip1::ChooseShipTrack; // Trackdir - } - - Trackdir td_ret = pfnChooseShipTrack(v, tile, enterdir, tracks, path_found, path_cache); + Trackdir td_ret = CYapfShip::ChooseShipTrack(v, tile, enterdir, tracks, path_found, path_cache); return (td_ret != INVALID_TRACKDIR) ? TrackdirToTrack(td_ret) : INVALID_TRACK; } @@ -369,16 +439,5 @@ bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir) Trackdir td = v->GetVehicleTrackdir(); Trackdir td_rev = ReverseTrackdir(td); TileIndex tile = v->tile; - - typedef bool (*PfnCheckReverseShip)(const Ship*, TileIndex, Trackdir, Trackdir, Trackdir*); - PfnCheckReverseShip pfnCheckReverseShip = CYapfShip2::CheckShipReverse; // default: ExitDir - - /* check if non-default YAPF type needed */ - if (_settings_game.pf.yapf.disable_node_optimization) { - pfnCheckReverseShip = &CYapfShip1::CheckShipReverse; // Trackdir - } - - bool reverse = pfnCheckReverseShip(v, tile, td, td_rev, trackdir); - - return reverse; + return CYapfShip::CheckShipReverse(v, tile, td, td_rev, trackdir); } diff --git a/src/pathfinder/yapf/yapf_ship_regions.cpp b/src/pathfinder/yapf/yapf_ship_regions.cpp new file mode 100644 index 0000000000..7e7b662d65 --- /dev/null +++ b/src/pathfinder/yapf/yapf_ship_regions.cpp @@ -0,0 +1,314 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + + /** @file yapf_ship_regions.cpp Implementation of YAPF for water regions, which are used for finding intermediate ship destinations. */ + +#include "../../stdafx.h" +#include "../../ship.h" + +#include "yapf.hpp" +#include "yapf_ship_regions.h" +#include "../water_regions.h" + +#include "../../safeguards.h" + +constexpr int DIRECT_NEIGHBOR_COST = 100; +constexpr int NODES_PER_REGION = 4; +constexpr int MAX_NUMBER_OF_NODES = 65536; + +/** Yapf Node Key that represents a single patch of interconnected water within a water region. */ +struct CYapfRegionPatchNodeKey { + WaterRegionPatchDesc m_water_region_patch; + + static_assert(sizeof(TWaterRegionPatchLabel) == sizeof(byte)); // Important for the hash calculation. + + inline void Set(const WaterRegionPatchDesc &water_region_patch) + { + m_water_region_patch = water_region_patch; + } + + inline int CalcHash() const { return m_water_region_patch.label | GetWaterRegionIndex(m_water_region_patch) << 8; } + inline bool operator==(const CYapfRegionPatchNodeKey &other) const { return CalcHash() == other.CalcHash(); } +}; + +inline uint ManhattanDistance(const CYapfRegionPatchNodeKey &a, const CYapfRegionPatchNodeKey &b) +{ + return (std::abs(a.m_water_region_patch.x - b.m_water_region_patch.x) + std::abs(a.m_water_region_patch.y - b.m_water_region_patch.y)) * DIRECT_NEIGHBOR_COST; +} + +/** Yapf Node for water regions. */ +template +struct CYapfRegionNodeT { + typedef Tkey_ Key; + typedef CYapfRegionNodeT Node; + + Tkey_ m_key; + Node *m_hash_next; + Node *m_parent; + int m_cost; + int m_estimate; + + inline void Set(Node *parent, const WaterRegionPatchDesc &water_region_patch) + { + m_key.Set(water_region_patch); + m_hash_next = nullptr; + m_parent = parent; + m_cost = 0; + m_estimate = 0; + } + + inline void Set(Node *parent, const Key &key) + { + Set(parent, key.m_water_region_patch); + } + + DiagDirection GetDiagDirFromParent() const + { + if (!m_parent) return INVALID_DIAGDIR; + const int dx = m_key.m_water_region_patch.x - m_parent->m_key.m_water_region_patch.x; + const int dy = m_key.m_water_region_patch.y - m_parent->m_key.m_water_region_patch.y; + if (dx > 0 && dy == 0) return DIAGDIR_SW; + if (dx < 0 && dy == 0) return DIAGDIR_NE; + if (dx == 0 && dy > 0) return DIAGDIR_SE; + if (dx == 0 && dy < 0) return DIAGDIR_NW; + return INVALID_DIAGDIR; + } + + inline Node *GetHashNext() { return m_hash_next; } + inline void SetHashNext(Node *pNext) { m_hash_next = pNext; } + inline const Tkey_ &GetKey() const { return m_key; } + inline int GetCost() { return m_cost; } + inline int GetCostEstimate() { return m_estimate; } + inline bool operator<(const Node &other) const { return m_estimate < other.m_estimate; } +}; + +/** YAPF origin for water regions. */ +template +class CYapfOriginRegionT +{ +public: + typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class). + typedef typename Types::NodeList::Titem Node; ///< This will be our node type. + typedef typename Node::Key Key; ///< Key to hash tables. + +protected: + inline Tpf &Yapf() { return *static_cast(this); } + +private: + std::vector m_origin_keys; + +public: + void AddOrigin(const WaterRegionPatchDesc &water_region_patch) + { + if (!HasOrigin(water_region_patch)) m_origin_keys.push_back(CYapfRegionPatchNodeKey{ water_region_patch }); + } + + bool HasOrigin(const WaterRegionPatchDesc &water_region_patch) + { + return std::find(m_origin_keys.begin(), m_origin_keys.end(), CYapfRegionPatchNodeKey{ water_region_patch }) != m_origin_keys.end(); + } + + void PfSetStartupNodes() + { + for (const CYapfRegionPatchNodeKey &origin_key : m_origin_keys) { + Node &node = Yapf().CreateNewNode(); + node.Set(nullptr, origin_key); + Yapf().AddStartupNode(node); + } + } +}; + +/** YAPF destination provider for water regions. */ +template +class CYapfDestinationRegionT +{ +public: + typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class). + typedef typename Types::NodeList::Titem Node; ///< This will be our node type. + typedef typename Node::Key Key; ///< Key to hash tables. + +protected: + Key m_dest; + +public: + void SetDestination(const WaterRegionPatchDesc &water_region_patch) + { + m_dest.Set(water_region_patch); + } + +protected: + Tpf &Yapf() { return *static_cast(this); } + +public: + inline bool PfDetectDestination(Node &n) const + { + return n.m_key == m_dest; + } + + inline bool PfCalcEstimate(Node &n) + { + if (PfDetectDestination(n)) { + n.m_estimate = n.m_cost; + return true; + } + + n.m_estimate = n.m_cost + ManhattanDistance(n.m_key, m_dest); + + return true; + } +}; + +/** YAPF node following for water region pathfinding. */ +template +class CYapfFollowRegionT +{ +public: + typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class). + typedef typename Types::TrackFollower TrackFollower; + typedef typename Types::NodeList::Titem Node; ///< This will be our node type. + typedef typename Node::Key Key; ///< Key to hash tables. + +protected: + inline Tpf &Yapf() { return *static_cast(this); } + +public: + inline void PfFollowNode(Node &old_node) + { + TVisitWaterRegionPatchCallBack visitFunc = [&](const WaterRegionPatchDesc &water_region_patch) + { + Node &node = Yapf().CreateNewNode(); + node.Set(&old_node, water_region_patch); + Yapf().AddNewNode(node, TrackFollower{}); + }; + VisitWaterRegionPatchNeighbors(old_node.m_key.m_water_region_patch, visitFunc); + } + + inline char TransportTypeChar() const { return '^'; } + + static std::vector FindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length) + { + const WaterRegionPatchDesc start_water_region_patch = GetWaterRegionPatchInfo(start_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(MapSize() * NODES_PER_REGION) / WATER_REGION_NUMBER_OF_TILES, MAX_NUMBER_OF_NODES)); + pf.SetDestination(start_water_region_patch); + + if (v->current_order.IsType(OT_GOTO_STATION)) { + DestinationID station_id = v->current_order.GetDestination(); + const BaseStation *station = BaseStation::Get(station_id); + TileArea tile_area; + station->GetTileArea(&tile_area, STATION_DOCK); + for (const auto &tile : tile_area) { + if (IsDockingTile(tile) && IsShipDestinationTile(tile, station_id)) { + pf.AddOrigin(GetWaterRegionPatchInfo(tile)); + } + } + } else { + TileIndex tile = v->dest_tile; + pf.AddOrigin(GetWaterRegionPatchInfo(tile)); + } + + /* If origin and destination are the same we simply return that water patch. */ + std::vector path = { start_water_region_patch }; + path.reserve(max_returned_path_length); + if (pf.HasOrigin(start_water_region_patch)) return path; + + /* Find best path. */ + if (!pf.FindPath(v)) return {}; // Path not found. + + Node *node = pf.GetBestNode(); + for (int i = 0; i < max_returned_path_length - 1; ++i) { + if (node != nullptr) { + node = node->m_parent; + if (node != nullptr) path.push_back(node->m_key.m_water_region_patch); + } + } + + assert(!path.empty()); + return path; + } +}; + +/** Cost Provider of YAPF for water regions. */ +template +class CYapfCostRegionT +{ +public: + typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class). + typedef typename Types::TrackFollower TrackFollower; + typedef typename Types::NodeList::Titem Node; ///< This will be our node type. + typedef typename Node::Key Key; ///< Key to hash tables. + +protected: + /** To access inherited path finder. */ + Tpf &Yapf() { return *static_cast(this); } + +public: + /** + * Called by YAPF to calculate the cost from the origin to the given node. + * Calculates only the cost of given node, adds it to the parent node cost + * and stores the result into Node::m_cost member. + */ + inline bool PfCalcCost(Node &n, const TrackFollower *) + { + n.m_cost = n.m_parent->m_cost + ManhattanDistance(n.m_key, n.m_parent->m_key); + + /* Incentivise zigzagging by adding a slight penalty when the search continues in the same direction. */ + Node *grandparent = n.m_parent->m_parent; + if (grandparent != nullptr) { + const DiagDirDiff dir_diff = DiagDirDifference(n.m_parent->GetDiagDirFromParent(), n.GetDiagDirFromParent()); + if (dir_diff != DIAGDIRDIFF_90LEFT && dir_diff != DIAGDIRDIFF_90RIGHT) n.m_cost += 1; + } + + return true; + } +}; + +/* We don't need a follower but YAPF requires one. */ +struct DummyFollower : public CFollowTrackWater {}; + +/** + * Config struct of YAPF for route planning. + * Defines all 6 base YAPF modules as classes providing services for CYapfBaseT. + */ +template +struct CYapfRegion_TypesT +{ + typedef CYapfRegion_TypesT Types; ///< Shortcut for this struct type. + typedef Tpf_ Tpf; ///< Pathfinder type. + typedef DummyFollower TrackFollower; ///< Track follower helper class + typedef Tnode_list NodeList; + typedef Ship VehicleType; + + /** Pathfinder components (modules). */ + typedef CYapfBaseT PfBase; ///< Base pathfinder class. + typedef CYapfFollowRegionT PfFollow; ///< Node follower. + typedef CYapfOriginRegionT PfOrigin; ///< Origin provider. + typedef CYapfDestinationRegionT PfDestination; ///< Destination/distance provider. + typedef CYapfSegmentCostCacheNoneT PfCache; ///< Segment cost cache provider. + typedef CYapfCostRegionT PfCost; ///< Cost provider. +}; + +typedef CNodeList_HashTableT, 12, 12> CRegionNodeListWater; + +struct CYapfRegionWater : CYapfT> +{ + explicit CYapfRegionWater(int max_nodes) { m_max_search_nodes = max_nodes; } +}; + +/** + * Finds a path at the water region level. Note that the starting region is always included if the path was found. + * @param v The ship to find a path for. + * @param start_tile The tile to start searching from. + * @param max_returned_path_length The maximum length of the path that will be returned. + * @returns A path of water region patches, or an empty vector if no path was found. + */ +std::vector YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length) +{ + return CYapfRegionWater::FindWaterRegionPath(v, start_tile, max_returned_path_length); +} diff --git a/src/pathfinder/yapf/yapf_ship_regions.h b/src/pathfinder/yapf/yapf_ship_regions.h new file mode 100644 index 0000000000..8b75773cc8 --- /dev/null +++ b/src/pathfinder/yapf/yapf_ship_regions.h @@ -0,0 +1,21 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + + /** @file yapf_ship_regions.h Implementation of YAPF for water regions, which are used for finding intermediate ship destinations. */ + +#ifndef YAPF_SHIP_REGIONS_H +#define YAPF_SHIP_REGIONS_H + +#include "../../stdafx.h" +#include "../../tile_type.h" +#include "../water_regions.h" + +struct Ship; + +std::vector YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length); + +#endif /* YAPF_SHIP_REGIONS_H */ diff --git a/src/saveload/CMakeLists.txt b/src/saveload/CMakeLists.txt index 3961647f9a..98a1a4e6b5 100644 --- a/src/saveload/CMakeLists.txt +++ b/src/saveload/CMakeLists.txt @@ -37,4 +37,5 @@ add_files( subsidy_sl.cpp town_sl.cpp vehicle_sl.cpp + water_regions_sl.cpp ) diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 818ddbd835..2f34a7fd6f 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -74,6 +74,7 @@ #include "../newgrf_industrytiles.h" #include "../timer/timer.h" #include "../timer/timer_game_tick.h" +#include "../pathfinder/water_regions.h" #include "../sl/saveload_internal.h" @@ -4398,6 +4399,8 @@ bool AfterLoadGame() c->settings = _settings_client.company; } + if (IsSavegameVersionBefore(SLV_WATER_REGIONS) && SlXvIsFeatureMissing(XSLFI_WATER_REGIONS)) InitializeWaterRegions(); + return true; } diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index 9b88cace34..e41cff950f 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -115,6 +115,7 @@ static const std::vector &ChunkHandlers() extern const ChunkHandlerTable _airport_chunk_handlers; extern const ChunkHandlerTable _object_chunk_handlers; extern const ChunkHandlerTable _persistent_storage_chunk_handlers; + extern const ChunkHandlerTable _water_region_chunk_handlers; /** List of all chunks in a savegame. */ static const ChunkHandlerTable _chunk_handler_tables[] = { @@ -152,6 +153,7 @@ static const std::vector &ChunkHandlers() _airport_chunk_handlers, _object_chunk_handlers, _persistent_storage_chunk_handlers, + _water_region_chunk_handlers, }; static std::vector _chunk_handlers; diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp index 1315323203..c9befd80ed 100644 --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -309,13 +309,9 @@ public: if (v->type != VEH_SHIP) return; SlObject(v, this->GetLoadDescription()); - if (!_path_td.empty() && _path_td.size() <= SHIP_PATH_CACHE_LENGTH) { + if (!_path_td.empty()) { Ship *s = Ship::From(v); - s->cached_path.reset(new ShipPathCache()); - s->cached_path->count = (uint8_t)_path_td.size(); - for (size_t i = 0; i < _path_td.size(); i++) { - s->cached_path->td[i] = _path_td[i]; - } + s->cached_path.insert(s->cached_path.end(), _path_td.begin(), _path_td.end()); } _path_td.clear(); } diff --git a/src/saveload/water_regions_sl.cpp b/src/saveload/water_regions_sl.cpp new file mode 100644 index 0000000000..e13c2b017a --- /dev/null +++ b/src/saveload/water_regions_sl.cpp @@ -0,0 +1,58 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file water_regions_sl.cpp Handles saving and loading of water region data */ + +#include "../stdafx.h" + +#include "saveload.h" +#include "../pathfinder/water_regions.h" + +#include "../safeguards.h" + +namespace upstream_sl { + +static const SaveLoad _water_region_desc[] = { + SLE_VAR(WaterRegionSaveLoadInfo, initialized, SLE_BOOL), +}; + +struct WRGNChunkHandler : ChunkHandler { + WRGNChunkHandler() : ChunkHandler('WRGN', CH_TABLE) {} + + void Save() const override + { + SlTableHeader(_water_region_desc); + + int index = 0; + for (WaterRegionSaveLoadInfo ®ion : GetWaterRegionSaveLoadInfo()) { + SlSetArrayIndex(index++); + SlObject(®ion, _water_region_desc); + } + } + + void Load() const override + { + const std::vector slt = SlTableHeader(_water_region_desc); + + int index; + + std::vector loaded_info; + while ((index = SlIterateArray()) != -1) { + WaterRegionSaveLoadInfo region_info; + SlObject(®ion_info, slt); + loaded_info.push_back(std::move(region_info)); + } + + LoadWaterRegions(loaded_info); + } +}; + +static const WRGNChunkHandler WRGN; +static const ChunkHandlerRef water_region_chunk_handlers[] = { WRGN }; +extern const ChunkHandlerTable _water_region_chunk_handlers(water_region_chunk_handlers); + +} diff --git a/src/script/api/ai_changelog.hpp b/src/script/api/ai_changelog.hpp index 9695ab0fd2..0a35d43ed4 100644 --- a/src/script/api/ai_changelog.hpp +++ b/src/script/api/ai_changelog.hpp @@ -25,6 +25,11 @@ * \li AIError::ERR_PRECONDITION_TOO_MANY_PARAMETERS, that error is never returned anymore. * * Other changes: + * \li AIGroupList accepts an optional filter function + * \li AIIndustryList accepts an optional filter function + * \li AISignList accepts an optional filter function + * \li AISubsidyList accepts an optional filter function + * \li AITownList accepts an optional filter function * \li AIVehicleList accepts an optional filter function * * \b 13.0 diff --git a/src/script/api/game_changelog.hpp b/src/script/api/game_changelog.hpp index 07b172440d..0dfb08c2e5 100644 --- a/src/script/api/game_changelog.hpp +++ b/src/script/api/game_changelog.hpp @@ -85,6 +85,11 @@ * \li GSError::ERR_PRECONDITION_TOO_MANY_PARAMETERS, that error is never returned anymore. * * Other changes: + * \li GSGroupList accepts an optional filter function + * \li GSIndustryList accepts an optional filter function + * \li GSSignList accepts an optional filter function + * \li GSSubsidyList accepts an optional filter function + * \li GSTownList accepts an optional filter function * \li GSVehicleList accepts an optional filter function * * \b 13.0 diff --git a/src/script/api/script_grouplist.cpp b/src/script/api/script_grouplist.cpp index 7fa93e8099..363b4d9f53 100644 --- a/src/script/api/script_grouplist.cpp +++ b/src/script/api/script_grouplist.cpp @@ -14,11 +14,11 @@ #include "../../safeguards.h" -ScriptGroupList::ScriptGroupList() +ScriptGroupList::ScriptGroupList(HSQUIRRELVM vm) { EnforceCompanyModeValid_Void(); CompanyID owner = ScriptObject::GetCompany(); - for (const Group *g : Group::Iterate()) { - if (g->owner == owner) this->AddItem(g->index); - } + ScriptList::FillList(vm, this, + [owner](const Group *g) { return g->owner == owner; } + ); } diff --git a/src/script/api/script_grouplist.hpp b/src/script/api/script_grouplist.hpp index 49e8028dcb..d85303b854 100644 --- a/src/script/api/script_grouplist.hpp +++ b/src/script/api/script_grouplist.hpp @@ -20,10 +20,35 @@ */ class ScriptGroupList : public ScriptList { public: +#ifdef DOXYGEN_API /** * @game @pre ScriptCompanyMode::IsValid(). */ ScriptGroupList(); + + /** + * Apply a filter when building the list. + * @param filter_function The function which will be doing the filtering. + * @param params The params to give to the filters (minus the first param, + * which is always the index-value). + * @game @pre ScriptCompanyMode::IsValid(). + * @note You can write your own filters and use them. Just remember that + * the first parameter should be the index-value, and it should return + * a bool. + * @note Example: + * function IsType(group_id, type) + * { + * return ScriptGroup.GetVehicleType(group_id) == type; + * } + * ScriptGroupList(IsType, ScriptVehicle.VT_ROAD); + */ + ScriptGroupList(void *filter_function, int params, ...); +#else + /** + * The constructor wrapper from Squirrel. + */ + ScriptGroupList(HSQUIRRELVM vm); +#endif /* DOXYGEN_API */ }; #endif /* SCRIPT_GROUPLIST_HPP */ diff --git a/src/script/api/script_industrylist.cpp b/src/script/api/script_industrylist.cpp index 0cc1cde454..25faec31ee 100644 --- a/src/script/api/script_industrylist.cpp +++ b/src/script/api/script_industrylist.cpp @@ -13,23 +13,21 @@ #include "../../safeguards.h" -ScriptIndustryList::ScriptIndustryList() +ScriptIndustryList::ScriptIndustryList(HSQUIRRELVM vm) { - for (const Industry *i : Industry::Iterate()) { - this->AddItem(i->index); - } + ScriptList::FillList(vm, this); } ScriptIndustryList_CargoAccepting::ScriptIndustryList_CargoAccepting(CargoID cargo_id) { - for (const Industry *i : Industry::Iterate()) { - if (i->IsCargoAccepted(cargo_id)) this->AddItem(i->index); - } + ScriptList::FillList(this, + [cargo_id](const Industry *i) { return i->IsCargoAccepted(cargo_id); } + ); } ScriptIndustryList_CargoProducing::ScriptIndustryList_CargoProducing(CargoID cargo_id) { - for (const Industry *i : Industry::Iterate()) { - if (i->IsCargoProduced(cargo_id)) this->AddItem(i->index); - } + ScriptList::FillList(this, + [cargo_id](const Industry *i) { return i->IsCargoProduced(cargo_id); } + ); } diff --git a/src/script/api/script_industrylist.hpp b/src/script/api/script_industrylist.hpp index ff7210d682..2139b2258e 100644 --- a/src/script/api/script_industrylist.hpp +++ b/src/script/api/script_industrylist.hpp @@ -19,7 +19,32 @@ */ class ScriptIndustryList : public ScriptList { public: +#ifdef DOXYGEN_API ScriptIndustryList(); + + /** + * Apply a filter when building the list. + * @param filter_function The function which will be doing the filtering. + * @param params The params to give to the filters (minus the first param, + * which is always the index-value). + * @note You can write your own filters and use them. Just remember that + * the first parameter should be the index-value, and it should return + * a bool. + * @note Example: + * ScriptIndustryList(ScriptIndustry.HasDock); + * function IsType(industry_id, type) + * { + * return ScriptIndustry.GetIndustryType(industry_id) == type; + * } + * ScriptIndustryList(IsType, 0); + */ + ScriptIndustryList(void *filter_function, int params, ...); +#else + /** + * The constructor wrapper from Squirrel. + */ + ScriptIndustryList(HSQUIRRELVM vm); +#endif /* DOXYGEN_API */ }; /** diff --git a/src/script/api/script_list.cpp b/src/script/api/script_list.cpp index 5d02b5d8dc..07e73b7d78 100644 --- a/src/script/api/script_list.cpp +++ b/src/script/api/script_list.cpp @@ -11,9 +11,7 @@ #include "script_list.hpp" #include "script_controller.hpp" #include "../../debug.h" -#include "../../core/backup_type.hpp" #include "../../script/squirrel.hpp" -#include <../squirrel/sqvm.h> #include "../../safeguards.h" @@ -1029,12 +1027,7 @@ SQInteger ScriptList::Valuate(HSQUIRRELVM vm) ScriptObject::SetAllowDoCommand(false); /* Limit the total number of ops that can be consumed by a valuate operation */ - SQInteger new_ops_error_threshold = vm->_ops_till_suspend_error_threshold; - if (vm->_ops_till_suspend_error_threshold == INT64_MIN) { - new_ops_error_threshold = vm->_ops_till_suspend - MAX_VALUATE_OPS; - vm->_ops_till_suspend_error_label = "valuator function"; - } - AutoRestoreBackup ops_error_threshold_backup(vm->_ops_till_suspend_error_threshold, new_ops_error_threshold); + SQOpsLimiter limiter(vm, MAX_VALUATE_OPS, "valuator function"); /* Push the function to call */ sq_push(vm, 2); diff --git a/src/script/api/script_list.hpp b/src/script/api/script_list.hpp index b524e8fa66..cb6d3b36ab 100644 --- a/src/script/api/script_list.hpp +++ b/src/script/api/script_list.hpp @@ -58,6 +58,107 @@ private: ScriptListMap::iterator RemoveIter(ScriptListMap::iterator item_iter); ScriptListValueSet::iterator RemoveValueIter(ScriptListValueSet::iterator value_iter); +protected: + template + static void FillList(ScriptList *list, ItemValid item_valid, ItemFilter item_filter) + { + for (const T *item : T::Iterate()) { + if (!item_valid(item)) continue; + if (!item_filter(item)) continue; + list->AddItem(item->index); + } + } + + template + static void FillList(ScriptList *list, ItemValid item_valid) + { + ScriptList::FillList(list, item_valid, [](const T *) { return true; }); + } + + template + static void FillList(ScriptList *list) + { + ScriptList::FillList(list, [](const T *) { return true; }); + } + + template + static void FillList(HSQUIRRELVM vm, ScriptList *list, ItemValid item_valid) + { + int nparam = sq_gettop(vm) - 1; + if (nparam >= 1) { + /* Make sure the filter function is really a function, and not any + * other type. It's parameter 2 for us, but for the user it's the + * first parameter they give. */ + SQObjectType valuator_type = sq_gettype(vm, 2); + if (valuator_type != OT_CLOSURE && valuator_type != OT_NATIVECLOSURE) { + throw sq_throwerror(vm, "parameter 1 has an invalid type (expected function)"); + } + + /* Push the function to call */ + sq_push(vm, 2); + } + + /* Don't allow docommand from a Valuator, as we can't resume in + * mid C++-code. */ + bool backup_allow = ScriptObject::GetAllowDoCommand(); + ScriptObject::SetAllowDoCommand(false); + + + if (nparam < 1) { + ScriptList::FillList(list, item_valid); + } else { + /* Limit the total number of ops that can be consumed by a filter operation, if a filter function is present */ + SQOpsLimiter limiter(vm, MAX_VALUATE_OPS, "list filter function"); + + ScriptList::FillList(list, item_valid, + [vm, nparam, backup_allow](const T *item) { + /* Push the root table as instance object, this is what squirrel does for meta-functions. */ + sq_pushroottable(vm); + /* Push all arguments for the valuator function. */ + sq_pushinteger(vm, item->index); + for (int i = 0; i < nparam - 1; i++) { + sq_push(vm, i + 3); + } + + /* Call the function. Squirrel pops all parameters and pushes the return value. */ + if (SQ_FAILED(sq_call(vm, nparam + 1, SQTrue, SQTrue))) { + ScriptObject::SetAllowDoCommand(backup_allow); + throw sq_throwerror(vm, "failed to run filter"); + } + + SQBool add = SQFalse; + + /* Retrieve the return value */ + switch (sq_gettype(vm, -1)) { + case OT_BOOL: + sq_getbool(vm, -1, &add); + break; + + default: + ScriptObject::SetAllowDoCommand(backup_allow); + throw sq_throwerror(vm, "return value of filter is not valid (not bool)"); + } + + /* Pop the return value. */ + sq_poptop(vm); + + return add; + } + ); + + /* Pop the filter function */ + sq_poptop(vm); + } + + ScriptObject::SetAllowDoCommand(backup_allow); + } + + template + static void FillList(HSQUIRRELVM vm, ScriptList *list) + { + ScriptList::FillList(vm, list, [](const T *) { return true; }); + } + public: ScriptListMap items; ///< The items in the list ScriptListValueSet values; ///< The items in the list, sorted by value diff --git a/src/script/api/script_signlist.cpp b/src/script/api/script_signlist.cpp index c64891a903..000c104093 100644 --- a/src/script/api/script_signlist.cpp +++ b/src/script/api/script_signlist.cpp @@ -14,9 +14,9 @@ #include "../../safeguards.h" -ScriptSignList::ScriptSignList() +ScriptSignList::ScriptSignList(HSQUIRRELVM vm) { - for (const Sign *s : Sign::Iterate()) { - if (ScriptSign::IsValidSign(s->index)) this->AddItem(s->index); - } + ScriptList::FillList(vm, this, + [](const Sign *s) { return ScriptSign::IsValidSign(s->index); } + ); } diff --git a/src/script/api/script_signlist.hpp b/src/script/api/script_signlist.hpp index 125b06cce8..7e5117fca0 100644 --- a/src/script/api/script_signlist.hpp +++ b/src/script/api/script_signlist.hpp @@ -19,7 +19,29 @@ */ class ScriptSignList : public ScriptList { public: +#ifdef DOXYGEN_API ScriptSignList(); + + /** + * Apply a filter when building the list. + * @param filter_function The function which will be doing the filtering. + * @param params The params to give to the filters (minus the first param, + * which is always the index-value). + * @note You can write your own filters and use them. Just remember that + * the first parameter should be the index-value, and it should return + * a bool. + * @note Example: + * function Contains(sign_id, str) + * { + * local name = ScriptSign.GetName(sign_id); + * return name != null && name.find(str) != null; + * } + * ScriptSignList(Contains, "something"); + */ + ScriptSignList(void *filter_function, int params, ...); +#else + ScriptSignList(HSQUIRRELVM); +#endif /* DOXYGEN_API */ }; #endif /* SCRIPT_SIGNLIST_HPP */ diff --git a/src/script/api/script_subsidylist.cpp b/src/script/api/script_subsidylist.cpp index b92f1398c3..bd2ea96dfc 100644 --- a/src/script/api/script_subsidylist.cpp +++ b/src/script/api/script_subsidylist.cpp @@ -13,9 +13,7 @@ #include "../../safeguards.h" -ScriptSubsidyList::ScriptSubsidyList() +ScriptSubsidyList::ScriptSubsidyList(HSQUIRRELVM vm) { - for (const Subsidy *s : Subsidy::Iterate()) { - this->AddItem(s->index); - } + ScriptList::FillList(vm, this); } diff --git a/src/script/api/script_subsidylist.hpp b/src/script/api/script_subsidylist.hpp index 2d245bec9c..281a6a5880 100644 --- a/src/script/api/script_subsidylist.hpp +++ b/src/script/api/script_subsidylist.hpp @@ -19,7 +19,28 @@ */ class ScriptSubsidyList : public ScriptList { public: +#ifdef DOXYGEN_API ScriptSubsidyList(); + + /** + * Apply a filter when building the list. + * @param filter_function The function which will be doing the filtering. + * @param params The params to give to the filters (minus the first param, + * which is always the index-value). + * @note You can write your own filters and use them. Just remember that + * the first parameter should be the index-value, and it should return + * a bool. + * @note Example: + * function IsType(subsidy_id, type) + * { + * return ScriptSubsidy.GetSourceType(subsidy_id) == type; + * } + * ScriptSubsidyList(IsType, ScriptSubsidy.SPT_TOWN); + */ + ScriptSubsidyList(void *filter_function, int params, ...); +#else + ScriptSubsidyList(HSQUIRRELVM vm); +#endif /* DOXYGEN_API */ }; #endif /* SCRIPT_SUBSIDYLIST_HPP */ diff --git a/src/script/api/script_townlist.cpp b/src/script/api/script_townlist.cpp index b5af62b5f2..ec9a81f3f4 100644 --- a/src/script/api/script_townlist.cpp +++ b/src/script/api/script_townlist.cpp @@ -13,11 +13,9 @@ #include "../../safeguards.h" -ScriptTownList::ScriptTownList() +ScriptTownList::ScriptTownList(HSQUIRRELVM vm) { - for (const Town *t : Town::Iterate()) { - this->AddItem(t->index); - } + ScriptList::FillList(vm, this); } ScriptTownEffectList::ScriptTownEffectList() diff --git a/src/script/api/script_townlist.hpp b/src/script/api/script_townlist.hpp index 5b20eb2bd1..8e24d8af0e 100644 --- a/src/script/api/script_townlist.hpp +++ b/src/script/api/script_townlist.hpp @@ -19,7 +19,29 @@ */ class ScriptTownList : public ScriptList { public: +#ifdef DOXYGEN_API ScriptTownList(); + + /** + * Apply a filter when building the list. + * @param filter_function The function which will be doing the filtering. + * @param params The params to give to the filters (minus the first param, + * which is always the index-value). + * @note You can write your own filters and use them. Just remember that + * the first parameter should be the index-value, and it should return + * a bool. + * @note Example: + * ScriptTownList(ScriptTown.IsActionAvailable, ScriptTown.TOWN_ACTION_BRIBE); + * function MinPopulation(town_id, pop) + * { + * return ScriptTown.GetPopulation(town_id) >= pop; + * } + * ScriptTownList(MinPopulation, 1000); + */ + ScriptTownList(void *filter_function, int params, ...); +#else + ScriptTownList(HSQUIRRELVM vm); +#endif /* DOXYGEN_API */ }; /** diff --git a/src/script/api/script_vehiclelist.cpp b/src/script/api/script_vehiclelist.cpp index f3080953c3..73bb4f4d30 100644 --- a/src/script/api/script_vehiclelist.cpp +++ b/src/script/api/script_vehiclelist.cpp @@ -16,8 +16,6 @@ #include "../../vehicle_base.h" #include "../../vehiclelist_func.h" #include "../../train.h" -#include "../../core/backup_type.hpp" -#include <../squirrel/sqvm.h> #include "../../safeguards.h" @@ -25,84 +23,14 @@ ScriptVehicleList::ScriptVehicleList(HSQUIRRELVM vm) { EnforceDeityOrCompanyModeValid_Void(); - int nparam = sq_gettop(vm) - 1; - if (nparam >= 1) { - /* Make sure the filter function is really a function, and not any - * other type. It's parameter 2 for us, but for the user it's the - * first parameter they give. */ - SQObjectType valuator_type = sq_gettype(vm, 2); - if (valuator_type != OT_CLOSURE && valuator_type != OT_NATIVECLOSURE) { - throw sq_throwerror(vm, "parameter 1 has an invalid type (expected function)"); - } - - /* Push the function to call */ - sq_push(vm, 2); - } - - /* Don't allow docommand from a Valuator, as we can't resume in - * mid C++-code. */ - bool backup_allow = ScriptObject::GetAllowDoCommand(); - ScriptObject::SetAllowDoCommand(false); - - /* Limit the total number of ops that can be consumed by a filter operation, if a filter function is present */ - SQInteger new_ops_error_threshold = vm->_ops_till_suspend_error_threshold; - if (nparam >= 1 && vm->_ops_till_suspend_error_threshold == INT64_MIN) { - new_ops_error_threshold = vm->_ops_till_suspend - MAX_VALUATE_OPS; - vm->_ops_till_suspend_error_label = "vehicle filter function"; - } - AutoRestoreBackup ops_error_threshold_backup(vm->_ops_till_suspend_error_threshold, new_ops_error_threshold); - bool is_deity = ScriptCompanyMode::IsDeity(); CompanyID owner = ScriptObject::GetCompany(); - for (const Vehicle *v : Vehicle::Iterate()) { - if (v->owner != owner && !is_deity) continue; - if (!v->IsPrimaryVehicle() && !(v->type == VEH_TRAIN && ::Train::From(v)->IsFreeWagon())) continue; - - if (nparam < 1) { - /* No filter, just add the item. */ - this->AddItem(v->index); - continue; - } - - /* Push the root table as instance object, this is what squirrel does for meta-functions. */ - sq_pushroottable(vm); - /* Push all arguments for the valuator function. */ - sq_pushinteger(vm, v->index); - for (int i = 0; i < nparam - 1; i++) { - sq_push(vm, i + 3); - } - /* Call the function. Squirrel pops all parameters and pushes the return value. */ - if (SQ_FAILED(sq_call(vm, nparam + 1, SQTrue, SQTrue))) { - ScriptObject::SetAllowDoCommand(backup_allow); - throw sq_throwerror(vm, "failed to run filter"); + ScriptList::FillList(vm, this, + [is_deity, owner](const Vehicle *v) { + return (is_deity || v->owner == owner) && (v->IsPrimaryVehicle() || (v->type == VEH_TRAIN && ::Train::From(v)->IsFreeWagon())); } - - /* Retrieve the return value */ - switch (sq_gettype(vm, -1)) { - case OT_BOOL: { - SQBool add; - sq_getbool(vm, -1, &add); - if (add) this->AddItem(v->index); - break; - } - - default: { - ScriptObject::SetAllowDoCommand(backup_allow); - throw sq_throwerror(vm, "return value of filter is not valid (not bool)"); - } - } - - /* Pop the return value. */ - sq_poptop(vm); - } - - if (nparam >= 1) { - /* Pop the filter function */ - sq_poptop(vm); - } - - ScriptObject::SetAllowDoCommand(backup_allow); + ); } ScriptVehicleList_Station::ScriptVehicleList_Station(StationID station_id) @@ -182,11 +110,11 @@ ScriptVehicleList_Group::ScriptVehicleList_Group(GroupID group_id) if (!ScriptGroup::IsValidGroup((ScriptGroup::GroupID)group_id)) return; CompanyID owner = ScriptObject::GetCompany(); - for (const Vehicle *v : Vehicle::Iterate()) { - if (v->owner == owner && v->IsPrimaryVehicle()) { - if (v->group_id == group_id) this->AddItem(v->index); - } - } + + ScriptList::FillList(this, + [owner](const Vehicle *v) { return v->owner == owner && v->IsPrimaryVehicle(); }, + [group_id](const Vehicle *v) { return v->group_id == group_id; } + ); } ScriptVehicleList_DefaultGroup::ScriptVehicleList_DefaultGroup(ScriptVehicle::VehicleType vehicle_type) @@ -195,9 +123,9 @@ ScriptVehicleList_DefaultGroup::ScriptVehicleList_DefaultGroup(ScriptVehicle::Ve if (vehicle_type < ScriptVehicle::VT_RAIL || vehicle_type > ScriptVehicle::VT_AIR) return; CompanyID owner = ScriptObject::GetCompany(); - for (const Vehicle *v : Vehicle::Iterate()) { - if (v->owner == owner && v->IsPrimaryVehicle()) { - if (v->type == (::VehicleType)vehicle_type && v->group_id == ScriptGroup::GROUP_DEFAULT) this->AddItem(v->index); - } - } + + ScriptList::FillList(this, + [owner](const Vehicle *v) { return v->owner == owner && v->IsPrimaryVehicle(); }, + [vehicle_type](const Vehicle *v) { return v->type == (::VehicleType)vehicle_type && v->group_id == ScriptGroup::GROUP_DEFAULT; } + ); } diff --git a/src/settings.cpp b/src/settings.cpp index fbe3a8f109..0be251c447 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1781,7 +1781,7 @@ static void MaxVehiclesChanged(int32_t new_value) static void InvalidateShipPathCache(int32_t new_value) { for (Ship *s : Ship::Iterate()) { - s->cached_path.reset(); + s->cached_path.clear(); } } diff --git a/src/ship.h b/src/ship.h index 951b9f12cd..6ccd4329d1 100644 --- a/src/ship.h +++ b/src/ship.h @@ -14,61 +14,26 @@ #include "vehicle_base.h" #include "water_map.h" +#include "core/ring_buffer.hpp" extern const DiagDirection _ship_search_directions[TRACK_END][DIAGDIR_END]; void GetShipSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type); WaterClass GetEffectiveWaterClass(TileIndex tile); +typedef ring_buffer ShipPathCache; + /** Maximum segments of ship path cache */ static const uint8_t SHIP_PATH_CACHE_LENGTH = 32; static const uint8_t SHIP_PATH_CACHE_MASK = (SHIP_PATH_CACHE_LENGTH - 1); static_assert((SHIP_PATH_CACHE_LENGTH & SHIP_PATH_CACHE_MASK) == 0, ""); // Must be a power of 2 -struct ShipPathCache { - std::array td; - uint8_t start = 0; - uint8_t count = 0; - - inline bool empty() const { return this->count == 0; } - inline uint8_t size() const { return this->count; } - inline bool full() const { return this->count >= SHIP_PATH_CACHE_LENGTH; } - - inline void clear() - { - this->start = 0; - this->count = 0; - } - - inline Trackdir front() const { return this->td[this->start]; } - inline Trackdir back() const { return this->td[(this->start + this->count - 1) & SHIP_PATH_CACHE_MASK]; } - - /* push an item to the front of the ring, if the ring is already full, the back item is overwritten */ - inline void push_front(Trackdir td) - { - this->start = (this->start - 1) & SHIP_PATH_CACHE_MASK; - if (!this->full()) this->count++; - this->td[this->start] = td; - } - - inline void pop_front() - { - this->start = (this->start + 1) & SHIP_PATH_CACHE_MASK; - this->count--; - } - - inline void pop_back() - { - this->count--; - } -}; - /** * All ships have this type. */ struct Ship FINAL : public SpecializedVehicle { TrackBits state; ///< The "track" the ship is following. - std::unique_ptr cached_path; ///< Cached path. + ShipPathCache cached_path; ///< Cached path. Direction rotation; ///< Visible direction. int16_t rotation_x_pos; ///< NOSAVE: X Position before rotation. int16_t rotation_y_pos; ///< NOSAVE: Y Position before rotation. @@ -102,12 +67,6 @@ struct Ship FINAL : public SpecializedVehicle { ClosestDepot FindClosestDepot() override; void UpdateCache(); void SetDestTile(TileIndex tile) override; - - inline ShipPathCache &GetOrCreatePathCache() - { - if (!this->cached_path) this->cached_path.reset(new ShipPathCache()); - return *this->cached_path; - } }; bool IsShipDestinationTile(TileIndex tile, StationID station); diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index 0ee751cb08..7ba619184d 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -567,22 +567,22 @@ static Track ChooseShipTrack(Ship *v, TileIndex tile, DiagDirection enterdir, Tr path_found = false; } else { /* Attempt to follow cached path. */ - if (v->cached_path != nullptr && !v->cached_path->empty()) { - track = TrackdirToTrack(v->cached_path->front()); + if (!v->cached_path.empty()) { + track = TrackdirToTrack(v->cached_path.front()); if (HasBit(tracks, track)) { - v->cached_path->pop_front(); + v->cached_path.pop_front(); /* HandlePathfindResult() is not called here because this is not a new pathfinder result. */ return track; } /* Cached path is invalid so continue with pathfinder. */ - v->cached_path->clear(); + v->cached_path.clear(); } switch (_settings_game.pf.pathfinder_for_ships) { case VPF_NPF: track = NPFShipChooseTrack(v, path_found); break; - case VPF_YAPF: track = YapfShipChooseTrack(v, tile, enterdir, tracks, path_found, v->GetOrCreatePathCache()); break; + case VPF_YAPF: track = YapfShipChooseTrack(v, tile, enterdir, tracks, path_found, v->cached_path); break; default: NOT_REACHED(); } } @@ -882,7 +882,7 @@ static void ReverseShipIntoTrackdir(Ship *v, Trackdir trackdir) v->rotation_x_pos = v->x_pos; v->rotation_y_pos = v->y_pos; UpdateShipSpeed(v, 0); - if (v->cached_path != nullptr) v->cached_path->clear(); + v->cached_path.clear(); v->UpdatePosition(); v->UpdateViewport(true, true); @@ -896,7 +896,7 @@ static void ReverseShip(Ship *v) v->rotation_x_pos = v->x_pos; v->rotation_y_pos = v->y_pos; UpdateShipSpeed(v, 0); - if (v->cached_path != nullptr) v->cached_path->clear(); + v->cached_path.clear(); v->UpdatePosition(); v->UpdateViewport(true, true); @@ -1081,7 +1081,7 @@ static void ShipController(Ship *v) /* Ship is back on the bridge head, we need to consume its path * cache entry here as we didn't have to choose a ship track. */ - if (v->cached_path != nullptr && !v->cached_path->empty()) v->cached_path->pop_front(); + if (!v->cached_path.empty()) v->cached_path.pop_front(); } /* update image of ship, as well as delta XY */ @@ -1107,7 +1107,7 @@ bool Ship::Tick() void Ship::SetDestTile(TileIndex tile) { if (tile == this->dest_tile) return; - if (this->cached_path != nullptr) this->cached_path->clear(); + this->cached_path.clear(); this->dest_tile = tile; } diff --git a/src/sl/CMakeLists.txt b/src/sl/CMakeLists.txt index 88f233e715..06e086376d 100644 --- a/src/sl/CMakeLists.txt +++ b/src/sl/CMakeLists.txt @@ -54,5 +54,6 @@ add_files( train_speed_adaptation.cpp tunnel_sl.cpp vehicle_sl.cpp + water_regions_sl.cpp waypoint_sl.cpp ) diff --git a/src/sl/extended_ver_sl.cpp b/src/sl/extended_ver_sl.cpp index c608dec652..8469e964d1 100644 --- a/src/sl/extended_ver_sl.cpp +++ b/src/sl/extended_ver_sl.cpp @@ -208,6 +208,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_SAVEGAME_ID, XSCF_NULL, 1, 1, "slv_savegame_id", nullptr, nullptr, nullptr }, { XSLFI_NEWGRF_LAST_SERVICE, XSCF_NULL, 1, 1, "slv_newgrf_last_service", nullptr, nullptr, nullptr }, { XSLFI_CARGO_TRAVELLED, XSCF_NULL, 1, 1, "slv_cargo_travelled", nullptr, nullptr, nullptr }, + { XSLFI_WATER_REGIONS, XSCF_IGNORABLE_ALL, 1, 1, "slv_water_regions", nullptr, nullptr, "WRGN" }, { XSLFI_TABLE_PATS, XSCF_NULL, 1, 1, "table_pats", nullptr, nullptr, nullptr }, diff --git a/src/sl/extended_ver_sl.h b/src/sl/extended_ver_sl.h index 82c828f71c..1200ee803a 100644 --- a/src/sl/extended_ver_sl.h +++ b/src/sl/extended_ver_sl.h @@ -157,6 +157,7 @@ enum SlXvFeatureIndex { XSLFI_SAVEGAME_ID, ///< See: SLV_SAVEGAME_ID (PR #10719) XSLFI_NEWGRF_LAST_SERVICE, ///< See: SLV_NEWGRF_LAST_SERVICE (PR #11124) XSLFI_CARGO_TRAVELLED, ///< See: SLV_CARGO_TRAVELLED (PR #11283) + XSLFI_WATER_REGIONS, ///< See: SLV_WATER_REGIONS (PR #11435) XSLFI_TABLE_PATS, ///< Use upstream table format for PATS diff --git a/src/sl/saveload.cpp b/src/sl/saveload.cpp index 2713dc985a..61ee92d462 100644 --- a/src/sl/saveload.cpp +++ b/src/sl/saveload.cpp @@ -302,6 +302,7 @@ static const std::vector &ChunkHandlers() extern const ChunkHandlerTable _tunnel_chunk_handlers; extern const ChunkHandlerTable _train_speed_adaptation_chunk_handlers; extern const ChunkHandlerTable _new_signal_chunk_handlers; + extern const ChunkHandlerTable _water_region_chunk_handlers; extern const ChunkHandlerTable _debug_chunk_handlers; /** List of all chunks in a savegame. */ @@ -350,6 +351,7 @@ static const std::vector &ChunkHandlers() _tunnel_chunk_handlers, _train_speed_adaptation_chunk_handlers, _new_signal_chunk_handlers, + _water_region_chunk_handlers, _debug_chunk_handlers, }; diff --git a/src/sl/saveload_common.h b/src/sl/saveload_common.h index 6cf6c720ec..5001fe838e 100644 --- a/src/sl/saveload_common.h +++ b/src/sl/saveload_common.h @@ -378,6 +378,7 @@ enum SaveLoadVersion : uint16_t { SLV_TIMETABLE_START_TICKS, ///< 321 PR#11468 Convert timetable start from a date to ticks. SLV_TIMETABLE_START_TICKS_FIX, ///< 322 PR#11557 Fix for missing convert timetable start from a date to ticks. SLV_TIMETABLE_TICKS_TYPE, ///< 323 PR#11435 Convert timetable current order time to ticks. + SLV_WATER_REGIONS, ///< 324 PR#10543 Water Regions for ship pathfinder. SL_MAX_VERSION, ///< Highest possible saveload version diff --git a/src/sl/vehicle_sl.cpp b/src/sl/vehicle_sl.cpp index 431b1b3c34..c586398413 100644 --- a/src/sl/vehicle_sl.cpp +++ b/src/sl/vehicle_sl.cpp @@ -881,7 +881,7 @@ SaveLoadTable GetVehicleDescription(VehicleType vt) SLE_WRITEBYTE(Vehicle, type), SLE_VEH_INCLUDE(), SLE_VAR(Ship, state, SLE_UINT8), - SLEG_CONDVARVEC(_path_td, SLE_UINT8, SLV_SHIP_PATH_CACHE, SL_MAX_VERSION), + SLE_CONDRING(Ship, cached_path, SLE_UINT8, SLV_SHIP_PATH_CACHE, SL_MAX_VERSION), SLE_CONDVAR(Ship, rotation, SLE_UINT8, SLV_SHIP_ROTATION, SL_MAX_VERSION), SLE_CONDVAR_X(Ship, lost_count, SLE_UINT8, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_SHIP_LOST_COUNTER)), SLE_CONDVAR_X(Ship, critical_breakdown_count, SLE_UINT8, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_IMPROVED_BREAKDOWNS, 8)), @@ -1047,17 +1047,6 @@ static void Save_VEHS() } _path_layout_ctr = rv->cached_path->layout_ctr; } - } else if (v->type == VEH_SHIP) { - _path_td.clear(); - - Ship *s = Ship::From(v); - if (s->cached_path != nullptr && !s->cached_path->empty()) { - uint idx = s->cached_path->start; - for (uint i = 0; i < s->cached_path->size(); i++) { - _path_td.push_back(s->cached_path->td[idx]); - idx = (idx + 1) & SHIP_PATH_CACHE_MASK; - } - } } SlSetArrayIndex(v->index); SlObjectSaveFiltered(v, GetVehicleDescriptionFiltered(v->type)); @@ -1145,13 +1134,6 @@ void Load_VEHS() rv->cached_path->tile[i] = _path_tile[i]; } rv->cached_path->layout_ctr = _path_layout_ctr; - } else if (vtype == VEH_SHIP && !_path_td.empty() && _path_td.size() <= SHIP_PATH_CACHE_LENGTH) { - Ship *s = Ship::From(v); - s->cached_path.reset(new ShipPathCache()); - s->cached_path->count = (uint8_t)_path_td.size(); - for (size_t i = 0; i < _path_td.size(); i++) { - s->cached_path->td[i] = _path_td[i]; - } } } } diff --git a/src/sl/water_regions_sl.cpp b/src/sl/water_regions_sl.cpp new file mode 100644 index 0000000000..1aa4422a88 --- /dev/null +++ b/src/sl/water_regions_sl.cpp @@ -0,0 +1,27 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file water_regions_sl.cpp Handles saving and loading of water region data */ +#include "../stdafx.h" + +#include "saveload.h" + +extern SaveLoadVersion _sl_xv_upstream_version; + +struct GetWaterRegionsLoadInfo +{ + static SaveLoadVersion GetLoadVersion() + { + return _sl_xv_upstream_version != SL_MIN_VERSION ? _sl_xv_upstream_version : SLV_WATER_REGIONS; + } +}; + +static const ChunkHandler water_region_chunk_handlers[] = { + MakeUpstreamChunkHandler<'WRGN', GetWaterRegionsLoadInfo>(), +}; + +extern const ChunkHandlerTable _water_region_chunk_handlers(water_region_chunk_handlers); diff --git a/src/track_func.h b/src/track_func.h index 296ef5d526..fb68b9375a 100644 --- a/src/track_func.h +++ b/src/track_func.h @@ -16,6 +16,7 @@ #include "slope_func.h" using SetTrackBitIterator = SetBitIterator; +using SetTrackdirBitIterator = SetBitIterator; /** * Checks if a Track is valid. diff --git a/src/track_type.h b/src/track_type.h index 974e95576f..ef59906da4 100644 --- a/src/track_type.h +++ b/src/track_type.h @@ -91,6 +91,8 @@ enum Trackdir : byte { INVALID_TRACKDIR = 0xFF, ///< Flag for an invalid trackdir }; +/** Allow incrementing of Trackdir variables */ +DECLARE_POSTFIX_INCREMENT(Trackdir) /** Define basic enum properties */ template <> struct EnumPropsT : MakeEnumPropsT {}; diff --git a/src/tunnelbridge_cmd.cpp b/src/tunnelbridge_cmd.cpp index ed74881fc2..153825fa1d 100644 --- a/src/tunnelbridge_cmd.cpp +++ b/src/tunnelbridge_cmd.cpp @@ -21,6 +21,7 @@ #include "ship.h" #include "roadveh.h" #include "pathfinder/yapf/yapf_cache.h" +#include "pathfinder/water_regions.h" #include "newgrf_sound.h" #include "autoslope.h" #include "tunnelbridge_map.h" @@ -787,6 +788,8 @@ CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32_t p1, MakeAqueductBridgeRamp(tile_end, owner, ReverseDiagDir(dir)); CheckForDockingTile(tile_start); CheckForDockingTile(tile_end); + InvalidateWaterRegion(tile_start); + InvalidateWaterRegion(tile_end); break; default: diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp index db22012f2f..00f99281f1 100644 --- a/src/water_cmd.cpp +++ b/src/water_cmd.cpp @@ -38,6 +38,7 @@ #include "company_gui.h" #include "newgrf_generic.h" #include "industry.h" +#include "pathfinder/water_regions.h" #include "object_base.h" #include "object_map.h" #include "newgrf_object.h" @@ -145,6 +146,9 @@ CommandCost CmdBuildShipDepot(TileIndex tile, DoCommandFlag flags, uint32_t p1, } if (flags & DC_EXEC) { + InvalidateWaterRegion(tile); + InvalidateWaterRegion(tile2); + Depot *depot = new Depot(tile); depot->build_date = _date; @@ -258,6 +262,7 @@ void MakeWaterKeepingClass(TileIndex tile, Owner o) /* Zero map array and terminate animation */ DoClearSquare(tile); + InvalidateWaterRegion(tile); /* Maybe change to water */ switch (wc) { @@ -355,6 +360,10 @@ static CommandCost DoBuildLock(TileIndex tile, DiagDirection dir, DoCommandFlag } if (flags & DC_EXEC) { + InvalidateWaterRegion(tile); + InvalidateWaterRegion(tile + delta); + InvalidateWaterRegion(tile - delta); + /* Update company infrastructure counts. */ Company *c = Company::GetIfValid(_current_company); if (c != nullptr) { @@ -512,6 +521,8 @@ CommandCost CmdBuildCanal(TileIndex tile, DoCommandFlag flags, uint32_t p1, uint if (!water) cost.AddCost(ret); if (flags & DC_EXEC) { + InvalidateWaterRegion(current_tile); + if (IsTileType(current_tile, MP_WATER) && IsCanal(current_tile)) { Owner owner = GetTileOwner(current_tile); if (Company::IsValidID(owner)) { @@ -561,8 +572,11 @@ CommandCost CmdBuildCanal(TileIndex tile, DoCommandFlag flags, uint32_t p1, uint } } + static CommandCost ClearTile_Water(TileIndex tile, DoCommandFlag flags) { + if (flags & DC_EXEC) InvalidateWaterRegion(tile); + switch (GetWaterTileType(tile)) { case WATER_TILE_CLEAR: { if (flags & DC_NO_WATER) return_cmd_error(STR_ERROR_CAN_T_BUILD_ON_WATER); @@ -1250,6 +1264,8 @@ void DoFloodTile(TileIndex target) } if (flooded) { + InvalidateWaterRegion(target); + /* Mark surrounding canal tiles dirty too to avoid glitches */ MarkCanalsAndRiversAroundDirty(target); diff --git a/src/waypoint_cmd.cpp b/src/waypoint_cmd.cpp index bbf326176b..102197d3b2 100644 --- a/src/waypoint_cmd.cpp +++ b/src/waypoint_cmd.cpp @@ -16,6 +16,7 @@ #include "town.h" #include "waypoint_base.h" #include "pathfinder/yapf/yapf_cache.h" +#include "pathfinder/water_regions.h" #include "strings_func.h" #include "viewport_func.h" #include "viewport_kdtree.h" @@ -553,6 +554,7 @@ CommandCost CmdBuildBuoy(TileIndex tile, DoCommandFlag flags, uint32_t p1, uint3 if (wp->town == nullptr) MakeDefaultName(wp); MakeBuoy(tile, wp->index, GetWaterClass(tile)); + InvalidateWaterRegion(tile); CheckForDockingTile(tile); MarkTileDirtyByTile(tile); ClearNeighbourNonFloodingStates(tile);