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