Changeset - r28419:6bf3242eed6a
[Not reviewed]
src/genworld.cpp
Show inline comments
 
@@ -35,6 +35,7 @@
 
#include "string_func.h"
 
#include "thread.h"
 
#include "tgp.h"
 
#include "pathfinder/water_regions.h"
 

	
 
#include "safeguards.h"
 

	
 
@@ -174,6 +175,8 @@ static void _GenerateWorld()
 
			}
 
		}
 

	
 
		InitializeWaterRegions();
 

	
 
		BasePersistentStorageArray::SwitchMode(PSM_LEAVE_GAMELOOP);
 

	
 
		ResetObjectToPlace();
src/pathfinder/CMakeLists.txt
Show inline comments
 
@@ -5,4 +5,6 @@ add_files(
 
    follow_track.hpp
 
    pathfinder_func.h
 
    pathfinder_type.h
 
    water_regions.h
 
    water_regions.cpp
 
)
src/pathfinder/water_regions.cpp
Show inline comments
 
new file 100644
 
/*
 
 * 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 <http://www.gnu.org/licenses/>.
 
 */
 

	
 
 /** @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 Map::SizeX() / WATER_REGION_EDGE_LENGTH; }
 
static inline int GetWaterRegionMapSizeY() { return Map::SizeY() / 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<TWaterRegionTraversabilityBits, DIAGDIR_END> 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<TWaterRegionPatchLabel, WATER_REGION_NUMBER_OF_TILES> 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<TileIndex> 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<WaterRegion> _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 &region = 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<int>(_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 &current_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<TWaterRegionPatchLabel> 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 &current_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<WaterRegionSaveLoadInfo> GetWaterRegionSaveLoadInfo()
 
{
 
	std::vector<WaterRegionSaveLoadInfo> result;
 
	for (WaterRegion &region : _water_regions) result.push_back({ region.IsInitialized() });
 
	return result;
 
}
 

	
 
void LoadWaterRegions(const std::vector<WaterRegionSaveLoadInfo> &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 &region = _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<size_t>(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();
 
		}
 
	}
 
}
src/pathfinder/water_regions.h
Show inline comments
 
new file 100644
 
/*
 
 * 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 <http://www.gnu.org/licenses/>.
 
 */
 

	
 
 /** @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(const WaterRegionPatchDesc &)>;
 
void VisitWaterRegionPatchNeighbors(const WaterRegionPatchDesc &water_region_patch, TVisitWaterRegionPatchCallBack &callback);
 

	
 
void InitializeWaterRegions();
 

	
 
struct WaterRegionSaveLoadInfo
 
{
 
	bool initialized;
 
};
 

	
 
std::vector<WaterRegionSaveLoadInfo> GetWaterRegionSaveLoadInfo();
 
void LoadWaterRegions(const std::vector<WaterRegionSaveLoadInfo> &save_load_info);
 

	
 
#endif /* WATER_REGIONS_H */
src/pathfinder/yapf/CMakeLists.txt
Show inline comments
 
@@ -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
 
)
src/pathfinder/yapf/yapf_ship.cpp
Show inline comments
 
@@ -14,46 +14,64 @@
 

	
 
#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 Types>
 
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)
 
	{
 
		if (v->current_order.IsType(OT_GOTO_STATION)) {
 
			m_destStation   = v->current_order.GetDestination();
 
			m_destTile      = CalcClosestStationTile(m_destStation, v->tile, STATION_DOCK);
 
			m_destStation = v->current_order.GetDestination();
 
			m_destTile = CalcClosestStationTile(m_destStation, v->tile, STATION_DOCK);
 
			m_destTrackdirs = INVALID_TRACKDIR_BIT;
 
		} else {
 
			m_destStation   = INVALID_STATION;
 
			m_destTile      = v->dest_tile;
 
			m_destStation = INVALID_STATION;
 
			m_destTile = v->dest_tile;
 
			m_destTrackdirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->dest_tile, TRANSPORT_WATER, 0));
 
		}
 
	}
 

	
 
	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<Tpf*>(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 Types>
 
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<Tpf *>(this);
 
		return *static_cast<Tpf*>(this);
 
	}
 

	
 
	std::vector<WaterRegionDesc> 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<WaterRegionPatchDesc> &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();
 
		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);
 
		const std::vector<WaterRegionPatchDesc> high_level_path = YapfShipFindWaterRegionPath(v, tile, NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1);
 
		if (high_level_path.empty()) {
 
			path_found = false;
 
			/* Make the ship move around aimlessly. This prevents repeated pathfinder calls and clearly indicates that the ship is lost. */
 
			return CreateRandomPath(v, src_tile, trackdir, path_cache, SHIP_LOST_PATH_LENGTH);
 
		}
 

	
 
		Trackdir next_trackdir = INVALID_TRACKDIR; // this would mean "path not found"
 
		/* 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);
 

	
 
		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 = YAPF_SHIP_PATH_CACHE_LENGTH / 2;
 
			/* Set origin and destination nodes */
 
			pf.SetOrigin(src_tile, trackdirs);
 
			pf.SetDestination(v);
 
			const bool is_intermediate_destination = static_cast<int>(high_level_path.size()) >= NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1;
 
			if (is_intermediate_destination) pf.SetIntermediateDestination(high_level_path.back());
 

	
 
			/* 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);
 

	
 
			/* 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 < YAPF_SHIP_PATH_CACHE_LENGTH) {
 
					path_cache.push_front(pNode->GetTrackdir());
 
			/* 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<WaterRegionPatchDesc> 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 Types>
 
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<Tpf *>(this);
 
		return *static_cast<Tpf*>(this);
 
	}
 

	
 
public:
 
@@ -266,10 +349,10 @@ public:
 
		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->type == VEH_SHIP && (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(), &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 <class Tpf_, class Ttrack_follower, class Tnode_list>
 
struct CYapfShip_TypesT
 
{
 
	/** Types - shortcut for this struct type */
 
	typedef CYapfShip_TypesT<Tpf_, Ttrack_follower, Tnode_list>  Types;
 
	typedef CYapfShip_TypesT<Tpf_, Ttrack_follower, Tnode_list>  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;
 

	
 
	/** 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<Types>                 PfBase;        // base pathfinder class
 
	typedef CYapfFollowShipT<Types>           PfFollow;      // node follower
 
	typedef CYapfOriginTileT<Types>           PfOrigin;      // origin provider
 
	typedef CYapfDestinationTileWaterT<Types> PfDestination; // destination/distance provider
 
	typedef CYapfSegmentCostCacheNoneT<Types> PfCache;       // segment cost cache provider
 
	typedef CYapfCostShipT<Types>             PfCost;        // cost provider
 
	/** Pathfinder components (modules). */
 
	typedef CYapfBaseT<Types>                 PfBase;        ///< Base pathfinder class.
 
	typedef CYapfFollowShipT<Types>           PfFollow;      ///< Node follower.
 
	typedef CYapfOriginTileT<Types>           PfOrigin;      ///< Origin provider.
 
	typedef CYapfDestinationTileWaterT<Types> PfDestination; ///< Destination/distance provider.
 
	typedef CYapfSegmentCostCacheNoneT<Types> PfCache;       ///< Segment cost cache provider.
 
	typedef CYapfCostShipT<Types>             PfCost;        ///< Cost provider.
 
};
 

	
 
/* YAPF type 1 - uses TileIndex/Trackdir as Node key */
 
struct CYapfShip1 : CYapfT<CYapfShip_TypesT<CYapfShip1, CFollowTrackWater    , CShipNodeListTrackDir> > {};
 
/* YAPF type 2 - uses TileIndex/DiagDirection as Node key */
 
struct CYapfShip2 : CYapfT<CYapfShip_TypesT<CYapfShip2, CFollowTrackWater    , CShipNodeListExitDir > > {};
 
struct CYapfShip : CYapfT<CYapfShip_TypesT<CYapfShip, CFollowTrackWater, CShipNodeListExitDir > >
 
{
 
	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 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);
 
}
src/pathfinder/yapf/yapf_ship_regions.cpp
Show inline comments
 
new file 100644
 
/*
 
 * 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 <http://www.gnu.org/licenses/>.
 
 */
 

	
 
 /** @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 <class Tkey_>
 
struct CYapfRegionNodeT {
 
	typedef Tkey_ Key;
 
	typedef CYapfRegionNodeT<Tkey_> 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 Types>
 
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<Tpf*>(this); }
 

	
 
private:
 
	std::vector<CYapfRegionPatchNodeKey> 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 Types>
 
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<Tpf*>(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 Types>
 
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<Tpf*>(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<WaterRegionPatchDesc> 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<int>(Map::Size() * 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<WaterRegionPatchDesc> 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 Types>
 
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<Tpf*>(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 <class Tpf_, class Tnode_list>
 
struct CYapfRegion_TypesT
 
{
 
	typedef CYapfRegion_TypesT<Tpf_, Tnode_list> 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<Types>                 PfBase;        ///< Base pathfinder class.
 
	typedef CYapfFollowRegionT<Types>         PfFollow;      ///< Node follower.
 
	typedef CYapfOriginRegionT<Types>         PfOrigin;      ///< Origin provider.
 
	typedef CYapfDestinationRegionT<Types>    PfDestination; ///< Destination/distance provider.
 
	typedef CYapfSegmentCostCacheNoneT<Types> PfCache;       ///< Segment cost cache provider.
 
	typedef CYapfCostRegionT<Types>           PfCost;        ///< Cost provider.
 
};
 

	
 
typedef CNodeList_HashTableT<CYapfRegionNodeT<CYapfRegionPatchNodeKey>, 12, 12> CRegionNodeListWater;
 

	
 
struct CYapfRegionWater : CYapfT<CYapfRegion_TypesT<CYapfRegionWater, CRegionNodeListWater>>
 
{
 
	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<WaterRegionPatchDesc> YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length)
 
{
 
	return CYapfRegionWater::FindWaterRegionPath(v, start_tile, max_returned_path_length);
 
}
src/pathfinder/yapf/yapf_ship_regions.h
Show inline comments
 
new file 100644
 
/*
 
 * 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 <http://www.gnu.org/licenses/>.
 
 */
 

	
 
 /** @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<WaterRegionPatchDesc> YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length);
 

	
 
#endif /* YAPF_SHIP_REGIONS_H */
src/saveload/CMakeLists.txt
Show inline comments
 
@@ -44,4 +44,5 @@ add_files(
 
    town_sl.cpp
 
    vehicle_sl.cpp
 
    waypoint_sl.cpp
 
    water_regions_sl.cpp
 
)
src/saveload/afterload.cpp
Show inline comments
 
@@ -61,6 +61,7 @@
 
#include "../timer/timer.h"
 
#include "../timer/timer_game_calendar.h"
 
#include "../timer/timer_game_tick.h"
 
#include "../pathfinder/water_regions.h"
 

	
 
#include "saveload_internal.h"
 

	
 
@@ -3296,6 +3297,8 @@ bool AfterLoadGame()
 
		}
 
	}
 

	
 
	if (IsSavegameVersionBefore(SLV_WATER_REGIONS)) InitializeWaterRegions();
 

	
 
	return true;
 
}
 

	
src/saveload/saveload.cpp
Show inline comments
 
@@ -249,6 +249,7 @@ static const std::vector<ChunkHandlerRef
 
	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[] = {
 
@@ -286,6 +287,7 @@ static const std::vector<ChunkHandlerRef
 
		_airport_chunk_handlers,
 
		_object_chunk_handlers,
 
		_persistent_storage_chunk_handlers,
 
		_water_region_chunk_handlers,
 
	};
 

	
 
	static std::vector<ChunkHandlerRef> _chunk_handlers;
src/saveload/saveload.h
Show inline comments
 
@@ -366,6 +366,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
 
};
src/saveload/water_regions_sl.cpp
Show inline comments
 
new file 100644
 
/*
 
 * 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 <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @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"
 

	
 
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 &region : GetWaterRegionSaveLoadInfo()) {
 
			SlSetArrayIndex(index++);
 
			SlObject(&region, _water_region_desc);
 
		}
 
	}
 

	
 
	void Load() const override
 
	{
 
		const std::vector<SaveLoad> slt = SlTableHeader(_water_region_desc);
 

	
 
		int index;
 

	
 
		std::vector<WaterRegionSaveLoadInfo> loaded_info;
 
		while ((index = SlIterateArray()) != -1) {
 
			WaterRegionSaveLoadInfo region_info;
 
			SlObject(&region_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);
src/track_func.h
Show inline comments
 
@@ -16,6 +16,7 @@
 
#include "slope_func.h"
 

	
 
using SetTrackBitIterator = SetBitIterator<Track, TrackBits>;
 
using SetTrackdirBitIterator = SetBitIterator<Trackdir, TrackdirBits>;
 

	
 
/**
 
 * Checks if a Track is valid.
src/track_type.h
Show inline comments
 
@@ -86,6 +86,9 @@ enum Trackdir : byte {
 
	INVALID_TRACKDIR  = 0xFF,       ///< Flag for an invalid trackdir
 
};
 

	
 
/** Allow incrementing of Trackdir variables */
 
DECLARE_POSTFIX_INCREMENT(Trackdir)
 

	
 
/**
 
 * Enumeration of bitmasks for the TrackDirs
 
 *
src/tunnelbridge_cmd.cpp
Show inline comments
 
@@ -20,6 +20,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"
 
@@ -561,6 +562,8 @@ CommandCost CmdBuildBridge(DoCommandFlag
 
				MakeAqueductBridgeRamp(tile_end,   owner, ReverseDiagDir(dir));
 
				CheckForDockingTile(tile_start);
 
				CheckForDockingTile(tile_end);
 
				InvalidateWaterRegion(tile_start);
 
				InvalidateWaterRegion(tile_end);
 
				break;
 

	
 
			default:
src/water_cmd.cpp
Show inline comments
 
@@ -39,6 +39,7 @@
 
#include "industry.h"
 
#include "water_cmd.h"
 
#include "landscape_cmd.h"
 
#include "pathfinder/water_regions.h"
 

	
 
#include "table/strings.h"
 

	
 
@@ -133,6 +134,9 @@ CommandCost CmdBuildShipDepot(DoCommandF
 
	}
 

	
 
	if (flags & DC_EXEC) {
 
		InvalidateWaterRegion(tile);
 
		InvalidateWaterRegion(tile2);
 

	
 
		Depot *depot = new Depot(tile);
 
		depot->build_date = TimerGameCalendar::date;
 

	
 
@@ -243,6 +247,7 @@ void MakeWaterKeepingClass(TileIndex til
 

	
 
	/* Zero map array and terminate animation */
 
	DoClearSquare(tile);
 
	InvalidateWaterRegion(tile);
 

	
 
	/* Maybe change to water */
 
	switch (wc) {
 
@@ -340,6 +345,10 @@ static CommandCost DoBuildLock(TileIndex
 
	}
 

	
 
	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) {
 
@@ -482,6 +491,8 @@ CommandCost CmdBuildCanal(DoCommandFlag 
 
		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)) {
 
@@ -529,8 +540,11 @@ CommandCost CmdBuildCanal(DoCommandFlag 
 
	}
 
}
 

	
 

	
 
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);
 
@@ -1161,6 +1175,8 @@ void DoFloodTile(TileIndex target)
 
	}
 

	
 
	if (flooded) {
 
		InvalidateWaterRegion(target);
 

	
 
		/* Mark surrounding canal tiles dirty too to avoid glitches */
 
		MarkCanalsAndRiversAroundDirty(target);
 

	
src/waypoint_cmd.cpp
Show inline comments
 
@@ -15,6 +15,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"
 
@@ -346,6 +347,7 @@ CommandCost CmdBuildBuoy(DoCommandFlag f
 
		if (wp->town == nullptr) MakeDefaultName(wp);
 

	
 
		MakeBuoy(tile, wp->index, GetWaterClass(tile));
 
		InvalidateWaterRegion(tile);
 
		CheckForDockingTile(tile);
 
		MarkTileDirtyByTile(tile);
 

	
0 comments (0 inline, 0 general)