From 150e502cf9d812aae9878c6de7abec585acec200 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Sat, 12 Jun 2021 09:56:59 +0200 Subject: [PATCH 01/45] Add generation of public roads linking towns --- src/genworld.cpp | 2 + src/genworld.h | 1 + src/genworld_gui.cpp | 3 +- src/lang/english.txt | 7 + src/map_func.h | 7 + src/road.cpp | 656 +++++++++++++++++++++++++++++++++++++++++ src/settings_gui.cpp | 1 + src/settings_type.h | 1 + src/table/settings.ini | 16 +- 9 files changed, 690 insertions(+), 4 deletions(-) diff --git a/src/genworld.cpp b/src/genworld.cpp index 003ab4993f..0d6338f030 100644 --- a/src/genworld.cpp +++ b/src/genworld.cpp @@ -42,6 +42,7 @@ void GenerateClearTile(); void GenerateIndustries(); void GenerateObjects(); void GenerateTrees(); +void GeneratePublicRoads(); void StartupEconomy(); void StartupCompanies(); @@ -140,6 +141,7 @@ static void _GenerateWorld() GenerateIndustries(); GenerateObjects(); GenerateTrees(); + GeneratePublicRoads(); } } diff --git a/src/genworld.h b/src/genworld.h index d3821e947f..3699a0b93d 100644 --- a/src/genworld.h +++ b/src/genworld.h @@ -78,6 +78,7 @@ enum GenWorldProgress { GWP_INDUSTRY, ///< Generate industries GWP_OBJECT, ///< Generate objects (radio tower, light houses) GWP_TREE, ///< Generate trees + GWP_PUBLIC_ROADS,///< Generate public roads GWP_GAME_INIT, ///< Initialize the game GWP_RUNTILELOOP, ///< Runs the tile loop 1280 times to make snow etc GWP_RUNSCRIPT, ///< Runs the game script at most 2500 times, or when ever the script sleeps diff --git a/src/genworld_gui.cpp b/src/genworld_gui.cpp index 3e170162da..94e9fa7d89 100644 --- a/src/genworld_gui.cpp +++ b/src/genworld_gui.cpp @@ -1462,6 +1462,7 @@ static const StringID _generation_class_table[] = { STR_SCENEDIT_TOOLBAR_INDUSTRY_GENERATION, STR_GENERATION_OBJECT_GENERATION, STR_GENERATION_TREE_GENERATION, + STR_GENERATION_PUBLIC_ROADS_GENERATION, STR_GENERATION_SETTINGUP_GAME, STR_GENERATION_PREPARING_TILELOOP, STR_GENERATION_PREPARING_SCRIPT, @@ -1568,7 +1569,7 @@ void ShowGenerateWorldProgress() static void _SetGeneratingWorldProgress(GenWorldProgress cls, uint progress, uint total) { - static const int percent_table[] = {0, 5, 14, 17, 20, 40, 60, 65, 80, 85, 95, 99, 100 }; + static const int percent_table[] = {0, 7, 14, 22, 29, 36, 44, 51, 58, 65, 73, 80, 90, 100 }; static_assert(lengthof(percent_table) == GWP_CLASS_COUNT + 1); assert(cls < GWP_CLASS_COUNT); diff --git a/src/lang/english.txt b/src/lang/english.txt index 08d7dc5b4b..60af82c5de 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1931,6 +1931,12 @@ STR_CONFIG_SETTING_TREES_AROUND_SNOWLINE_HELPTEXT :Adjust placemen STR_CONFIG_SETTING_TREES_AROUND_SNOWLINE_RANGE :Arctic tree range: {STRING2} STR_CONFIG_SETTING_TREES_AROUND_SNOWLINE_RANGE_HELPTEXT :Approximate range of arctic trees around snow line +STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS :Build public roads connecting towns: {STRING2} +STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS_HELPTEXT :Generates public roads which connect the times. Takes a bit of time on bigger maps. 'Build and avoid' generates roads which avoid curves and result in very grid-like connections. +STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS_NONE :None (Default) +STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS_WITH_CURVES :Build with curves +STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS_AVOID_CURVES :Build and avoid curves + STR_CONFIG_SETTING_TREE_GROWTH :Tree growth speed: {STRING2} STR_CONFIG_SETTING_TREE_GROWTH_HELPTEXT :Control rate at which trees grow during the game. This might affect industries which rely on tree growth, for example lumber mills STR_CONFIG_SETTING_TREE_GROWTH_NORMAL :Normal @@ -3774,6 +3780,7 @@ STR_GENERATION_PROGRESS_NUM :{BLACK}{NUM} / STR_GENERATION_WORLD_GENERATION :{BLACK}World generation STR_GENERATION_RIVER_GENERATION :{BLACK}River generation STR_GENERATION_TREE_GENERATION :{BLACK}Tree generation +STR_GENERATION_PUBLIC_ROADS_GENERATION :{BLACK}Public roads generation STR_GENERATION_OBJECT_GENERATION :{BLACK}Object generation STR_GENERATION_CLEARING_TILES :{BLACK}Rough and rocky area generation STR_GENERATION_SETTINGUP_GAME :{BLACK}Setting up game diff --git a/src/map_func.h b/src/map_func.h index e9c876284d..eebd21fb67 100644 --- a/src/map_func.h +++ b/src/map_func.h @@ -399,6 +399,13 @@ static inline TileIndex TileAddByDiagDir(TileIndex tile, DiagDirection dir) return TILE_ADD(tile, TileOffsByDiagDir(dir)); } +/** Checks if two tiles are adjacent */ +static inline bool AreTilesAdjacent(TileIndex a, TileIndex b) +{ + return (std::abs((int)TileX(a) - (int)TileX(b)) <= 1) && + (std::abs((int)TileY(a) - (int)TileY(b)) <= 1); +} + /** * Determines the DiagDirection to get from one tile to another. * The tiles do not necessarily have to be adjacent. diff --git a/src/road.cpp b/src/road.cpp index a578bd953d..7e525d9a72 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -8,6 +8,11 @@ /** @file road.cpp Generic road related functions. */ #include "stdafx.h" +#include +#include +#include +#include +#include #include "rail_map.h" #include "road_map.h" #include "water_map.h" @@ -18,13 +23,30 @@ #include "date_func.h" #include "landscape.h" #include "road.h" +#include "town.h" +#include "pathfinder/npf/aystar.h" +#include "tunnelbridge.h" #include "road_func.h" #include "roadveh.h" +#include "map_func.h" +#include "core/backup_type.hpp" +#include "core/random_func.hpp" +#include + +#include "cheat_func.h" +#include "command_func.h" #include "safeguards.h" uint32 _road_layout_change_counter = 0; +/** Whether to build public roads */ +enum PublicRoadsConstruction { + PRC_NONE, ///< Generate no public roads + PRC_WITH_CURVES, ///< Generate roads with lots of curves + PRC_AVOID_CURVES, ///< Generate roads avoiding curves if possible +}; + /** * Return if the tile is a valid tile for a crossing. * @@ -216,6 +238,640 @@ RoadTypes GetCompanyRoadTypes(CompanyID company, bool introduces) return rts; } +/* ========================================================================= */ +/* PUBLIC ROADS */ +/* ========================================================================= */ + +CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text = nullptr); +CommandCost CmdBuildTunnel(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text = nullptr); +CommandCost CmdBuildRoad(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text = nullptr); + +static std::vector _town_centers; +static std::vector _towns_visited_along_the_way; +static bool _has_tunnel_in_path; +static RoadType _public_road_type; +static const uint _public_road_hash_size = 8U; ///< The number of bits the hash for river finding should have. + +/** +* Simple hash function for public road tiles to be used by AyStar. +* @param tile The tile to hash. +* @param dir The unused direction. +* @return The hash for the tile. +*/ +static uint PublicRoad_Hash(uint tile, uint dir) +{ + return GB(TileHash(TileX(tile), TileY(tile)), 0, _public_road_hash_size); +} + +static const int32 BASE_COST = 1; // Cost for utilizing an existing road, bridge, or tunnel. +static const int32 COST_FOR_NEW_ROAD = 10; // Cost for building a new road. +static const int32 COST_FOR_SLOPE = 5; // Additional cost if the road heads up or down a slope. + +/** AyStar callback for getting the cost of the current node. */ +static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode *parent) +{ + int32 cost = BASE_COST; + + if (!IsTileType(current->tile, MP_ROAD)) { + if (!AreTilesAdjacent(parent->path.node.tile, current->tile)) + { + // We're not adjacent, so we built a tunnel or bridge. + cost += (DistanceManhattan(parent->path.node.tile, current->tile)) * COST_FOR_NEW_ROAD + 6 * COST_FOR_SLOPE; + } + else if (!IsTileFlat(current->tile)) { + cost += COST_FOR_NEW_ROAD; + cost += COST_FOR_SLOPE; + } + else + { + cost += COST_FOR_NEW_ROAD; + } + } + + if (_settings_game.game_creation.build_public_roads == PRC_AVOID_CURVES && + parent->path.parent != nullptr && + DiagdirBetweenTiles(parent->path.parent->node.tile, parent->path.node.tile) != DiagdirBetweenTiles(parent->path.node.tile, current->tile)) { + cost += 1; + } + + return cost; +} + +/** AyStar callback for getting the estimated cost to the destination. */ +static int32 PublicRoad_CalculateH(AyStar *aystar, AyStarNode *current, OpenListNode *parent) +{ + return DistanceManhattan(*static_cast(aystar->user_target), current->tile) * BASE_COST; +} + +/** Helper function to check if a tile along a certain direction is going up an inclined slope. */ +static bool IsUpwardsSlope(TileIndex tile, DiagDirection road_direction) +{ + const auto slope = GetTileSlope(tile); + + if (!IsInclinedSlope(slope)) return false; + + const auto slope_direction = GetInclinedSlopeDirection(slope); + + return road_direction == slope_direction; +} + +/** Helper function to check if a tile along a certain direction is going down an inclined slope. */ +static bool IsDownwardsSlope(const TileIndex tile, const DiagDirection road_direction) +{ + const auto slope = GetTileSlope(tile); + + if (!IsInclinedSlope(slope)) return false; + + const auto slope_direction = GetInclinedSlopeDirection(slope); + + return road_direction == ReverseDiagDir(slope_direction); +} + +static TileIndex BuildTunnel(PathNode *current, TileIndex end_tile = INVALID_TILE, const bool build_tunnel = false) +{ + const TileIndex start_tile = current->node.tile; + int start_z; + GetTileSlope(start_tile, &start_z); + + if (start_z == 0) return INVALID_TILE; + + const DiagDirection direction = GetInclinedSlopeDirection(GetTileSlope(start_tile)); + + if (!build_tunnel) { + // We are not building yet, so we still need to find the end_tile. + const TileIndexDiff delta = TileOffsByDiagDir(direction); + end_tile = start_tile; + int end_z; + + for (int tunnel_length = 1;;tunnel_length++) { + end_tile += delta; + + if (!IsValidTile(end_tile)) return INVALID_TILE; + if (tunnel_length > _settings_game.construction.max_tunnel_length) return INVALID_TILE; + + GetTileSlope(end_tile, &end_z); + + if (start_z == end_z) break; + + if (!_cheats.crossing_tunnels.value && IsTunnelInWay(end_tile, start_z)) return INVALID_TILE; + } + + // No too long or super-short tunnels and always ending up on a matching upwards slope. + if (IsSteepSlope(GetTileSlope(end_tile)) || IsHalftileSlope(GetTileSlope(end_tile))) return INVALID_TILE; + if (GetTileSlope(start_tile) != ComplementSlope(GetTileSlope(end_tile))) return INVALID_TILE; + if (AreTilesAdjacent(start_tile, end_tile)) return INVALID_TILE; + if (!IsValidTile(end_tile)) return INVALID_TILE; + if (!IsTileType(end_tile, MP_CLEAR) && !IsTileType(end_tile, MP_TREES)) return INVALID_TILE; + } + + assert(!build_tunnel || (IsValidTile(end_tile) && GetTileSlope(start_tile) == ComplementSlope(GetTileSlope(end_tile)))); + + Backup cur_company(_current_company, OWNER_DEITY, FILE_LINE); + const auto build_tunnel_cmd = CmdBuildTunnel(start_tile, build_tunnel ? DC_EXEC : DC_NONE, _public_road_type | (TRANSPORT_ROAD << 8), 0); + cur_company.Restore(); + + assert(!build_tunnel || build_tunnel_cmd.Succeeded()); + assert(!build_tunnel || (IsTileType(start_tile, MP_TUNNELBRIDGE) && IsTileType(end_tile, MP_TUNNELBRIDGE))); + + if (!build_tunnel_cmd.Succeeded()) return INVALID_TILE; + + return end_tile; +} + +static TileIndex BuildBridge(PathNode *current, TileIndex end_tile = INVALID_TILE, const bool build_bridge = false) +{ + const TileIndex start_tile = current->node.tile; + + const DiagDirection direction = ReverseDiagDir(GetInclinedSlopeDirection(GetTileSlope(start_tile))); + + if (!build_bridge) { + // We are not building yet, so we still need to find the end_tile. + for (TileIndex tile = start_tile + TileOffsByDiagDir(direction); + IsValidTile(tile) && + (GetTunnelBridgeLength(start_tile, tile) <= _settings_game.construction.max_bridge_length) && + (GetTileZ(start_tile) < (GetTileZ(tile) + _settings_game.construction.max_bridge_height)) && + (GetTileZ(tile) <= GetTileZ(start_tile)); + tile += TileOffsByDiagDir(direction)) { + + auto is_complementary_slope = + !IsSteepSlope(GetTileSlope(tile)) && + !IsHalftileSlope(GetTileSlope(tile)) && + GetTileSlope(start_tile) == ComplementSlope(GetTileSlope(tile)); + + // No super-short bridges and always ending up on a matching upwards slope. + if (!AreTilesAdjacent(start_tile, tile) && is_complementary_slope) { + end_tile = tile; + break; + } + } + + if (!IsValidTile(end_tile)) return INVALID_TILE; + if (GetTileSlope(start_tile) != ComplementSlope(GetTileSlope(end_tile))) return INVALID_TILE; + if (!IsTileType(end_tile, MP_CLEAR) && !IsTileType(end_tile, MP_TREES)) return INVALID_TILE; + } + + assert(!build_bridge || (IsValidTile(end_tile) && GetTileSlope(start_tile) == ComplementSlope(GetTileSlope(end_tile)))); + + std::vector available_bridge_types; + + for (uint i = 0; i < MAX_BRIDGES; ++i) { + if (CheckBridgeAvailability(i, GetTunnelBridgeLength(start_tile, end_tile)).Succeeded()) { + available_bridge_types.push_back(i); + } + } + + assert(!build_bridge || !available_bridge_types.empty()); + if (available_bridge_types.empty()) return INVALID_TILE; + + const auto bridge_type = available_bridge_types[build_bridge ? RandomRange(uint32(available_bridge_types.size())) : 0]; + + Backup cur_company(_current_company, OWNER_DEITY, FILE_LINE); + const auto build_bridge_cmd = CmdBuildBridge(end_tile, build_bridge ? DC_EXEC : DC_NONE, start_tile, bridge_type | (ROADTYPE_ROAD << 8) | (TRANSPORT_ROAD << 15)); + cur_company.Restore(); + + assert(!build_bridge || build_bridge_cmd.Succeeded()); + assert(!build_bridge || (IsTileType(start_tile, MP_TUNNELBRIDGE) && IsTileType(end_tile, MP_TUNNELBRIDGE))); + + if (!build_bridge_cmd.Succeeded()) return INVALID_TILE; + + return end_tile; +} + +static TileIndex BuildRiverBridge(PathNode *current, const DiagDirection road_direction, TileIndex end_tile = INVALID_TILE, const bool build_bridge = false) +{ + const TileIndex start_tile = current->node.tile; + + if (!build_bridge) { + // We are not building yet, so we still need to find the end_tile. + // We will only build a bridge if we need to cross a river, so first check for that. + TileIndex tile = start_tile + TileOffsByDiagDir(road_direction); + + if (!IsWaterTile(tile) || !IsRiver(tile)) return INVALID_TILE; + + // Now let's see if we can bridge it. But don't bridge anything more than 4 river tiles. Cities aren't allowed to, so public roads we are not either. + // Only bridges starting at slopes should be longer ones. The others look like crap when built this way. Players can build them but the map generator + // should not force that on them. This is just to bridge rivers, not to make long bridges. + for (; + IsValidTile(tile) && + (GetTunnelBridgeLength(start_tile, tile) <= 5) && + (GetTileZ(start_tile) < (GetTileZ(tile) + _settings_game.construction.max_bridge_height)) && + (GetTileZ(tile) <= GetTileZ(start_tile)); + tile += TileOffsByDiagDir(road_direction)) { + + if ((IsTileType(tile, MP_CLEAR) || IsTileType(tile, MP_TREES)) && + GetTileZ(tile) <= GetTileZ(start_tile) && + GetTileSlope(tile) == SLOPE_FLAT) { + end_tile = tile; + break; + } + } + + if (!IsValidTile(end_tile)) return INVALID_TILE; + if (!IsTileType(end_tile, MP_CLEAR) && !IsTileType(end_tile, MP_TREES)) return INVALID_TILE; + } + + assert(!build_bridge || IsValidTile(end_tile)); + + std::vector available_bridge_types; + + for (uint i = 0; i < MAX_BRIDGES; ++i) { + if (CheckBridgeAvailability(i, GetTunnelBridgeLength(start_tile, end_tile)).Succeeded()) { + available_bridge_types.push_back(i); + } + } + + const auto bridge_type = available_bridge_types[build_bridge ? RandomRange(uint32(available_bridge_types.size())) : 0]; + + Backup cur_company(_current_company, OWNER_DEITY, FILE_LINE); + const auto build_bridge_cmd = CmdBuildBridge(end_tile, build_bridge ? DC_EXEC : DC_NONE, start_tile, bridge_type | (ROADTYPE_ROAD << 8) | (TRANSPORT_ROAD << 15)); + cur_company.Restore(); + + assert(!build_bridge || build_bridge_cmd.Succeeded()); + assert(!build_bridge || (IsTileType(start_tile, MP_TUNNELBRIDGE) && IsTileType(end_tile, MP_TUNNELBRIDGE))); + + if (!build_bridge_cmd.Succeeded()) return INVALID_TILE; + + return end_tile; +} + +static bool IsValidNeighbourOfPreviousTile(const TileIndex tile, const TileIndex previous_tile) +{ + if (!IsValidTile(tile) || (tile == previous_tile)) return false; + + if (IsTileType(tile, MP_TUNNELBRIDGE)) + { + if (GetOtherTunnelBridgeEnd(tile) == previous_tile) return true; + + const auto tunnel_direction = GetTunnelBridgeDirection(tile); + + if (previous_tile + TileOffsByDiagDir(tunnel_direction) != tile) return false; + } else { + + if (!IsTileType(tile, MP_CLEAR) && !IsTileType(tile, MP_TREES) && !IsTileType(tile, MP_ROAD)) return false; + + const auto slope = GetTileSlope(tile); + + // Do not allow foundations. We'll mess things up later. + const bool has_foundation = GetFoundationSlope(tile) != slope; + + if (has_foundation) return false; + + if (IsInclinedSlope(slope)) { + const auto slope_direction = GetInclinedSlopeDirection(slope); + const auto road_direction = DiagdirBetweenTiles(previous_tile, tile); + + if (slope_direction != road_direction && ReverseDiagDir(slope_direction) != road_direction) { + return false; + } + } else if (slope != SLOPE_FLAT) { + return false; + } + } + + return true; +} + +/** AyStar callback for getting the neighbouring nodes of the given node. */ +static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) +{ + const TileIndex tile = current->path.node.tile; + + aystar->num_neighbours = 0; + + // Check if we just went through a tunnel or a bridge. + if (current->path.parent != nullptr && !AreTilesAdjacent(tile, current->path.parent->node.tile)) { + const auto previous_tile = current->path.parent->node.tile; + + // We went through a tunnel or bridge, this limits our options to proceed to only forward. + const auto tunnel_bridge_direction = DiagdirBetweenTiles(previous_tile, tile); + + const TileIndex tunnel_bridge_end = tile + TileOffsByDiagDir(tunnel_bridge_direction); + + if (IsValidNeighbourOfPreviousTile(tunnel_bridge_end, tile)) { + aystar->neighbours[aystar->num_neighbours].tile = tunnel_bridge_end; + aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; + aystar->num_neighbours++; + } + } else { + // Handle all the regular neighbours and existing tunnels/bridges. + std::vector potential_neighbours; + + if (IsTileType(tile, MP_TUNNELBRIDGE)) { + auto neighbour = GetOtherTunnelBridgeEnd(tile); + + aystar->neighbours[aystar->num_neighbours].tile = neighbour; + aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; + aystar->num_neighbours++; + + neighbour = tile + TileOffsByDiagDir(ReverseDiagDir(DiagdirBetweenTiles(tile, neighbour))); + + if (IsValidNeighbourOfPreviousTile(neighbour, tile)) { + aystar->neighbours[aystar->num_neighbours].tile = neighbour; + aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; + aystar->num_neighbours++; + } + } else { + for (DiagDirection d = DIAGDIR_BEGIN; d < DIAGDIR_END; d++) { + const auto neighbour = tile + TileOffsByDiagDir(d); + + if (IsValidNeighbourOfPreviousTile(neighbour, tile)) { + aystar->neighbours[aystar->num_neighbours].tile = neighbour; + aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; + aystar->num_neighbours++; + } + } + + // Check if we can turn this into a tunnel or a bridge. + if (current->path.parent != nullptr) { + const auto road_direction = DiagdirBetweenTiles(current->path.parent->node.tile, tile); + + if (IsUpwardsSlope(tile, road_direction) && !_has_tunnel_in_path) { + const auto tunnel_end = BuildTunnel(¤t->path); + + if (tunnel_end != INVALID_TILE && + !IsSteepSlope(GetTileSlope(tunnel_end)) && + !IsHalftileSlope(GetTileSlope(tunnel_end)) && + (GetTileSlope(tunnel_end) == ComplementSlope(GetTileSlope(current->path.node.tile)))) { + assert(IsValidDiagDirection(DiagdirBetweenTiles(tile, tunnel_end))); + aystar->neighbours[aystar->num_neighbours].tile = tunnel_end; + aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; + aystar->num_neighbours++; + _has_tunnel_in_path = true; + } + } + else if (IsDownwardsSlope(tile, road_direction)) { + const auto bridge_end = BuildBridge(¤t->path); + + if (bridge_end != INVALID_TILE && + !IsSteepSlope(GetTileSlope(bridge_end)) && + !IsHalftileSlope(GetTileSlope(bridge_end)) && + (GetTileSlope(bridge_end) == ComplementSlope(GetTileSlope(current->path.node.tile)))) { + assert(IsValidDiagDirection(DiagdirBetweenTiles(tile, bridge_end))); + aystar->neighbours[aystar->num_neighbours].tile = bridge_end; + aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; + aystar->num_neighbours++; + } + } + else if (GetTileSlope(tile) == SLOPE_FLAT) + { + // Check if we could bridge a river from a flat tile. Not looking pretty on the map but you gotta do what you gotta do. + const auto bridge_end = BuildRiverBridge(¤t->path, DiagdirBetweenTiles(current->path.parent->node.tile, tile)); + assert(bridge_end == INVALID_TILE || GetTileSlope(bridge_end) == SLOPE_FLAT); + + if (bridge_end != INVALID_TILE) { + assert(IsValidDiagDirection(DiagdirBetweenTiles(tile, bridge_end))); + aystar->neighbours[aystar->num_neighbours].tile = bridge_end; + aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; + aystar->num_neighbours++; + } + } + } + } + } +} + +/** AyStar callback for checking whether we reached our destination. */ +static int32 PublicRoad_EndNodeCheck(const AyStar *aystar, const OpenListNode *current) +{ + // Mark towns visited along the way. + const auto search_result = + std::find(_town_centers.begin(), _town_centers.end(), current->path.node.tile); + + if (search_result != _town_centers.end()) { + _towns_visited_along_the_way.push_back(current->path.node.tile); + } + + return current->path.node.tile == *static_cast(aystar->user_target) ? AYSTAR_FOUND_END_NODE : AYSTAR_DONE; +} + +/** AyStar callback when an route has been found. */ +static void PublicRoad_FoundEndNode(AyStar *aystar, OpenListNode *current) +{ + PathNode* child = nullptr; + + for (PathNode *path = ¤t->path; path != nullptr; path = path->parent) { + const TileIndex tile = path->node.tile; + + if (IsTileType(tile, MP_TUNNELBRIDGE)) { + // Just follow the path; infrastructure is already in place. + continue; + } + + if (path->parent == nullptr || AreTilesAdjacent(tile, path->parent->node.tile)) { + RoadBits road_bits = ROAD_NONE; + + if (child != nullptr) { + const TileIndex tile2 = child->node.tile; + road_bits |= DiagDirToRoadBits(DiagdirBetweenTiles(tile, tile2)); + } + if (path->parent != nullptr) { + const TileIndex tile2 = path->parent->node.tile; + road_bits |= DiagDirToRoadBits(DiagdirBetweenTiles(tile, tile2)); + } + + if (child != nullptr || path->parent != nullptr) { + // Check if we need to build anything. + bool need_to_build_road = true; + + if (IsTileType(tile, MP_ROAD)) { + const RoadBits existing_bits = GetRoadBits(tile, RTT_ROAD); + CLRBITS(road_bits, existing_bits); + if (road_bits == ROAD_NONE) need_to_build_road = false; + } + + // If it is already a road and has the right bits, we are good. Otherwise build the needed ones. + if (need_to_build_road) + { + Backup cur_company(_current_company, OWNER_DEITY, FILE_LINE); + CmdBuildRoad(tile, DC_EXEC, _public_road_type << 4 | road_bits, 0); + cur_company.Restore(); + } + } + } else { + // We only get here if we have a parent and we're not adjacent to it. River/Tunnel time! + const DiagDirection road_direction = DiagdirBetweenTiles(tile, path->parent->node.tile); + + auto end_tile = INVALID_TILE; + + if (IsUpwardsSlope(tile, road_direction)) { + end_tile = BuildTunnel(path, path->parent->node.tile, true); + assert(IsValidTile(end_tile) && IsDownwardsSlope(end_tile, road_direction)); + } else if (IsDownwardsSlope(tile, road_direction)) { + // Provide the function with the end tile, since we already know it, but still check the result. + end_tile = BuildBridge(path, path->parent->node.tile, true); + assert(IsValidTile(end_tile) && IsUpwardsSlope(end_tile, road_direction)); + } else { + // River bridge is the last possibility. + assert(GetTileSlope(tile) == SLOPE_FLAT); + end_tile = BuildRiverBridge(path, road_direction, path->parent->node.tile, true); + assert(IsValidTile(end_tile) && GetTileSlope(end_tile) == SLOPE_FLAT); + } + } + + child = path; + } +} + +bool FindPath(AyStar& finder, const TileIndex from, TileIndex to) +{ + finder.CalculateG = PublicRoad_CalculateG; + finder.CalculateH = PublicRoad_CalculateH; + finder.GetNeighbours = PublicRoad_GetNeighbours; + finder.EndNodeCheck = PublicRoad_EndNodeCheck; + finder.FoundEndNode = PublicRoad_FoundEndNode; + finder.user_target = &(to); + finder.max_search_nodes = 1 << 20; // 1,048,576 + + finder.Init(PublicRoad_Hash, 1 << _public_road_hash_size); + + _has_tunnel_in_path = false; + + AyStarNode start {}; + start.tile = from; + start.direction = INVALID_TRACKDIR; + finder.AddStartNode(&start, 0); + + int result = AYSTAR_STILL_BUSY; + + while (result == AYSTAR_STILL_BUSY) { + result = finder.Main(); + } + + const bool found_path = (result == AYSTAR_FOUND_END_NODE); + + return found_path; +} + +/** +* Build the public road network connecting towns using AyStar. +*/ +void GeneratePublicRoads() +{ + using namespace std; + + if (_settings_game.game_creation.build_public_roads == PRC_NONE) return; + + _town_centers.clear(); + _towns_visited_along_the_way.clear(); + + vector towns; + towns.clear(); + { + for (const Town *town : Town::Iterate()) { + towns.push_back(town->xy); + _town_centers.push_back(town->xy); + } + } + + if (towns.empty()) { + return; + } + + SetGeneratingWorldProgress(GWP_PUBLIC_ROADS, uint(towns.size())); + + // Create a list of networks which also contain a value indicating how many times we failed to connect to them. + vector>>> town_networks; + unordered_map>> towns_reachable_networks; + + TileIndex main_town = *towns.begin(); + towns.erase(towns.begin()); + + _public_road_type = GetTownRoadType(Town::GetByTile(main_town)); + + auto main_network = make_shared>(); + main_network->push_back(main_town); + + town_networks.emplace_back(0, main_network); + IncreaseGeneratingWorldProgress(GWP_PUBLIC_ROADS); + + sort(towns.begin(), towns.end(), [&](auto a, auto b) { return DistanceManhattan(main_town, a) < DistanceManhattan(main_town, b); }); + + for (auto begin_town : towns) { + // Check if we can connect to any of the networks. + _towns_visited_along_the_way.clear(); + + auto reachable_network_iter = towns_reachable_networks.find(begin_town); + bool found_easy_path = false; + + if (reachable_network_iter != towns_reachable_networks.end()) { + auto reachable_network = reachable_network_iter->second; + + sort(reachable_network->begin(), reachable_network->end(), [&](auto a, auto b) { return DistanceManhattan(begin_town, a) < DistanceManhattan(begin_town, b); }); + + const TileIndex end_town = *reachable_network->begin(); + + AyStar finder {}; + + found_easy_path = FindPath(finder, begin_town, end_town); + + finder.Free(); + } + + if (found_easy_path) { + reachable_network_iter->second->push_back(begin_town); + + for (const TileIndex visited_town : _towns_visited_along_the_way) { + if (visited_town != begin_town) towns_reachable_networks[visited_town] = reachable_network_iter->second; + } + } else { + // Sort networks by failed connection attempts, so we try the most likely one first. + sort(town_networks.begin(), town_networks.end(), [&](auto a, auto b) { return a.first < b.first; }); + + std::function>>)> can_reach_network = [&](auto network_pair) { + AyStar finder {}; + + auto network = network_pair.second; + + // Try to connect to the town in the network that is closest to us. + // If we can't connect to that one, we can't connect to any of them since they are all interconnected. + sort(network->begin(), network->end(), [&](auto a, auto b) { return DistanceManhattan(begin_town, a) < DistanceManhattan(begin_town, b); }); + const TileIndex end_town = *network->begin(); + + const bool found_path = FindPath(finder, begin_town, end_town); + + if (found_path) { + network->push_back(begin_town); + + for (auto visited_town : _towns_visited_along_the_way) { + if (visited_town != begin_town) towns_reachable_networks[visited_town] = network; + } + } + + // Increase number of failed attempts if necessary. + network_pair.first += (found_path ? (network_pair.first > 0 ? -1 : 0) : 1); + + finder.Free(); + + return found_path; + + }; + + if (!any_of(town_networks.begin(), town_networks.end(), can_reach_network)) { + // We failed to connect to any network, so we are a separate network. Let future towns try to connect to us. + auto new_network = make_shared>(); + new_network->push_back(begin_town); + + // We basically failed to connect to this many towns. + int towns_already_in_networks = std::accumulate(town_networks.begin(), town_networks.end(), 0, [&](int accumulator, auto network_pair) { + return accumulator + static_cast(network_pair.second->size()); + }); + + town_networks.emplace_back(towns_already_in_networks, new_network); + + for (const TileIndex visited_town : _towns_visited_along_the_way) { + if (visited_town != begin_town) towns_reachable_networks.insert(make_pair(visited_town, new_network)); + } + } + } + + IncreaseGeneratingWorldProgress(GWP_PUBLIC_ROADS); + } +} + +/* ========================================================================= */ +/* END PUBLIC ROADS */ +/* ========================================================================= */ + /** * Get list of road types, regardless of company availability. * @param introduces If true, include road types introduced by other road types diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index f5a8af3ce6..43b5f1bac3 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -2036,6 +2036,7 @@ static SettingsContainer &GetSettingsTree() genworld->Add(new SettingEntry("economy.initial_city_size")); genworld->Add(new SettingEntry("economy.town_layout")); genworld->Add(new SettingEntry("economy.town_min_distance")); + genworld->Add(new SettingEntry("game_creation.build_public_roads")); genworld->Add(new SettingEntry("difficulty.industry_density")); genworld->Add(new SettingEntry("gui.pause_on_newgame")); genworld->Add(new SettingEntry("game_creation.ending_year")); diff --git a/src/settings_type.h b/src/settings_type.h index 1ad161930f..8b09284b30 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -388,6 +388,7 @@ struct GameCreationSettings { bool lakes_allowed_in_deserts; ///< are lakes allowed in deserts? uint8 amount_of_rocks; ///< the amount of rocks uint8 height_affects_rocks; ///< the affect that map height has on rocks + uint8 build_public_roads; ///< build public roads connecting towns }; /** Settings related to construction in-game */ diff --git a/src/table/settings.ini b/src/table/settings.ini index 7c02b39b9b..bf3054f19a 100644 --- a/src/table/settings.ini +++ b/src/table/settings.ini @@ -4073,9 +4073,19 @@ strval = STR_JUST_COMMA patxname = ""rocks.game_creation.height_affects_rocks"" ;;game_creation.build_public_roads -[SDT_NULL] -length = 1 -extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_JOKERPP) +[SDT_VAR] +base = GameSettings +var = game_creation.build_public_roads +type = SLE_UINT8 +guiflags = SGF_MULTISTRING | SGF_NEWGAME_ONLY +def = 0 +min = 0 +max = 2 +str = STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS +strhelp = STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS_HELPTEXT +strval = STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS_NONE +patxname = ""public_roads.game_creation.build_public_roads"" +extver = SlXvFeatureTest(XSLFTO_OR, XSLFI_JOKERPP) ; locale From 83ea6e9fd8afd1a44874a8fe893c6eaa56773b2a Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Sun, 13 Jun 2021 10:17:20 +0200 Subject: [PATCH 02/45] Replace original algorithm with a simpler one --- src/road.cpp | 157 +++++++++++++++++++-------------------------------- 1 file changed, 59 insertions(+), 98 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index 7e525d9a72..ca24e8421c 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -246,8 +246,6 @@ CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32 p1, u CommandCost CmdBuildTunnel(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text = nullptr); CommandCost CmdBuildRoad(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text = nullptr); -static std::vector _town_centers; -static std::vector _towns_visited_along_the_way; static bool _has_tunnel_in_path; static RoadType _public_road_type; static const uint _public_road_hash_size = 8U; ///< The number of bits the hash for river finding should have. @@ -263,14 +261,15 @@ static uint PublicRoad_Hash(uint tile, uint dir) return GB(TileHash(TileX(tile), TileY(tile)), 0, _public_road_hash_size); } -static const int32 BASE_COST = 1; // Cost for utilizing an existing road, bridge, or tunnel. -static const int32 COST_FOR_NEW_ROAD = 10; // Cost for building a new road. -static const int32 COST_FOR_SLOPE = 5; // Additional cost if the road heads up or down a slope. +static const int32 _base_cost = 1; // Cost for utilizing an existing road, bridge, or tunnel. /** AyStar callback for getting the cost of the current node. */ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode *parent) { - int32 cost = BASE_COST; + static const int32 COST_FOR_NEW_ROAD = 10; // Cost for building a new road. + static const int32 COST_FOR_SLOPE = 5; // Additional cost if the road heads up or down a slope. + + int32 cost = _base_cost; if (!IsTileType(current->tile, MP_ROAD)) { if (!AreTilesAdjacent(parent->path.node.tile, current->tile)) @@ -300,7 +299,7 @@ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode * /** AyStar callback for getting the estimated cost to the destination. */ static int32 PublicRoad_CalculateH(AyStar *aystar, AyStarNode *current, OpenListNode *parent) { - return DistanceManhattan(*static_cast(aystar->user_target), current->tile) * BASE_COST; + return DistanceManhattan(*static_cast(aystar->user_target), current->tile) * _base_cost; } /** Helper function to check if a tile along a certain direction is going up an inclined slope. */ @@ -633,14 +632,6 @@ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) /** AyStar callback for checking whether we reached our destination. */ static int32 PublicRoad_EndNodeCheck(const AyStar *aystar, const OpenListNode *current) { - // Mark towns visited along the way. - const auto search_result = - std::find(_town_centers.begin(), _town_centers.end(), current->path.node.tile); - - if (search_result != _town_centers.end()) { - _towns_visited_along_the_way.push_back(current->path.node.tile); - } - return current->path.node.tile == *static_cast(aystar->user_target) ? AYSTAR_FOUND_END_NODE : AYSTAR_DONE; } @@ -750,117 +741,87 @@ void GeneratePublicRoads() using namespace std; if (_settings_game.game_creation.build_public_roads == PRC_NONE) return; + + vector cities; + vector villages; + vector unconnected_villages; - _town_centers.clear(); - _towns_visited_along_the_way.clear(); + _public_road_type = GetTownRoadType(*Town::Iterate().begin()); - vector towns; - towns.clear(); - { - for (const Town *town : Town::Iterate()) { - towns.push_back(town->xy); - _town_centers.push_back(town->xy); + for (const Town *town : Town::Iterate()) { + if (town->larger_town) { + cities.push_back(town->xy); + } else { + villages.push_back(town->xy); } } - if (towns.empty()) { - return; - } - - SetGeneratingWorldProgress(GWP_PUBLIC_ROADS, uint(towns.size())); - - // Create a list of networks which also contain a value indicating how many times we failed to connect to them. - vector>>> town_networks; - unordered_map>> towns_reachable_networks; + SetGeneratingWorldProgress(GWP_PUBLIC_ROADS, uint(cities.size() + villages.size())); - TileIndex main_town = *towns.begin(); - towns.erase(towns.begin()); - - _public_road_type = GetTownRoadType(Town::GetByTile(main_town)); - - auto main_network = make_shared>(); - main_network->push_back(main_town); - - town_networks.emplace_back(0, main_network); - IncreaseGeneratingWorldProgress(GWP_PUBLIC_ROADS); - - sort(towns.begin(), towns.end(), [&](auto a, auto b) { return DistanceManhattan(main_town, a) < DistanceManhattan(main_town, b); }); - - for (auto begin_town : towns) { - // Check if we can connect to any of the networks. - _towns_visited_along_the_way.clear(); - - auto reachable_network_iter = towns_reachable_networks.find(begin_town); - bool found_easy_path = false; - - if (reachable_network_iter != towns_reachable_networks.end()) { - auto reachable_network = reachable_network_iter->second; - - sort(reachable_network->begin(), reachable_network->end(), [&](auto a, auto b) { return DistanceManhattan(begin_town, a) < DistanceManhattan(begin_town, b); }); - - const TileIndex end_town = *reachable_network->begin(); + // Try to connect every village to the nearest city. + for (auto village : villages) { + sort(cities.begin(), cities.end(), [&](auto a, auto b) { return DistanceManhattan(village, a) < DistanceManhattan(village, b); }); + bool is_connected = false; + + for (auto city : cities) { AyStar finder {}; - found_easy_path = FindPath(finder, begin_town, end_town); + auto found_path = FindPath(finder, village, city); finder.Free(); - } - - if (found_easy_path) { - reachable_network_iter->second->push_back(begin_town); - for (const TileIndex visited_town : _towns_visited_along_the_way) { - if (visited_town != begin_town) towns_reachable_networks[visited_town] = reachable_network_iter->second; + if (found_path){ + is_connected = true; + break; } + } + + if (!is_connected) { + unconnected_villages.push_back(village); } else { - // Sort networks by failed connection attempts, so we try the most likely one first. - sort(town_networks.begin(), town_networks.end(), [&](auto a, auto b) { return a.first < b.first; }); + IncreaseGeneratingWorldProgress(GWP_PUBLIC_ROADS); + } + } - std::function>>)> can_reach_network = [&](auto network_pair) { - AyStar finder {}; + vector villages_copy(unconnected_villages); - auto network = network_pair.second; + // If a village could not be connected to a city, try to connect it to another unconnected village. + for (auto village : unconnected_villages) { + sort(villages_copy.begin(), villages_copy.end(), [&](auto a, auto b) { return DistanceManhattan(village, a) < DistanceManhattan(village, b); }); - // Try to connect to the town in the network that is closest to us. - // If we can't connect to that one, we can't connect to any of them since they are all interconnected. - sort(network->begin(), network->end(), [&](auto a, auto b) { return DistanceManhattan(begin_town, a) < DistanceManhattan(begin_town, b); }); - const TileIndex end_town = *network->begin(); + for (auto other_village : villages_copy) { + AyStar finder {}; - const bool found_path = FindPath(finder, begin_town, end_town); + auto found_path = FindPath(finder, village, other_village); - if (found_path) { - network->push_back(begin_town); + finder.Free(); - for (auto visited_town : _towns_visited_along_the_way) { - if (visited_town != begin_town) towns_reachable_networks[visited_town] = network; - } - } + if (found_path) { + break; + } + } - // Increase number of failed attempts if necessary. - network_pair.first += (found_path ? (network_pair.first > 0 ? -1 : 0) : 1); + IncreaseGeneratingWorldProgress(GWP_PUBLIC_ROADS); + } - finder.Free(); + vector cities_copy(cities); - return found_path; + // Connect the cities with each-other. + for (auto city : cities) { + sort(cities_copy.begin(), cities_copy.end(), [&](auto a, auto b) { return DistanceManhattan(city, a) < DistanceManhattan(city, b); }); - }; + for (auto other_city : cities_copy) { + if (other_city == city) continue; - if (!any_of(town_networks.begin(), town_networks.end(), can_reach_network)) { - // We failed to connect to any network, so we are a separate network. Let future towns try to connect to us. - auto new_network = make_shared>(); - new_network->push_back(begin_town); + AyStar finder {}; - // We basically failed to connect to this many towns. - int towns_already_in_networks = std::accumulate(town_networks.begin(), town_networks.end(), 0, [&](int accumulator, auto network_pair) { - return accumulator + static_cast(network_pair.second->size()); - }); + auto found_path = FindPath(finder, city, other_city); - town_networks.emplace_back(towns_already_in_networks, new_network); + finder.Free(); - for (const TileIndex visited_town : _towns_visited_along_the_way) { - if (visited_town != begin_town) towns_reachable_networks.insert(make_pair(visited_town, new_network)); - } + if (found_path) { + break; } } From 35ebeff8742201bba0cf4bf003b4d87755dc25e5 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Sun, 13 Jun 2021 10:18:00 +0200 Subject: [PATCH 03/45] Remove low performance containers with standard library ones --- src/pathfinder/npf/aystar.cpp | 49 +++++++++++++++++++++++++---------- src/pathfinder/npf/aystar.h | 16 ++++++++++-- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/pathfinder/npf/aystar.cpp b/src/pathfinder/npf/aystar.cpp index b14053b7c0..5ac1071aff 100644 --- a/src/pathfinder/npf/aystar.cpp +++ b/src/pathfinder/npf/aystar.cpp @@ -26,6 +26,7 @@ #include "aystar.h" #include "../../safeguards.h" +#include "core/mem_func.hpp" /** * This looks in the hash whether a node exists in the closed list. @@ -34,7 +35,9 @@ */ PathNode *AyStar::ClosedListIsInList(const AyStarNode *node) { - return (PathNode*)this->closedlist_hash.Get(node->tile, node->direction); + const auto result = this->closedlist_hash.find(std::make_pair(node->tile, node->direction)); + + return (result == this->closedlist_hash.end()) ? nullptr : result->second; } /** @@ -45,9 +48,10 @@ PathNode *AyStar::ClosedListIsInList(const AyStarNode *node) void AyStar::ClosedListAdd(const PathNode *node) { /* Add a node to the ClosedList */ - PathNode *new_node = MallocT(1); + const auto new_node = MallocT(1); *new_node = *node; - this->closedlist_hash.Set(node->node.tile, node->node.direction, new_node); + + this->closedlist_hash[std::make_pair(node->node.tile, node->node.direction)] = new_node; } /** @@ -57,7 +61,9 @@ void AyStar::ClosedListAdd(const PathNode *node) */ OpenListNode *AyStar::OpenListIsInList(const AyStarNode *node) { - return (OpenListNode*)this->openlist_hash.Get(node->tile, node->direction); + const auto result = this->openlist_hash.find(std::make_pair(node->tile, node->direction)); + + return (result == this->openlist_hash.end()) ? nullptr : result->second; } /** @@ -70,7 +76,7 @@ OpenListNode *AyStar::OpenListPop() /* Return the item the Queue returns.. the best next OpenList item. */ OpenListNode *res = (OpenListNode*)this->openlist_queue.Pop(); if (res != nullptr) { - this->openlist_hash.DeleteValue(res->path.node.tile, res->path.node.direction); + this->openlist_hash.erase(std::make_pair(res->path.node.tile, res->path.node.direction)); } return res; @@ -87,7 +93,7 @@ void AyStar::OpenListAdd(PathNode *parent, const AyStarNode *node, int f, int g) new_node->g = g; new_node->path.parent = parent; new_node->path.node = *node; - this->openlist_hash.Set(node->tile, node->direction, new_node); + this->openlist_hash[std::make_pair(node->tile, node->direction)] = new_node; /* Add it to the queue */ this->openlist_queue.Push(new_node, f); @@ -134,6 +140,7 @@ void AyStar::CheckTile(AyStarNode *current, OpenListNode *parent) /* Yes, check if this g value is lower.. */ if (new_g > check->g) return; this->openlist_queue.Delete(check, 0); + /* It is lower, so change it to this item */ check->g = new_g; check->path.parent = closedlist_parent; @@ -191,7 +198,7 @@ int AyStar::Loop() /* Free the node */ free(current); - if (this->max_search_nodes != 0 && this->closedlist_hash.GetSize() >= this->max_search_nodes) { + if (this->max_search_nodes != 0 && this->closedlist_hash.size() >= this->max_search_nodes) { /* We've expanded enough nodes */ return AYSTAR_LIMIT_REACHED; } else { @@ -208,8 +215,14 @@ void AyStar::Free() this->openlist_queue.Free(false); /* 2nd argument above is false, below is true, to free the values only * once */ - this->openlist_hash.Delete(true); - this->closedlist_hash.Delete(true); + for (const auto& pair : this->openlist_hash) { + free(pair.second); + } + this->openlist_hash.clear(); + for (const auto& pair : this->closedlist_hash) { + free(pair.second); + } + this->closedlist_hash.clear(); #ifdef AYSTAR_DEBUG printf("[AyStar] Memory free'd\n"); #endif @@ -225,8 +238,15 @@ void AyStar::Clear() * the hash. */ this->openlist_queue.Clear(false); /* Clean the hashes */ - this->openlist_hash.Clear(true); - this->closedlist_hash.Clear(true); + for (const auto& pair : this->openlist_hash) { + free(pair.second); + } + this->openlist_hash.clear(); + + for (const auto& pair : this->closedlist_hash) { + free(pair.second); + } + this->closedlist_hash.clear(); #ifdef AYSTAR_DEBUG printf("[AyStar] Cleared AyStar\n"); @@ -292,9 +312,12 @@ void AyStar::AddStartNode(AyStarNode *start_node, uint g) */ void AyStar::Init(Hash_HashProc hash, uint num_buckets) { + MemSetT(&neighbours, 0); + MemSetT(&openlist_queue, 0); + /* Allocated the Hash for the OpenList and ClosedList */ - this->openlist_hash.Init(hash, num_buckets); - this->closedlist_hash.Init(hash, num_buckets); + this->openlist_hash.reserve(num_buckets); + this->closedlist_hash.reserve(num_buckets); /* Set up our sorting queue * BinaryHeap allocates a block of 1024 nodes diff --git a/src/pathfinder/npf/aystar.h b/src/pathfinder/npf/aystar.h index cbbe8a4157..f3c02fd2a1 100644 --- a/src/pathfinder/npf/aystar.h +++ b/src/pathfinder/npf/aystar.h @@ -17,6 +17,9 @@ #define AYSTAR_H #include "queue.h" +#include +#include + #include "../../tile_type.h" #include "../../track_type.h" @@ -57,6 +60,15 @@ struct OpenListNode { PathNode path; }; +struct PairHash { +public: + template + std::size_t operator()(const std::pair &x) const + { + return std::hash()(x.first) ^ std::hash()(x.second); + } +}; + bool CheckIgnoreFirstTile(const PathNode *node); struct AyStar; @@ -156,9 +168,9 @@ struct AyStar { void CheckTile(AyStarNode *current, OpenListNode *parent); protected: - Hash closedlist_hash; ///< The actual closed list. + std::unordered_map, PathNode*, PairHash> closedlist_hash; BinaryHeap openlist_queue; ///< The open queue. - Hash openlist_hash; ///< An extra hash to speed up the process of looking up an element in the open list. + std::unordered_map, OpenListNode*, PairHash> openlist_hash; void OpenListAdd(PathNode *parent, const AyStarNode *node, int f, int g); OpenListNode *OpenListIsInList(const AyStarNode *node); From 9d6d41e4578073fd5493acbb3f3320a4f0b7e365 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Sat, 12 Jun 2021 20:12:07 +0200 Subject: [PATCH 04/45] Update src/lang/english.txt Co-authored-by: stormcone <48624099+stormcone@users.noreply.github.com> --- src/lang/english.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/english.txt b/src/lang/english.txt index 60af82c5de..1ab7354042 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1932,7 +1932,7 @@ STR_CONFIG_SETTING_TREES_AROUND_SNOWLINE_RANGE :Arctic tree ran STR_CONFIG_SETTING_TREES_AROUND_SNOWLINE_RANGE_HELPTEXT :Approximate range of arctic trees around snow line STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS :Build public roads connecting towns: {STRING2} -STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS_HELPTEXT :Generates public roads which connect the times. Takes a bit of time on bigger maps. 'Build and avoid' generates roads which avoid curves and result in very grid-like connections. +STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS_HELPTEXT :Generates public roads which connect the towns. Takes a bit of time on bigger maps. 'Build and avoid' generates roads which avoid curves and result in very grid-like connections. STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS_NONE :None (Default) STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS_WITH_CURVES :Build with curves STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS_AVOID_CURVES :Build and avoid curves From 4542410b4177573f486ad02ed1ad9363a7501ee1 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Sun, 13 Jun 2021 12:03:53 +0200 Subject: [PATCH 05/45] Revert "Replace original algorithm with a simpler one" This reverts commit 8cb3d80402f626034c08a162b8956dce03eb79f6. --- src/road.cpp | 157 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 98 insertions(+), 59 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index ca24e8421c..7e525d9a72 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -246,6 +246,8 @@ CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32 p1, u CommandCost CmdBuildTunnel(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text = nullptr); CommandCost CmdBuildRoad(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text = nullptr); +static std::vector _town_centers; +static std::vector _towns_visited_along_the_way; static bool _has_tunnel_in_path; static RoadType _public_road_type; static const uint _public_road_hash_size = 8U; ///< The number of bits the hash for river finding should have. @@ -261,15 +263,14 @@ static uint PublicRoad_Hash(uint tile, uint dir) return GB(TileHash(TileX(tile), TileY(tile)), 0, _public_road_hash_size); } -static const int32 _base_cost = 1; // Cost for utilizing an existing road, bridge, or tunnel. +static const int32 BASE_COST = 1; // Cost for utilizing an existing road, bridge, or tunnel. +static const int32 COST_FOR_NEW_ROAD = 10; // Cost for building a new road. +static const int32 COST_FOR_SLOPE = 5; // Additional cost if the road heads up or down a slope. /** AyStar callback for getting the cost of the current node. */ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode *parent) { - static const int32 COST_FOR_NEW_ROAD = 10; // Cost for building a new road. - static const int32 COST_FOR_SLOPE = 5; // Additional cost if the road heads up or down a slope. - - int32 cost = _base_cost; + int32 cost = BASE_COST; if (!IsTileType(current->tile, MP_ROAD)) { if (!AreTilesAdjacent(parent->path.node.tile, current->tile)) @@ -299,7 +300,7 @@ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode * /** AyStar callback for getting the estimated cost to the destination. */ static int32 PublicRoad_CalculateH(AyStar *aystar, AyStarNode *current, OpenListNode *parent) { - return DistanceManhattan(*static_cast(aystar->user_target), current->tile) * _base_cost; + return DistanceManhattan(*static_cast(aystar->user_target), current->tile) * BASE_COST; } /** Helper function to check if a tile along a certain direction is going up an inclined slope. */ @@ -632,6 +633,14 @@ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) /** AyStar callback for checking whether we reached our destination. */ static int32 PublicRoad_EndNodeCheck(const AyStar *aystar, const OpenListNode *current) { + // Mark towns visited along the way. + const auto search_result = + std::find(_town_centers.begin(), _town_centers.end(), current->path.node.tile); + + if (search_result != _town_centers.end()) { + _towns_visited_along_the_way.push_back(current->path.node.tile); + } + return current->path.node.tile == *static_cast(aystar->user_target) ? AYSTAR_FOUND_END_NODE : AYSTAR_DONE; } @@ -741,87 +750,117 @@ void GeneratePublicRoads() using namespace std; if (_settings_game.game_creation.build_public_roads == PRC_NONE) return; - - vector cities; - vector villages; - vector unconnected_villages; - _public_road_type = GetTownRoadType(*Town::Iterate().begin()); + _town_centers.clear(); + _towns_visited_along_the_way.clear(); - for (const Town *town : Town::Iterate()) { - if (town->larger_town) { - cities.push_back(town->xy); - } else { - villages.push_back(town->xy); + vector towns; + towns.clear(); + { + for (const Town *town : Town::Iterate()) { + towns.push_back(town->xy); + _town_centers.push_back(town->xy); } } - SetGeneratingWorldProgress(GWP_PUBLIC_ROADS, uint(cities.size() + villages.size())); + if (towns.empty()) { + return; + } + + SetGeneratingWorldProgress(GWP_PUBLIC_ROADS, uint(towns.size())); - // Try to connect every village to the nearest city. - for (auto village : villages) { - sort(cities.begin(), cities.end(), [&](auto a, auto b) { return DistanceManhattan(village, a) < DistanceManhattan(village, b); }); + // Create a list of networks which also contain a value indicating how many times we failed to connect to them. + vector>>> town_networks; + unordered_map>> towns_reachable_networks; - bool is_connected = false; - - for (auto city : cities) { - AyStar finder {}; + TileIndex main_town = *towns.begin(); + towns.erase(towns.begin()); - auto found_path = FindPath(finder, village, city); + _public_road_type = GetTownRoadType(Town::GetByTile(main_town)); - finder.Free(); + auto main_network = make_shared>(); + main_network->push_back(main_town); - if (found_path){ - is_connected = true; - break; - } - } + town_networks.emplace_back(0, main_network); + IncreaseGeneratingWorldProgress(GWP_PUBLIC_ROADS); - if (!is_connected) { - unconnected_villages.push_back(village); - } else { - IncreaseGeneratingWorldProgress(GWP_PUBLIC_ROADS); - } - } + sort(towns.begin(), towns.end(), [&](auto a, auto b) { return DistanceManhattan(main_town, a) < DistanceManhattan(main_town, b); }); + + for (auto begin_town : towns) { + // Check if we can connect to any of the networks. + _towns_visited_along_the_way.clear(); + + auto reachable_network_iter = towns_reachable_networks.find(begin_town); + bool found_easy_path = false; - vector villages_copy(unconnected_villages); + if (reachable_network_iter != towns_reachable_networks.end()) { + auto reachable_network = reachable_network_iter->second; - // If a village could not be connected to a city, try to connect it to another unconnected village. - for (auto village : unconnected_villages) { - sort(villages_copy.begin(), villages_copy.end(), [&](auto a, auto b) { return DistanceManhattan(village, a) < DistanceManhattan(village, b); }); + sort(reachable_network->begin(), reachable_network->end(), [&](auto a, auto b) { return DistanceManhattan(begin_town, a) < DistanceManhattan(begin_town, b); }); + + const TileIndex end_town = *reachable_network->begin(); - for (auto other_village : villages_copy) { AyStar finder {}; - auto found_path = FindPath(finder, village, other_village); + found_easy_path = FindPath(finder, begin_town, end_town); finder.Free(); + } - if (found_path) { - break; + if (found_easy_path) { + reachable_network_iter->second->push_back(begin_town); + + for (const TileIndex visited_town : _towns_visited_along_the_way) { + if (visited_town != begin_town) towns_reachable_networks[visited_town] = reachable_network_iter->second; } - } + } else { + // Sort networks by failed connection attempts, so we try the most likely one first. + sort(town_networks.begin(), town_networks.end(), [&](auto a, auto b) { return a.first < b.first; }); - IncreaseGeneratingWorldProgress(GWP_PUBLIC_ROADS); - } + std::function>>)> can_reach_network = [&](auto network_pair) { + AyStar finder {}; - vector cities_copy(cities); + auto network = network_pair.second; - // Connect the cities with each-other. - for (auto city : cities) { - sort(cities_copy.begin(), cities_copy.end(), [&](auto a, auto b) { return DistanceManhattan(city, a) < DistanceManhattan(city, b); }); + // Try to connect to the town in the network that is closest to us. + // If we can't connect to that one, we can't connect to any of them since they are all interconnected. + sort(network->begin(), network->end(), [&](auto a, auto b) { return DistanceManhattan(begin_town, a) < DistanceManhattan(begin_town, b); }); + const TileIndex end_town = *network->begin(); - for (auto other_city : cities_copy) { - if (other_city == city) continue; + const bool found_path = FindPath(finder, begin_town, end_town); - AyStar finder {}; + if (found_path) { + network->push_back(begin_town); + + for (auto visited_town : _towns_visited_along_the_way) { + if (visited_town != begin_town) towns_reachable_networks[visited_town] = network; + } + } - auto found_path = FindPath(finder, city, other_city); + // Increase number of failed attempts if necessary. + network_pair.first += (found_path ? (network_pair.first > 0 ? -1 : 0) : 1); - finder.Free(); + finder.Free(); - if (found_path) { - break; + return found_path; + + }; + + if (!any_of(town_networks.begin(), town_networks.end(), can_reach_network)) { + // We failed to connect to any network, so we are a separate network. Let future towns try to connect to us. + auto new_network = make_shared>(); + new_network->push_back(begin_town); + + // We basically failed to connect to this many towns. + int towns_already_in_networks = std::accumulate(town_networks.begin(), town_networks.end(), 0, [&](int accumulator, auto network_pair) { + return accumulator + static_cast(network_pair.second->size()); + }); + + town_networks.emplace_back(towns_already_in_networks, new_network); + + for (const TileIndex visited_town : _towns_visited_along_the_way) { + if (visited_town != begin_town) towns_reachable_networks.insert(make_pair(visited_town, new_network)); + } } } From 8d584990aae1d77f1d6f0b6c3b5221382b8131e6 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Sun, 13 Jun 2021 17:45:28 +0200 Subject: [PATCH 06/45] Remove unused hash functions and start the road building from the map center --- src/landscape.cpp | 13 +------------ src/pathfinder/npf/aystar.cpp | 2 +- src/pathfinder/npf/aystar.h | 2 +- src/pathfinder/npf/npf.cpp | 20 +------------------- src/road.cpp | 15 +++------------ 5 files changed, 7 insertions(+), 45 deletions(-) diff --git a/src/landscape.cpp b/src/landscape.cpp index 301e63472b..5456765855 100644 --- a/src/landscape.cpp +++ b/src/landscape.cpp @@ -1241,17 +1241,6 @@ static void River_FoundEndNode(AyStar *aystar, OpenListNode *current) static const uint RIVER_HASH_SIZE = 8; ///< The number of bits the hash for river finding should have. -/** - * Simple hash function for river tiles to be used by AyStar. - * @param tile The tile to hash. - * @param dir The unused direction. - * @return The hash for the tile. - */ -static uint River_Hash(uint tile, uint dir) -{ - return GB(TileHash(TileX(tile), TileY(tile)), 0, RIVER_HASH_SIZE); -} - /** * Actually build the river between the begin and end tiles using AyStar. * @param begin The begin of the river. @@ -1267,7 +1256,7 @@ static void BuildRiver(TileIndex begin, TileIndex end) finder.FoundEndNode = River_FoundEndNode; finder.user_target = &end; - finder.Init(River_Hash, 1 << RIVER_HASH_SIZE); + finder.Init(1 << RIVER_HASH_SIZE); AyStarNode start; start.tile = begin; diff --git a/src/pathfinder/npf/aystar.cpp b/src/pathfinder/npf/aystar.cpp index 5ac1071aff..eef470ebbe 100644 --- a/src/pathfinder/npf/aystar.cpp +++ b/src/pathfinder/npf/aystar.cpp @@ -310,7 +310,7 @@ void AyStar::AddStartNode(AyStarNode *start_node, uint g) * Initialize an #AyStar. You should fill all appropriate fields before * calling #Init (see the declaration of #AyStar for which fields are internal). */ -void AyStar::Init(Hash_HashProc hash, uint num_buckets) +void AyStar::Init(uint num_buckets) { MemSetT(&neighbours, 0); MemSetT(&openlist_queue, 0); diff --git a/src/pathfinder/npf/aystar.h b/src/pathfinder/npf/aystar.h index f3c02fd2a1..f8cb187c83 100644 --- a/src/pathfinder/npf/aystar.h +++ b/src/pathfinder/npf/aystar.h @@ -156,7 +156,7 @@ struct AyStar { AyStarNode neighbours[12]; byte num_neighbours; - void Init(Hash_HashProc hash, uint num_buckets); + void Init(uint num_buckets); /* These will contain the methods for manipulating the AyStar. Only * Main() should be called externally */ diff --git a/src/pathfinder/npf/npf.cpp b/src/pathfinder/npf/npf.cpp index 0f063dee9f..738aef3461 100644 --- a/src/pathfinder/npf/npf.cpp +++ b/src/pathfinder/npf/npf.cpp @@ -131,24 +131,6 @@ static uint NPFDistanceTrack(TileIndex t0, TileIndex t1) return diagTracks * NPF_TILE_LENGTH + straightTracks * NPF_TILE_LENGTH * STRAIGHT_TRACK_LENGTH; } -/** - * Calculates a hash value for use in the NPF. - * @param key1 The TileIndex of the tile to hash - * @param key2 The Trackdir of the track on the tile. - * - * @todo Think of a better hash. - */ -static uint NPFHash(uint key1, uint key2) -{ - /* TODO: think of a better hash? */ - uint part1 = TileX(key1) & NPF_HASH_HALFMASK; - uint part2 = TileY(key1) & NPF_HASH_HALFMASK; - - assert(IsValidTrackdir((Trackdir)key2)); - assert(IsValidTile(key1)); - return ((part1 << NPF_HASH_HALFBITS | part2) + (NPF_HASH_SIZE * key2 / TRACKDIR_END)) % NPF_HASH_SIZE; -} - static int32 NPFCalcZero(AyStar *as, AyStarNode *current, OpenListNode *parent) { return 0; @@ -1127,7 +1109,7 @@ void InitializeNPF() static bool first_init = true; if (first_init) { first_init = false; - _npf_aystar.Init(NPFHash, NPF_HASH_SIZE); + _npf_aystar.Init(NPF_HASH_SIZE); } else { _npf_aystar.Clear(); } diff --git a/src/road.cpp b/src/road.cpp index 7e525d9a72..a34386791d 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -252,17 +252,6 @@ static bool _has_tunnel_in_path; static RoadType _public_road_type; static const uint _public_road_hash_size = 8U; ///< The number of bits the hash for river finding should have. -/** -* Simple hash function for public road tiles to be used by AyStar. -* @param tile The tile to hash. -* @param dir The unused direction. -* @return The hash for the tile. -*/ -static uint PublicRoad_Hash(uint tile, uint dir) -{ - return GB(TileHash(TileX(tile), TileY(tile)), 0, _public_road_hash_size); -} - static const int32 BASE_COST = 1; // Cost for utilizing an existing road, bridge, or tunnel. static const int32 COST_FOR_NEW_ROAD = 10; // Cost for building a new road. static const int32 COST_FOR_SLOPE = 5; // Additional cost if the road heads up or down a slope. @@ -722,7 +711,7 @@ bool FindPath(AyStar& finder, const TileIndex from, TileIndex to) finder.user_target = &(to); finder.max_search_nodes = 1 << 20; // 1,048,576 - finder.Init(PublicRoad_Hash, 1 << _public_road_hash_size); + finder.Init(1 << _public_road_hash_size); _has_tunnel_in_path = false; @@ -773,6 +762,8 @@ void GeneratePublicRoads() vector>>> town_networks; unordered_map>> towns_reachable_networks; + sort(towns.begin(), towns.end(), [&](auto a, auto b) { return DistanceFromEdge(a) > DistanceFromEdge(b); }); + TileIndex main_town = *towns.begin(); towns.erase(towns.begin()); From af29085e4228ae651ebf7f258463d353202767db Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Sun, 13 Jun 2021 22:09:14 +0200 Subject: [PATCH 07/45] Only build bridges over water --- src/road.cpp | 125 +++++++++++++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 54 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index a34386791d..74ec3c0230 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -367,13 +367,19 @@ static TileIndex BuildTunnel(PathNode *current, TileIndex end_tile = INVALID_TIL return end_tile; } -static TileIndex BuildBridge(PathNode *current, TileIndex end_tile = INVALID_TILE, const bool build_bridge = false) +static TileIndex BuildBridge(PathNode *current, const DiagDirection road_direction, TileIndex end_tile = INVALID_TILE, const bool build_bridge = false) { const TileIndex start_tile = current->node.tile; - const DiagDirection direction = ReverseDiagDir(GetInclinedSlopeDirection(GetTileSlope(start_tile))); - + // We are not building yet, so we still need to find the end_tile. + // We will only build a bridge if we need to cross a river, so first check for that. if (!build_bridge) { + const TileIndex tile = start_tile + TileOffsByDiagDir(road_direction); + + if (!IsWaterTile(tile) || !IsRiver(tile)) return INVALID_TILE; + + const DiagDirection direction = ReverseDiagDir(GetInclinedSlopeDirection(GetTileSlope(start_tile))); + // We are not building yet, so we still need to find the end_tile. for (TileIndex tile = start_tile + TileOffsByDiagDir(direction); IsValidTile(tile) && @@ -589,7 +595,7 @@ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) } } else if (IsDownwardsSlope(tile, road_direction)) { - const auto bridge_end = BuildBridge(¤t->path); + const auto bridge_end = BuildBridge(¤t->path, road_direction); if (bridge_end != INVALID_TILE && !IsSteepSlope(GetTileSlope(bridge_end)) && @@ -604,7 +610,7 @@ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) else if (GetTileSlope(tile) == SLOPE_FLAT) { // Check if we could bridge a river from a flat tile. Not looking pretty on the map but you gotta do what you gotta do. - const auto bridge_end = BuildRiverBridge(¤t->path, DiagdirBetweenTiles(current->path.parent->node.tile, tile)); + const auto bridge_end = BuildRiverBridge(¤t->path, road_direction); assert(bridge_end == INVALID_TILE || GetTileSlope(bridge_end) == SLOPE_FLAT); if (bridge_end != INVALID_TILE) { @@ -687,7 +693,7 @@ static void PublicRoad_FoundEndNode(AyStar *aystar, OpenListNode *current) assert(IsValidTile(end_tile) && IsDownwardsSlope(end_tile, road_direction)); } else if (IsDownwardsSlope(tile, road_direction)) { // Provide the function with the end tile, since we already know it, but still check the result. - end_tile = BuildBridge(path, path->parent->node.tile, true); + end_tile = BuildBridge(path, road_direction, path->parent->node.tile, true); assert(IsValidTile(end_tile) && IsUpwardsSlope(end_tile, road_direction)); } else { // River bridge is the last possibility. @@ -731,6 +737,12 @@ bool FindPath(AyStar& finder, const TileIndex from, TileIndex to) return found_path; } +struct TownNetwork +{ + uint failures_to_connect {}; + std::vector towns; +}; + /** * Build the public road network connecting towns using AyStar. */ @@ -759,8 +771,8 @@ void GeneratePublicRoads() SetGeneratingWorldProgress(GWP_PUBLIC_ROADS, uint(towns.size())); // Create a list of networks which also contain a value indicating how many times we failed to connect to them. - vector>>> town_networks; - unordered_map>> towns_reachable_networks; + vector> networks; + unordered_map> town_to_network_map; sort(towns.begin(), towns.end(), [&](auto a, auto b) { return DistanceFromEdge(a) > DistanceFromEdge(b); }); @@ -769,88 +781,93 @@ void GeneratePublicRoads() _public_road_type = GetTownRoadType(Town::GetByTile(main_town)); - auto main_network = make_shared>(); - main_network->push_back(main_town); + auto main_network = make_shared(); + main_network->towns.push_back(main_town); + + networks.push_back(main_network); + town_to_network_map[main_town] = main_network; - town_networks.emplace_back(0, main_network); IncreaseGeneratingWorldProgress(GWP_PUBLIC_ROADS); sort(towns.begin(), towns.end(), [&](auto a, auto b) { return DistanceManhattan(main_town, a) < DistanceManhattan(main_town, b); }); - for (auto begin_town : towns) { + for (auto start_town : towns) { // Check if we can connect to any of the networks. _towns_visited_along_the_way.clear(); - auto reachable_network_iter = towns_reachable_networks.find(begin_town); - bool found_easy_path = false; + auto reachable_from_town = town_to_network_map.find(start_town); + bool found_path = false; - if (reachable_network_iter != towns_reachable_networks.end()) { - auto reachable_network = reachable_network_iter->second; + if (reachable_from_town != town_to_network_map.end()) { + auto reachable_network = reachable_from_town->second; - sort(reachable_network->begin(), reachable_network->end(), [&](auto a, auto b) { return DistanceManhattan(begin_town, a) < DistanceManhattan(begin_town, b); }); + sort(reachable_network->towns.begin(), reachable_network->towns.end(), [&](auto a, auto b) { return DistanceManhattan(start_town, a) < DistanceManhattan(start_town, b); }); - const TileIndex end_town = *reachable_network->begin(); + const TileIndex end_town = *reachable_network->towns.begin(); AyStar finder {}; - - found_easy_path = FindPath(finder, begin_town, end_town); - + { + found_path = FindPath(finder, start_town, end_town); + } + auto num_visited_nodes = finder.closedlist_hash.size(); finder.Free(); - } - if (found_easy_path) { - reachable_network_iter->second->push_back(begin_town); + if (found_path) { + reachable_network->towns.push_back(start_town); - for (const TileIndex visited_town : _towns_visited_along_the_way) { - if (visited_town != begin_town) towns_reachable_networks[visited_town] = reachable_network_iter->second; + for (const TileIndex visited_town : _towns_visited_along_the_way) { + town_to_network_map[visited_town] = reachable_network; + } + + } else { + town_to_network_map.erase(reachable_from_town); + reachable_network->failures_to_connect++; } - } else { - // Sort networks by failed connection attempts, so we try the most likely one first. - sort(town_networks.begin(), town_networks.end(), [&](auto a, auto b) { return a.first < b.first; }); - - std::function>>)> can_reach_network = [&](auto network_pair) { - AyStar finder {}; + } - auto network = network_pair.second; + if (!found_path) { + // Sort networks by failed connection attempts, so we try the most likely one first. + sort(networks.begin(), networks.end(), [](const std::shared_ptr &a, const std::shared_ptr &b) { return a->failures_to_connect < b->failures_to_connect; }); + std::function can_reach = [&](const std::shared_ptr &network) { // Try to connect to the town in the network that is closest to us. // If we can't connect to that one, we can't connect to any of them since they are all interconnected. - sort(network->begin(), network->end(), [&](auto a, auto b) { return DistanceManhattan(begin_town, a) < DistanceManhattan(begin_town, b); }); - const TileIndex end_town = *network->begin(); + sort(network->towns.begin(), network->towns.end(), [&](auto a, auto b) { return DistanceManhattan(start_town, a) < DistanceManhattan(start_town, b); }); + const TileIndex end_town = *network->towns.begin(); - const bool found_path = FindPath(finder, begin_town, end_town); + AyStar finder {}; + { + found_path = FindPath(finder, start_town, end_town); + } + auto num_visited_nodes = finder.closedlist_hash.size(); + finder.Free(); if (found_path) { - network->push_back(begin_town); - - for (auto visited_town : _towns_visited_along_the_way) { - if (visited_town != begin_town) towns_reachable_networks[visited_town] = network; - } + network->towns.push_back(start_town); + town_to_network_map[start_town] = network; + } else { + network->failures_to_connect++; } - // Increase number of failed attempts if necessary. - network_pair.first += (found_path ? (network_pair.first > 0 ? -1 : 0) : 1); - - finder.Free(); - return found_path; - }; - if (!any_of(town_networks.begin(), town_networks.end(), can_reach_network)) { + if (!any_of(networks.begin(), networks.end(), can_reach)) { // We failed to connect to any network, so we are a separate network. Let future towns try to connect to us. - auto new_network = make_shared>(); - new_network->push_back(begin_town); + auto new_network = make_shared(); + new_network->towns.push_back(start_town); // We basically failed to connect to this many towns. - int towns_already_in_networks = std::accumulate(town_networks.begin(), town_networks.end(), 0, [&](int accumulator, auto network_pair) { - return accumulator + static_cast(network_pair.second->size()); + int towns_already_in_networks = std::accumulate(networks.begin(), networks.end(), 0, [&](int accumulator, const std::shared_ptr &network) { + return accumulator + static_cast(network->towns.size()); }); - town_networks.emplace_back(towns_already_in_networks, new_network); + new_network->failures_to_connect++; + town_to_network_map[start_town] = new_network; + networks.push_back(new_network); for (const TileIndex visited_town : _towns_visited_along_the_way) { - if (visited_town != begin_town) towns_reachable_networks.insert(make_pair(visited_town, new_network)); + town_to_network_map[visited_town] = new_network; } } } From 953be433013506d5542e6a5e0572c449a9b159fc Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Sun, 13 Jun 2021 22:28:13 +0200 Subject: [PATCH 08/45] Prevent numeric overflow --- src/road.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index 74ec3c0230..1551453a66 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -783,6 +783,7 @@ void GeneratePublicRoads() auto main_network = make_shared(); main_network->towns.push_back(main_town); + main_network->failures_to_connect = 0; networks.push_back(main_network); town_to_network_map[main_town] = main_network; @@ -809,11 +810,13 @@ void GeneratePublicRoads() { found_path = FindPath(finder, start_town, end_town); } - auto num_visited_nodes = finder.closedlist_hash.size(); finder.Free(); if (found_path) { reachable_network->towns.push_back(start_town); + if (reachable_network->failures_to_connect > 0) { + reachable_network->failures_to_connect--; + } for (const TileIndex visited_town : _towns_visited_along_the_way) { town_to_network_map[visited_town] = reachable_network; @@ -839,11 +842,13 @@ void GeneratePublicRoads() { found_path = FindPath(finder, start_town, end_town); } - auto num_visited_nodes = finder.closedlist_hash.size(); finder.Free(); if (found_path) { network->towns.push_back(start_town); + if (network->failures_to_connect > 0) { + network->failures_to_connect--; + } town_to_network_map[start_town] = network; } else { network->failures_to_connect++; @@ -856,13 +861,14 @@ void GeneratePublicRoads() // We failed to connect to any network, so we are a separate network. Let future towns try to connect to us. auto new_network = make_shared(); new_network->towns.push_back(start_town); + new_network->failures_to_connect = 0; // We basically failed to connect to this many towns. int towns_already_in_networks = std::accumulate(networks.begin(), networks.end(), 0, [&](int accumulator, const std::shared_ptr &network) { return accumulator + static_cast(network->towns.size()); }); - new_network->failures_to_connect++; + new_network->failures_to_connect += towns_already_in_networks; town_to_network_map[start_town] = new_network; networks.push_back(new_network); From b3044cc482b3114b0a428a0e8b320b880d9c04a2 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Sun, 13 Jun 2021 23:47:12 +0200 Subject: [PATCH 09/45] Improve performance --- src/road.cpp | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index 1551453a66..2abf1375f3 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "rail_map.h" #include "road_map.h" @@ -252,14 +253,13 @@ static bool _has_tunnel_in_path; static RoadType _public_road_type; static const uint _public_road_hash_size = 8U; ///< The number of bits the hash for river finding should have. -static const int32 BASE_COST = 1; // Cost for utilizing an existing road, bridge, or tunnel. -static const int32 COST_FOR_NEW_ROAD = 10; // Cost for building a new road. -static const int32 COST_FOR_SLOPE = 5; // Additional cost if the road heads up or down a slope. +static const int32 COST_FOR_NEW_ROAD = 100; // Cost for building a new road. +static const int32 COST_FOR_SLOPE = 50; // Additional cost if the road heads up or down a slope. /** AyStar callback for getting the cost of the current node. */ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode *parent) { - int32 cost = BASE_COST; + int32 cost = 0; if (!IsTileType(current->tile, MP_ROAD)) { if (!AreTilesAdjacent(parent->path.node.tile, current->tile)) @@ -289,7 +289,7 @@ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode * /** AyStar callback for getting the estimated cost to the destination. */ static int32 PublicRoad_CalculateH(AyStar *aystar, AyStarNode *current, OpenListNode *parent) { - return DistanceManhattan(*static_cast(aystar->user_target), current->tile) * BASE_COST; + return DistanceManhattan(*static_cast(aystar->user_target), current->tile) * COST_FOR_NEW_ROAD; } /** Helper function to check if a tile along a certain direction is going up an inclined slope. */ @@ -715,7 +715,8 @@ bool FindPath(AyStar& finder, const TileIndex from, TileIndex to) finder.EndNodeCheck = PublicRoad_EndNodeCheck; finder.FoundEndNode = PublicRoad_FoundEndNode; finder.user_target = &(to); - finder.max_search_nodes = 1 << 20; // 1,048,576 + finder.max_search_nodes = 1 << 18; // 1,048,576 + finder.max_path_cost = 1000 * COST_FOR_NEW_ROAD; finder.Init(1 << _public_road_hash_size); @@ -780,6 +781,7 @@ void GeneratePublicRoads() towns.erase(towns.begin()); _public_road_type = GetTownRoadType(Town::GetByTile(main_town)); + std::unordered_set checked_towns; auto main_network = make_shared(); main_network->towns.push_back(main_town); @@ -796,6 +798,8 @@ void GeneratePublicRoads() // Check if we can connect to any of the networks. _towns_visited_along_the_way.clear(); + checked_towns.clear(); + auto reachable_from_town = town_to_network_map.find(start_town); bool found_path = false; @@ -805,6 +809,7 @@ void GeneratePublicRoads() sort(reachable_network->towns.begin(), reachable_network->towns.end(), [&](auto a, auto b) { return DistanceManhattan(start_town, a) < DistanceManhattan(start_town, b); }); const TileIndex end_town = *reachable_network->towns.begin(); + checked_towns.emplace(end_town); AyStar finder {}; { @@ -833,11 +838,21 @@ void GeneratePublicRoads() sort(networks.begin(), networks.end(), [](const std::shared_ptr &a, const std::shared_ptr &b) { return a->failures_to_connect < b->failures_to_connect; }); std::function can_reach = [&](const std::shared_ptr &network) { + if (reachable_from_town != town_to_network_map.end() && network.get() == reachable_from_town->second.get()) { + return false; + } + // Try to connect to the town in the network that is closest to us. // If we can't connect to that one, we can't connect to any of them since they are all interconnected. sort(network->towns.begin(), network->towns.end(), [&](auto a, auto b) { return DistanceManhattan(start_town, a) < DistanceManhattan(start_town, b); }); const TileIndex end_town = *network->towns.begin(); + if (checked_towns.find(end_town) != checked_towns.end()/* || DistanceManhattan(start_town, end_town) > 2000*/) { + return false; + } + + checked_towns.emplace(end_town); + AyStar finder {}; { found_path = FindPath(finder, start_town, end_town); From b89afbdff3ef05587f0bf10bd2f96d3c4b241ada Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Tue, 15 Jun 2021 00:42:38 +0200 Subject: [PATCH 10/45] Reintroduce base cost to make A* admissible --- src/road.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index 2abf1375f3..4f0e10f417 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -253,13 +253,14 @@ static bool _has_tunnel_in_path; static RoadType _public_road_type; static const uint _public_road_hash_size = 8U; ///< The number of bits the hash for river finding should have. +static const int32 BASE_COST_PER_TILE = 1; // Cost for building a new road. static const int32 COST_FOR_NEW_ROAD = 100; // Cost for building a new road. static const int32 COST_FOR_SLOPE = 50; // Additional cost if the road heads up or down a slope. /** AyStar callback for getting the cost of the current node. */ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode *parent) { - int32 cost = 0; + int32 cost = BASE_COST_PER_TILE; if (!IsTileType(current->tile, MP_ROAD)) { if (!AreTilesAdjacent(parent->path.node.tile, current->tile)) @@ -289,7 +290,7 @@ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode * /** AyStar callback for getting the estimated cost to the destination. */ static int32 PublicRoad_CalculateH(AyStar *aystar, AyStarNode *current, OpenListNode *parent) { - return DistanceManhattan(*static_cast(aystar->user_target), current->tile) * COST_FOR_NEW_ROAD; + return DistanceManhattan(*static_cast(aystar->user_target), current->tile) * BASE_COST_PER_TILE; } /** Helper function to check if a tile along a certain direction is going up an inclined slope. */ From 7b7b74e21ef6d76d8e9d1dce4db773c565b22d56 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Tue, 15 Jun 2021 11:30:57 +0200 Subject: [PATCH 11/45] Adjust values --- src/road.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index 4f0e10f417..3dc45198d5 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -254,8 +254,8 @@ static RoadType _public_road_type; static const uint _public_road_hash_size = 8U; ///< The number of bits the hash for river finding should have. static const int32 BASE_COST_PER_TILE = 1; // Cost for building a new road. -static const int32 COST_FOR_NEW_ROAD = 100; // Cost for building a new road. -static const int32 COST_FOR_SLOPE = 50; // Additional cost if the road heads up or down a slope. +static const int32 COST_FOR_NEW_ROAD = 1000; // Cost for building a new road. +static const int32 COST_FOR_SLOPE = 500; // Additional cost if the road heads up or down a slope. /** AyStar callback for getting the cost of the current node. */ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode *parent) @@ -716,8 +716,7 @@ bool FindPath(AyStar& finder, const TileIndex from, TileIndex to) finder.EndNodeCheck = PublicRoad_EndNodeCheck; finder.FoundEndNode = PublicRoad_FoundEndNode; finder.user_target = &(to); - finder.max_search_nodes = 1 << 18; // 1,048,576 - finder.max_path_cost = 1000 * COST_FOR_NEW_ROAD; + finder.max_search_nodes = 1 << 20; finder.Init(1 << _public_road_hash_size); @@ -848,7 +847,7 @@ void GeneratePublicRoads() sort(network->towns.begin(), network->towns.end(), [&](auto a, auto b) { return DistanceManhattan(start_town, a) < DistanceManhattan(start_town, b); }); const TileIndex end_town = *network->towns.begin(); - if (checked_towns.find(end_town) != checked_towns.end()/* || DistanceManhattan(start_town, end_town) > 2000*/) { + if (checked_towns.find(end_town) != checked_towns.end()) { return false; } From 1899b1877d392f68d5303eddd7ccb1c52e4d8842 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Tue, 15 Jun 2021 12:22:44 +0200 Subject: [PATCH 12/45] Improve performance even more --- src/pathfinder/npf/aystar.cpp | 2 +- src/road.cpp | 38 ++++++++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/pathfinder/npf/aystar.cpp b/src/pathfinder/npf/aystar.cpp index eef470ebbe..e68146bbde 100644 --- a/src/pathfinder/npf/aystar.cpp +++ b/src/pathfinder/npf/aystar.cpp @@ -138,7 +138,7 @@ void AyStar::CheckTile(AyStarNode *current, OpenListNode *parent) if (check != nullptr) { uint i; /* Yes, check if this g value is lower.. */ - if (new_g > check->g) return; + if (new_g >= check->g) return; this->openlist_queue.Delete(check, 0); /* It is lower, so change it to this item */ diff --git a/src/road.cpp b/src/road.cpp index 3dc45198d5..3ff0242e18 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -254,8 +254,8 @@ static RoadType _public_road_type; static const uint _public_road_hash_size = 8U; ///< The number of bits the hash for river finding should have. static const int32 BASE_COST_PER_TILE = 1; // Cost for building a new road. -static const int32 COST_FOR_NEW_ROAD = 1000; // Cost for building a new road. -static const int32 COST_FOR_SLOPE = 500; // Additional cost if the road heads up or down a slope. +static const int32 COST_FOR_NEW_ROAD = 100; // Cost for building a new road. +static const int32 COST_FOR_SLOPE = 50; // Additional cost if the road heads up or down a slope. /** AyStar callback for getting the cost of the current node. */ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode *parent) @@ -792,9 +792,17 @@ void GeneratePublicRoads() IncreaseGeneratingWorldProgress(GWP_PUBLIC_ROADS); - sort(towns.begin(), towns.end(), [&](auto a, auto b) { return DistanceManhattan(main_town, a) < DistanceManhattan(main_town, b); }); + auto town_network_distance = [](const TileIndex town, const std::shared_ptr &network) { + const auto min_element = std::min_element(network->towns.begin(), network->towns.end(), [&](const TileIndex town_a, const TileIndex town_b) { + return DistanceManhattan(town, town_a) < DistanceManhattan(town, town_b); + }); - for (auto start_town : towns) { + return DistanceManhattan(*min_element, town); + }; + + sort(towns.begin(), towns.end(), [&](auto a, auto b) { return DistanceManhattan(a, main_town) < DistanceManhattan(b, main_town); }); + + for (auto start_town : towns) { // Check if we can connect to any of the networks. _towns_visited_along_the_way.clear(); @@ -835,7 +843,9 @@ void GeneratePublicRoads() if (!found_path) { // Sort networks by failed connection attempts, so we try the most likely one first. - sort(networks.begin(), networks.end(), [](const std::shared_ptr &a, const std::shared_ptr &b) { return a->failures_to_connect < b->failures_to_connect; }); + sort(networks.begin(), networks.end(), [&](const std::shared_ptr &a, const std::shared_ptr &b) { + return town_network_distance(start_town, a) < town_network_distance(start_town, b); + }); std::function can_reach = [&](const std::shared_ptr &network) { if (reachable_from_town != town_to_network_map.end() && network.get() == reachable_from_town->second.get()) { @@ -872,8 +882,22 @@ void GeneratePublicRoads() return found_path; }; - if (!any_of(networks.begin(), networks.end(), can_reach)) { - // We failed to connect to any network, so we are a separate network. Let future towns try to connect to us. + vector>::iterator networks_end; + + if (networks.size() > 5) { + networks_end = networks.begin() + 5; + } else { + networks_end = networks.end(); + } + + vector> sampled_networks; + std::copy(networks.begin(), networks_end, std::back_inserter(sampled_networks)); + sort(sampled_networks.begin(), sampled_networks.end(), [&](const std::shared_ptr &a, const std::shared_ptr &b) { + return a->failures_to_connect < b->failures_to_connect; + }); + + if (!any_of(sampled_networks.begin(), sampled_networks.end(), can_reach)) { + // We failed so many networks, we are a separate network. Let future towns try to connect to us. auto new_network = make_shared(); new_network->towns.push_back(start_town); new_network->failures_to_connect = 0; From a1df69be8737abfe27d74d4f34ade98e6d0a25b5 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Tue, 15 Jun 2021 14:07:39 +0200 Subject: [PATCH 13/45] Check for overlaps and intersections of planned bridges and tunnels --- src/road.cpp | 84 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 6 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index 3ff0242e18..3efc8ae32b 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -249,7 +249,6 @@ CommandCost CmdBuildRoad(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 static std::vector _town_centers; static std::vector _towns_visited_along_the_way; -static bool _has_tunnel_in_path; static RoadType _public_road_type; static const uint _public_road_hash_size = 8U; ///< The number of bits the hash for river finding should have. @@ -527,6 +526,79 @@ static bool IsValidNeighbourOfPreviousTile(const TileIndex tile, const TileIndex return true; } +static bool AreParallelOverlapping(const Point &start_a, const Point &end_a, const Point &start_b, const Point &end_b) +{ + // Check parallel overlaps. + if (start_a.x == end_a.x && start_b.x == end_b.x && start_a.x == start_b.x) { + if ((start_a.y <= start_b.y && end_a.y >= start_b.y) || (start_a.y >= start_b.y && end_a.y <= start_b.y) || + (start_a.y <= end_b.y && end_a.y >= end_b.y) || (start_a.y >= end_b.y && end_a.y <= end_b.y)) { + return true; + } + } + + if (start_a.y == end_a.y && start_b.y == end_b.y && start_a.y == start_b.y) { + if ((start_a.x <= start_b.x && end_a.x >= start_b.x) || (start_a.x >= start_b.x && end_a.x <= start_b.x) || + (start_a.x <= end_b.x && end_a.x >= end_b.x) || (start_a.x >= end_b.x && end_a.x <= end_b.x)) { + return true; + } + } + + return false; +} + +static bool AreIntersecting(const Point &start_a, const Point &end_a, const Point &start_b, const Point &end_b) +{ + if (start_a.x == end_a.x && start_b.y == end_b.y) { + if ((start_b.x <= start_a.x && end_b.x >= start_a.x) || (start_b.x >= start_a.x && end_b.x <= start_a.x)) { + if ((start_a.y <= start_b.y && end_a.y >= start_b.y) || (start_a.y >= start_b.y && end_a.y <= start_b.y)) { + return true; + } + } + } + + if (start_a.y == end_a.y && start_b.x == end_b.x) { + if ((start_b.y <= start_a.y && end_b.y >= start_a.y) || (start_b.y >= start_a.y && end_b.y <= start_a.y)) { + if ((start_a.x <= start_b.x && end_a.x >= start_b.x) || (start_a.x >= start_b.x && end_a.x <= start_b.x)) { + return true; + } + } + } + + return false; +} + +static bool IsBlockedByPreviousBridgeOrTunnel(OpenListNode *current, TileIndex start_tile, TileIndex end_tile) +{ + PathNode* start = ¤t->path; + PathNode* end = current->path.parent; + + while (end != nullptr) { + Point start_a {}; + start_a.x = TileX(start->node.tile); + start_a.y = TileY(start->node.tile); + Point end_a {}; + end_a.x = TileX(end->node.tile); + end_a.y = TileY(end->node.tile); + + Point start_b {}; + start_b.x = TileX(start_tile); + start_b.y = TileY(start_tile); + Point end_b {}; + end_b.x = TileX(end_tile); + end_b.y = TileY(end_tile); + + if (!AreTilesAdjacent(start->node.tile, end->node.tile) && + (AreIntersecting(start_a, end_a, start_b, end_b) || AreParallelOverlapping(start_a, end_a, start_b, end_b))) { + return true; + } + + start = end; + end = start->parent; + } + + return false; +} + /** AyStar callback for getting the neighbouring nodes of the given node. */ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) { @@ -581,10 +653,11 @@ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) if (current->path.parent != nullptr) { const auto road_direction = DiagdirBetweenTiles(current->path.parent->node.tile, tile); - if (IsUpwardsSlope(tile, road_direction) && !_has_tunnel_in_path) { + if (IsUpwardsSlope(tile, road_direction)) { const auto tunnel_end = BuildTunnel(¤t->path); if (tunnel_end != INVALID_TILE && + !IsBlockedByPreviousBridgeOrTunnel(current, current->path.node.tile, tunnel_end) && !IsSteepSlope(GetTileSlope(tunnel_end)) && !IsHalftileSlope(GetTileSlope(tunnel_end)) && (GetTileSlope(tunnel_end) == ComplementSlope(GetTileSlope(current->path.node.tile)))) { @@ -592,13 +665,13 @@ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) aystar->neighbours[aystar->num_neighbours].tile = tunnel_end; aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; aystar->num_neighbours++; - _has_tunnel_in_path = true; } } else if (IsDownwardsSlope(tile, road_direction)) { const auto bridge_end = BuildBridge(¤t->path, road_direction); if (bridge_end != INVALID_TILE && + !IsBlockedByPreviousBridgeOrTunnel(current, current->path.node.tile, bridge_end) && !IsSteepSlope(GetTileSlope(bridge_end)) && !IsHalftileSlope(GetTileSlope(bridge_end)) && (GetTileSlope(bridge_end) == ComplementSlope(GetTileSlope(current->path.node.tile)))) { @@ -614,7 +687,8 @@ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) const auto bridge_end = BuildRiverBridge(¤t->path, road_direction); assert(bridge_end == INVALID_TILE || GetTileSlope(bridge_end) == SLOPE_FLAT); - if (bridge_end != INVALID_TILE) { + if (bridge_end != INVALID_TILE && + !IsBlockedByPreviousBridgeOrTunnel(current, current->path.node.tile, bridge_end)) { assert(IsValidDiagDirection(DiagdirBetweenTiles(tile, bridge_end))); aystar->neighbours[aystar->num_neighbours].tile = bridge_end; aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; @@ -719,8 +793,6 @@ bool FindPath(AyStar& finder, const TileIndex from, TileIndex to) finder.max_search_nodes = 1 << 20; finder.Init(1 << _public_road_hash_size); - - _has_tunnel_in_path = false; AyStarNode start {}; start.tile = from; From 44fe1ca00b46fc88c70e685233b5effa6fbdc358 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Tue, 15 Jun 2021 14:39:21 +0200 Subject: [PATCH 14/45] Tweak the bridge generation --- src/landscape.cpp | 7 ------- src/road.cpp | 16 ++++++---------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/landscape.cpp b/src/landscape.cpp index 5456765855..457ca84840 100644 --- a/src/landscape.cpp +++ b/src/landscape.cpp @@ -1209,13 +1209,6 @@ static bool RiverMakeWider(TileIndex tile, void *data) /* AyStar callback when an route has been found. */ static void River_FoundEndNode(AyStar *aystar, OpenListNode *current) { - /* Count river length. */ - uint length = 0; - - for (PathNode *path = ¤t->path; path != nullptr; path = path->parent) { - length++; - } - uint cur_pos = 0; for (PathNode *path = ¤t->path; path != nullptr; path = path->parent, cur_pos++) { TileIndex tile = path->node.tile; diff --git a/src/road.cpp b/src/road.cpp index 3efc8ae32b..e7888029ed 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -367,23 +367,19 @@ static TileIndex BuildTunnel(PathNode *current, TileIndex end_tile = INVALID_TIL return end_tile; } -static TileIndex BuildBridge(PathNode *current, const DiagDirection road_direction, TileIndex end_tile = INVALID_TILE, const bool build_bridge = false) +static TileIndex BuildBridge(PathNode *current, TileIndex end_tile = INVALID_TILE, const bool build_bridge = false) { const TileIndex start_tile = current->node.tile; // We are not building yet, so we still need to find the end_tile. // We will only build a bridge if we need to cross a river, so first check for that. if (!build_bridge) { - const TileIndex tile = start_tile + TileOffsByDiagDir(road_direction); - - if (!IsWaterTile(tile) || !IsRiver(tile)) return INVALID_TILE; - const DiagDirection direction = ReverseDiagDir(GetInclinedSlopeDirection(GetTileSlope(start_tile))); // We are not building yet, so we still need to find the end_tile. for (TileIndex tile = start_tile + TileOffsByDiagDir(direction); IsValidTile(tile) && - (GetTunnelBridgeLength(start_tile, tile) <= _settings_game.construction.max_bridge_length) && + (GetTunnelBridgeLength(start_tile, tile) <= std::min(_settings_game.construction.max_bridge_length, (uint16)10)) && (GetTileZ(start_tile) < (GetTileZ(tile) + _settings_game.construction.max_bridge_height)) && (GetTileZ(tile) <= GetTileZ(start_tile)); tile += TileOffsByDiagDir(direction)) { @@ -421,7 +417,7 @@ static TileIndex BuildBridge(PathNode *current, const DiagDirection road_directi const auto bridge_type = available_bridge_types[build_bridge ? RandomRange(uint32(available_bridge_types.size())) : 0]; Backup cur_company(_current_company, OWNER_DEITY, FILE_LINE); - const auto build_bridge_cmd = CmdBuildBridge(end_tile, build_bridge ? DC_EXEC : DC_NONE, start_tile, bridge_type | (ROADTYPE_ROAD << 8) | (TRANSPORT_ROAD << 15)); + const auto build_bridge_cmd = CmdBuildBridge(end_tile, build_bridge ? DC_EXEC : DC_NONE, start_tile, bridge_type | (_public_road_type << 8) | (TRANSPORT_ROAD << 15)); cur_company.Restore(); assert(!build_bridge || build_bridge_cmd.Succeeded()); @@ -448,7 +444,7 @@ static TileIndex BuildRiverBridge(PathNode *current, const DiagDirection road_di // should not force that on them. This is just to bridge rivers, not to make long bridges. for (; IsValidTile(tile) && - (GetTunnelBridgeLength(start_tile, tile) <= 5) && + (GetTunnelBridgeLength(start_tile, tile) <= std::min(_settings_game.construction.max_bridge_length, (uint16)3)) && (GetTileZ(start_tile) < (GetTileZ(tile) + _settings_game.construction.max_bridge_height)) && (GetTileZ(tile) <= GetTileZ(start_tile)); tile += TileOffsByDiagDir(road_direction)) { @@ -478,7 +474,7 @@ static TileIndex BuildRiverBridge(PathNode *current, const DiagDirection road_di const auto bridge_type = available_bridge_types[build_bridge ? RandomRange(uint32(available_bridge_types.size())) : 0]; Backup cur_company(_current_company, OWNER_DEITY, FILE_LINE); - const auto build_bridge_cmd = CmdBuildBridge(end_tile, build_bridge ? DC_EXEC : DC_NONE, start_tile, bridge_type | (ROADTYPE_ROAD << 8) | (TRANSPORT_ROAD << 15)); + const auto build_bridge_cmd = CmdBuildBridge(end_tile, build_bridge ? DC_EXEC : DC_NONE, start_tile, bridge_type | (_public_road_type << 8) | (TRANSPORT_ROAD << 15)); cur_company.Restore(); assert(!build_bridge || build_bridge_cmd.Succeeded()); @@ -768,7 +764,7 @@ static void PublicRoad_FoundEndNode(AyStar *aystar, OpenListNode *current) assert(IsValidTile(end_tile) && IsDownwardsSlope(end_tile, road_direction)); } else if (IsDownwardsSlope(tile, road_direction)) { // Provide the function with the end tile, since we already know it, but still check the result. - end_tile = BuildBridge(path, road_direction, path->parent->node.tile, true); + end_tile = BuildBridge(path, path->parent->node.tile, true); assert(IsValidTile(end_tile) && IsUpwardsSlope(end_tile, road_direction)); } else { // River bridge is the last possibility. From 7601720ff729949ff48e7d75c237d4b4d0ebe718 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Tue, 15 Jun 2021 14:45:46 +0200 Subject: [PATCH 15/45] landscape.cpp fixes --- src/landscape.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/landscape.cpp b/src/landscape.cpp index 457ca84840..70b06161b4 100644 --- a/src/landscape.cpp +++ b/src/landscape.cpp @@ -1209,8 +1209,7 @@ static bool RiverMakeWider(TileIndex tile, void *data) /* AyStar callback when an route has been found. */ static void River_FoundEndNode(AyStar *aystar, OpenListNode *current) { - uint cur_pos = 0; - for (PathNode *path = ¤t->path; path != nullptr; path = path->parent, cur_pos++) { + for (PathNode *path = ¤t->path; path != nullptr; path = path->parent) { TileIndex tile = path->node.tile; if (!IsWaterTile(tile)) { MakeRiver(tile, Random()); From 2b1d73cb3d5cf5625685ed7c452dd91374d2a13a Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Tue, 15 Jun 2021 16:40:21 +0200 Subject: [PATCH 16/45] Fix tunnel glitch --- src/road.cpp | 163 +++++++++++++++++++++++++++++---------------------- 1 file changed, 94 insertions(+), 69 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index e7888029ed..04dc973665 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -598,98 +598,123 @@ static bool IsBlockedByPreviousBridgeOrTunnel(OpenListNode *current, TileIndex s /** AyStar callback for getting the neighbouring nodes of the given node. */ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) { - const TileIndex tile = current->path.node.tile; + const auto current_tile = current->path.node.tile; + const auto previous_tile = current->path.parent != nullptr ? current->path.parent->node.tile : INVALID_TILE; + const auto forward_direction = DiagdirBetweenTiles(previous_tile, current_tile); aystar->num_neighbours = 0; // Check if we just went through a tunnel or a bridge. - if (current->path.parent != nullptr && !AreTilesAdjacent(tile, current->path.parent->node.tile)) { - const auto previous_tile = current->path.parent->node.tile; - + if (previous_tile != INVALID_TILE && !AreTilesAdjacent(current_tile, previous_tile)) { + // We went through a tunnel or bridge, this limits our options to proceed to only forward. - const auto tunnel_bridge_direction = DiagdirBetweenTiles(previous_tile, tile); - - const TileIndex tunnel_bridge_end = tile + TileOffsByDiagDir(tunnel_bridge_direction); + const TileIndex tunnel_bridge_end = current_tile + TileOffsByDiagDir(forward_direction); - if (IsValidNeighbourOfPreviousTile(tunnel_bridge_end, tile)) { + if (IsValidNeighbourOfPreviousTile(tunnel_bridge_end, current_tile)) { aystar->neighbours[aystar->num_neighbours].tile = tunnel_bridge_end; aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; aystar->num_neighbours++; } - } else { - // Handle all the regular neighbours and existing tunnels/bridges. - std::vector potential_neighbours; + } else if (IsTileType(current_tile, MP_TUNNELBRIDGE)) { + // Handle existing tunnels and bridges + const auto tunnel_bridge_end = GetOtherTunnelBridgeEnd(current_tile); + const auto tunnel_bridge_direction = DiagdirBetweenTiles(current_tile, tunnel_bridge_end); + + // Thanks to custom bridge heads we can come towards a bridge from anywhere but the reverse direction. + if (forward_direction == ReverseDiagDir(tunnel_bridge_direction)) { + return; + } - if (IsTileType(tile, MP_TUNNELBRIDGE)) { - auto neighbour = GetOtherTunnelBridgeEnd(tile); + // For tunnels we need to come directly towards the tunnel though. + if (IsTunnel(current_tile) && forward_direction != tunnel_bridge_direction) { + return; + } + + aystar->neighbours[aystar->num_neighbours].tile = tunnel_bridge_end; + aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; + aystar->num_neighbours++; + + // Handle regular neighbors for bridges thanks to custom bridge heads. + if (IsBridge(current_tile)) { + for (DiagDirection d = DIAGDIR_BEGIN; d < DIAGDIR_END; d++) { + if (d == tunnel_bridge_direction) { + // We already added this direction. + // The direct neighbor in that direction makes no sense for a bridge. + continue; + } - aystar->neighbours[aystar->num_neighbours].tile = neighbour; - aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; - aystar->num_neighbours++; + const auto neighbour = current_tile + TileOffsByDiagDir(d); + + if (neighbour == previous_tile) { + // That's where we came from. + continue; + } + + if (IsValidNeighbourOfPreviousTile(neighbour, current_tile)) { + aystar->neighbours[aystar->num_neighbours].tile = neighbour; + aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; + aystar->num_neighbours++; + } + } + } + } else { + // Handle regular neighbors. + for (DiagDirection d = DIAGDIR_BEGIN; d < DIAGDIR_END; d++) { + const auto neighbour = current_tile + TileOffsByDiagDir(d); - neighbour = tile + TileOffsByDiagDir(ReverseDiagDir(DiagdirBetweenTiles(tile, neighbour))); + if (neighbour == previous_tile) { + continue; + } - if (IsValidNeighbourOfPreviousTile(neighbour, tile)) { + if (IsValidNeighbourOfPreviousTile(neighbour, current_tile)) { aystar->neighbours[aystar->num_neighbours].tile = neighbour; aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; aystar->num_neighbours++; } - } else { - for (DiagDirection d = DIAGDIR_BEGIN; d < DIAGDIR_END; d++) { - const auto neighbour = tile + TileOffsByDiagDir(d); - - if (IsValidNeighbourOfPreviousTile(neighbour, tile)) { - aystar->neighbours[aystar->num_neighbours].tile = neighbour; + } + + // Check if we can turn this into a tunnel or a bridge. + if (current->path.parent != nullptr) { + if (IsUpwardsSlope(current_tile, forward_direction)) { + const auto tunnel_end = BuildTunnel(¤t->path); + + if (tunnel_end != INVALID_TILE && + !IsBlockedByPreviousBridgeOrTunnel(current, current_tile, tunnel_end) && + !IsSteepSlope(GetTileSlope(tunnel_end)) && + !IsHalftileSlope(GetTileSlope(tunnel_end)) && + (GetTileSlope(tunnel_end) == ComplementSlope(GetTileSlope(current_tile)))) { + assert(IsValidDiagDirection(DiagdirBetweenTiles(current_tile, tunnel_end))); + aystar->neighbours[aystar->num_neighbours].tile = tunnel_end; aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; aystar->num_neighbours++; } } - - // Check if we can turn this into a tunnel or a bridge. - if (current->path.parent != nullptr) { - const auto road_direction = DiagdirBetweenTiles(current->path.parent->node.tile, tile); - - if (IsUpwardsSlope(tile, road_direction)) { - const auto tunnel_end = BuildTunnel(¤t->path); - - if (tunnel_end != INVALID_TILE && - !IsBlockedByPreviousBridgeOrTunnel(current, current->path.node.tile, tunnel_end) && - !IsSteepSlope(GetTileSlope(tunnel_end)) && - !IsHalftileSlope(GetTileSlope(tunnel_end)) && - (GetTileSlope(tunnel_end) == ComplementSlope(GetTileSlope(current->path.node.tile)))) { - assert(IsValidDiagDirection(DiagdirBetweenTiles(tile, tunnel_end))); - aystar->neighbours[aystar->num_neighbours].tile = tunnel_end; - aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; - aystar->num_neighbours++; - } - } - else if (IsDownwardsSlope(tile, road_direction)) { - const auto bridge_end = BuildBridge(¤t->path, road_direction); - - if (bridge_end != INVALID_TILE && - !IsBlockedByPreviousBridgeOrTunnel(current, current->path.node.tile, bridge_end) && - !IsSteepSlope(GetTileSlope(bridge_end)) && - !IsHalftileSlope(GetTileSlope(bridge_end)) && - (GetTileSlope(bridge_end) == ComplementSlope(GetTileSlope(current->path.node.tile)))) { - assert(IsValidDiagDirection(DiagdirBetweenTiles(tile, bridge_end))); - aystar->neighbours[aystar->num_neighbours].tile = bridge_end; - aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; - aystar->num_neighbours++; - } + else if (IsDownwardsSlope(current_tile, forward_direction)) { + const auto bridge_end = BuildBridge(¤t->path, forward_direction); + + if (bridge_end != INVALID_TILE && + !IsBlockedByPreviousBridgeOrTunnel(current, current_tile, bridge_end) && + !IsSteepSlope(GetTileSlope(bridge_end)) && + !IsHalftileSlope(GetTileSlope(bridge_end)) && + (GetTileSlope(bridge_end) == ComplementSlope(GetTileSlope(current_tile)))) { + assert(IsValidDiagDirection(DiagdirBetweenTiles(current_tile, bridge_end))); + aystar->neighbours[aystar->num_neighbours].tile = bridge_end; + aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; + aystar->num_neighbours++; } - else if (GetTileSlope(tile) == SLOPE_FLAT) - { - // Check if we could bridge a river from a flat tile. Not looking pretty on the map but you gotta do what you gotta do. - const auto bridge_end = BuildRiverBridge(¤t->path, road_direction); - assert(bridge_end == INVALID_TILE || GetTileSlope(bridge_end) == SLOPE_FLAT); - - if (bridge_end != INVALID_TILE && - !IsBlockedByPreviousBridgeOrTunnel(current, current->path.node.tile, bridge_end)) { - assert(IsValidDiagDirection(DiagdirBetweenTiles(tile, bridge_end))); - aystar->neighbours[aystar->num_neighbours].tile = bridge_end; - aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; - aystar->num_neighbours++; - } + } + else if (GetTileSlope(current_tile) == SLOPE_FLAT) + { + // Check if we could bridge a river from a flat tile. Not looking pretty on the map but you gotta do what you gotta do. + const auto bridge_end = BuildRiverBridge(¤t->path, forward_direction); + assert(bridge_end == INVALID_TILE || GetTileSlope(bridge_end) == SLOPE_FLAT); + + if (bridge_end != INVALID_TILE && + !IsBlockedByPreviousBridgeOrTunnel(current, current_tile, bridge_end)) { + assert(IsValidDiagDirection(DiagdirBetweenTiles(current_tile, bridge_end))); + aystar->neighbours[aystar->num_neighbours].tile = bridge_end; + aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; + aystar->num_neighbours++; } } } From 5d51909c3657e2e6ecbe6e438c0ab13160fa369f Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Tue, 15 Jun 2021 20:20:25 +0200 Subject: [PATCH 17/45] Reorganize file --- src/road.cpp | 177 ++++++++++++++++++++++++++------------------------- 1 file changed, 89 insertions(+), 88 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index 04dc973665..754b2a77df 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -239,6 +239,95 @@ RoadTypes GetCompanyRoadTypes(CompanyID company, bool introduces) return rts; } +/** + * Get list of road types, regardless of company availability. + * @param introduces If true, include road types introduced by other road types + * @return the road types. + */ +RoadTypes GetRoadTypes(bool introduces) +{ + RoadTypes rts = ROADTYPES_NONE; + + for (const Engine *e : Engine::IterateType(VEH_ROAD)) { + const EngineInfo *ei = &e->info; + if (!HasBit(ei->climates, _settings_game.game_creation.landscape)) continue; + + const RoadVehicleInfo *rvi = &e->u.road; + assert(rvi->roadtype < ROADTYPE_END); + if (introduces) { + rts |= GetRoadTypeInfo(rvi->roadtype)->introduces_roadtypes; + } else { + SetBit(rts, rvi->roadtype); + } + } + + if (introduces) return AddDateIntroducedRoadTypes(rts, MAX_DAY); + return rts; +} + +/** + * Get the road type for a given label. + * @param label the roadtype label. + * @param allow_alternate_labels Search in the alternate label lists as well. + * @return the roadtype. + */ +RoadType GetRoadTypeByLabel(RoadTypeLabel label, bool allow_alternate_labels) +{ + /* Loop through each road type until the label is found */ + for (RoadType r = ROADTYPE_BEGIN; r != ROADTYPE_END; r++) { + const RoadTypeInfo *rti = GetRoadTypeInfo(r); + if (rti->label == label) return r; + } + + if (allow_alternate_labels) { + /* Test if any road type defines the label as an alternate. */ + for (RoadType r = ROADTYPE_BEGIN; r != ROADTYPE_END; r++) { + const RoadTypeInfo *rti = GetRoadTypeInfo(r); + if (std::find(rti->alternate_labels.begin(), rti->alternate_labels.end(), label) != rti->alternate_labels.end()) return r; + } + } + + /* No matching label was found, so it is invalid */ + return INVALID_ROADTYPE; +} + +/** + * Returns the available RoadSubTypes for the provided RoadType + * If the given company is valid then will be returned a list of the available sub types at the current date, while passing + * a deity company will make all the sub types available + * @param rt the RoadType to filter + * @param c the company ID to check the roadtype against + * @param any_date whether to return only currently introduced roadtypes or also future ones + * @returns the existing RoadSubTypes + */ +RoadTypes ExistingRoadTypes(CompanyID c) +{ + /* Check only players which can actually own vehicles, editor and gamescripts are considered deities */ + if (c < OWNER_END) { + const Company *company = Company::GetIfValid(c); + if (company != nullptr) return company->avail_roadtypes; + } + + RoadTypes known_roadtypes = ROADTYPES_NONE; + + /* Find used roadtypes */ + for (Engine *e : Engine::IterateType(VEH_ROAD)) { + /* Check if the roadtype can be used in the current climate */ + if (!HasBit(e->info.climates, _settings_game.game_creation.landscape)) continue; + + /* Check whether available for all potential companies */ + if (e->company_avail != (CompanyMask)-1) continue; + + known_roadtypes |= GetRoadTypeInfo(e->u.road.roadtype)->introduces_roadtypes; + } + + /* Get the date introduced roadtypes as well. */ + known_roadtypes = AddDateIntroducedRoadTypes(known_roadtypes, MAX_DAY); + + return known_roadtypes; +} + + /* ========================================================================= */ /* PUBLIC ROADS */ /* ========================================================================= */ @@ -1017,91 +1106,3 @@ void GeneratePublicRoads() /* ========================================================================= */ /* END PUBLIC ROADS */ /* ========================================================================= */ - -/** - * Get list of road types, regardless of company availability. - * @param introduces If true, include road types introduced by other road types - * @return the road types. - */ -RoadTypes GetRoadTypes(bool introduces) -{ - RoadTypes rts = ROADTYPES_NONE; - - for (const Engine *e : Engine::IterateType(VEH_ROAD)) { - const EngineInfo *ei = &e->info; - if (!HasBit(ei->climates, _settings_game.game_creation.landscape)) continue; - - const RoadVehicleInfo *rvi = &e->u.road; - assert(rvi->roadtype < ROADTYPE_END); - if (introduces) { - rts |= GetRoadTypeInfo(rvi->roadtype)->introduces_roadtypes; - } else { - SetBit(rts, rvi->roadtype); - } - } - - if (introduces) return AddDateIntroducedRoadTypes(rts, MAX_DAY); - return rts; -} - -/** - * Get the road type for a given label. - * @param label the roadtype label. - * @param allow_alternate_labels Search in the alternate label lists as well. - * @return the roadtype. - */ -RoadType GetRoadTypeByLabel(RoadTypeLabel label, bool allow_alternate_labels) -{ - /* Loop through each road type until the label is found */ - for (RoadType r = ROADTYPE_BEGIN; r != ROADTYPE_END; r++) { - const RoadTypeInfo *rti = GetRoadTypeInfo(r); - if (rti->label == label) return r; - } - - if (allow_alternate_labels) { - /* Test if any road type defines the label as an alternate. */ - for (RoadType r = ROADTYPE_BEGIN; r != ROADTYPE_END; r++) { - const RoadTypeInfo *rti = GetRoadTypeInfo(r); - if (std::find(rti->alternate_labels.begin(), rti->alternate_labels.end(), label) != rti->alternate_labels.end()) return r; - } - } - - /* No matching label was found, so it is invalid */ - return INVALID_ROADTYPE; -} - -/** - * Returns the available RoadSubTypes for the provided RoadType - * If the given company is valid then will be returned a list of the available sub types at the current date, while passing - * a deity company will make all the sub types available - * @param rt the RoadType to filter - * @param c the company ID to check the roadtype against - * @param any_date whether to return only currently introduced roadtypes or also future ones - * @returns the existing RoadSubTypes - */ -RoadTypes ExistingRoadTypes(CompanyID c) -{ - /* Check only players which can actually own vehicles, editor and gamescripts are considered deities */ - if (c < OWNER_END) { - const Company *company = Company::GetIfValid(c); - if (company != nullptr) return company->avail_roadtypes; - } - - RoadTypes known_roadtypes = ROADTYPES_NONE; - - /* Find used roadtypes */ - for (Engine *e : Engine::IterateType(VEH_ROAD)) { - /* Check if the roadtype can be used in the current climate */ - if (!HasBit(e->info.climates, _settings_game.game_creation.landscape)) continue; - - /* Check whether available for all potential companies */ - if (e->company_avail != (CompanyMask)-1) continue; - - known_roadtypes |= GetRoadTypeInfo(e->u.road.roadtype)->introduces_roadtypes; - } - - /* Get the date introduced roadtypes as well. */ - known_roadtypes = AddDateIntroducedRoadTypes(known_roadtypes, MAX_DAY); - - return known_roadtypes; -} From a6dae1426c4950a0e4308d7d2fec1c80177efcb8 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Tue, 15 Jun 2021 20:41:07 +0200 Subject: [PATCH 18/45] Re-implement the cost function --- src/road.cpp | 79 ++++++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index 754b2a77df..4e0b99a384 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -341,46 +341,6 @@ static std::vector _towns_visited_along_the_way; static RoadType _public_road_type; static const uint _public_road_hash_size = 8U; ///< The number of bits the hash for river finding should have. -static const int32 BASE_COST_PER_TILE = 1; // Cost for building a new road. -static const int32 COST_FOR_NEW_ROAD = 100; // Cost for building a new road. -static const int32 COST_FOR_SLOPE = 50; // Additional cost if the road heads up or down a slope. - -/** AyStar callback for getting the cost of the current node. */ -static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode *parent) -{ - int32 cost = BASE_COST_PER_TILE; - - if (!IsTileType(current->tile, MP_ROAD)) { - if (!AreTilesAdjacent(parent->path.node.tile, current->tile)) - { - // We're not adjacent, so we built a tunnel or bridge. - cost += (DistanceManhattan(parent->path.node.tile, current->tile)) * COST_FOR_NEW_ROAD + 6 * COST_FOR_SLOPE; - } - else if (!IsTileFlat(current->tile)) { - cost += COST_FOR_NEW_ROAD; - cost += COST_FOR_SLOPE; - } - else - { - cost += COST_FOR_NEW_ROAD; - } - } - - if (_settings_game.game_creation.build_public_roads == PRC_AVOID_CURVES && - parent->path.parent != nullptr && - DiagdirBetweenTiles(parent->path.parent->node.tile, parent->path.node.tile) != DiagdirBetweenTiles(parent->path.node.tile, current->tile)) { - cost += 1; - } - - return cost; -} - -/** AyStar callback for getting the estimated cost to the destination. */ -static int32 PublicRoad_CalculateH(AyStar *aystar, AyStarNode *current, OpenListNode *parent) -{ - return DistanceManhattan(*static_cast(aystar->user_target), current->tile) * BASE_COST_PER_TILE; -} - /** Helper function to check if a tile along a certain direction is going up an inclined slope. */ static bool IsUpwardsSlope(TileIndex tile, DiagDirection road_direction) { @@ -892,6 +852,45 @@ static void PublicRoad_FoundEndNode(AyStar *aystar, OpenListNode *current) } } +static const int32 BASE_COST_PER_TILE = 1; // Cost for building a new road. +static const int32 COST_FOR_NEW_ROAD = 100; // Cost for building a new road. +static const int32 COST_FOR_SLOPE = 50; // Additional cost if the road heads up or down a slope. + +/** AyStar callback for getting the cost of the current node. */ +static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode *parent) +{ + int32 cost = 0; + + if (IsTileType(current->tile, MP_ROAD) || IsTileType(current->tile, MP_TUNNELBRIDGE)) { + cost += (DistanceManhattan(parent->path.node.tile, current->tile)) * BASE_COST_PER_TILE; + } else { + cost += (DistanceManhattan(parent->path.node.tile, current->tile)) * COST_FOR_NEW_ROAD; + + if (GetTileZ(parent->path.node.tile) != GetTileZ(current->tile)) { + cost += COST_FOR_SLOPE; + } + + if (DistanceManhattan(parent->path.node.tile, current->tile) > 1) { + // We are planning to build a bridge or tunnel. Make that a bit more expensive. + cost += 6 * COST_FOR_SLOPE; + } + } + + if (_settings_game.game_creation.build_public_roads == PRC_AVOID_CURVES && + parent->path.parent != nullptr && + DiagdirBetweenTiles(parent->path.parent->node.tile, parent->path.node.tile) != DiagdirBetweenTiles(parent->path.node.tile, current->tile)) { + cost += 1; + } + + return cost; +} + +/** AyStar callback for getting the estimated cost to the destination. */ +static int32 PublicRoad_CalculateH(AyStar *aystar, AyStarNode *current, OpenListNode *parent) +{ + return DistanceManhattan(*static_cast(aystar->user_target), current->tile) * BASE_COST_PER_TILE; +} + bool FindPath(AyStar& finder, const TileIndex from, TileIndex to) { finder.CalculateG = PublicRoad_CalculateG; From 35a98fae7d2f7d5605e3c01d56122c63086bb514 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Tue, 15 Jun 2021 21:47:21 +0200 Subject: [PATCH 19/45] Try and fix another assertion failure --- src/road.cpp | 82 +++++++++++++++------------------------------------- 1 file changed, 23 insertions(+), 59 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index 4e0b99a384..5af3dea7c8 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -538,34 +538,34 @@ static bool IsValidNeighbourOfPreviousTile(const TileIndex tile, const TileIndex { if (!IsValidTile(tile) || (tile == previous_tile)) return false; + const auto forward_direction = DiagdirBetweenTiles(previous_tile, tile); + if (IsTileType(tile, MP_TUNNELBRIDGE)) { if (GetOtherTunnelBridgeEnd(tile) == previous_tile) return true; const auto tunnel_direction = GetTunnelBridgeDirection(tile); - if (previous_tile + TileOffsByDiagDir(tunnel_direction) != tile) return false; - } else { + return (tunnel_direction == forward_direction); + } - if (!IsTileType(tile, MP_CLEAR) && !IsTileType(tile, MP_TREES) && !IsTileType(tile, MP_ROAD)) return false; + if (!IsTileType(tile, MP_CLEAR) && !IsTileType(tile, MP_TREES) && !IsTileType(tile, MP_ROAD)) return false; - const auto slope = GetTileSlope(tile); + const auto slope = GetTileSlope(tile); - // Do not allow foundations. We'll mess things up later. - const bool has_foundation = GetFoundationSlope(tile) != slope; + // Do not allow foundations. We'll mess things up later. + const bool has_foundation = GetFoundationSlope(tile) != slope; - if (has_foundation) return false; + if (has_foundation) return false; - if (IsInclinedSlope(slope)) { - const auto slope_direction = GetInclinedSlopeDirection(slope); - const auto road_direction = DiagdirBetweenTiles(previous_tile, tile); + if (IsInclinedSlope(slope)) { + const auto slope_direction = GetInclinedSlopeDirection(slope); - if (slope_direction != road_direction && ReverseDiagDir(slope_direction) != road_direction) { - return false; - } - } else if (slope != SLOPE_FLAT) { + if (slope_direction != forward_direction && ReverseDiagDir(slope_direction) != forward_direction) { return false; } + } else if (slope != SLOPE_FLAT) { + return false; } return true; @@ -654,58 +654,22 @@ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) aystar->num_neighbours = 0; // Check if we just went through a tunnel or a bridge. - if (previous_tile != INVALID_TILE && !AreTilesAdjacent(current_tile, previous_tile)) { + if (IsValidTile(previous_tile) && !AreTilesAdjacent(current_tile, previous_tile)) { // We went through a tunnel or bridge, this limits our options to proceed to only forward. - const TileIndex tunnel_bridge_end = current_tile + TileOffsByDiagDir(forward_direction); + const TileIndex next_tile = current_tile + TileOffsByDiagDir(forward_direction); - if (IsValidNeighbourOfPreviousTile(tunnel_bridge_end, current_tile)) { - aystar->neighbours[aystar->num_neighbours].tile = tunnel_bridge_end; + if (IsValidNeighbourOfPreviousTile(next_tile, current_tile)) { + aystar->neighbours[aystar->num_neighbours].tile = next_tile; aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; aystar->num_neighbours++; } } else if (IsTileType(current_tile, MP_TUNNELBRIDGE)) { // Handle existing tunnels and bridges const auto tunnel_bridge_end = GetOtherTunnelBridgeEnd(current_tile); - const auto tunnel_bridge_direction = DiagdirBetweenTiles(current_tile, tunnel_bridge_end); - - // Thanks to custom bridge heads we can come towards a bridge from anywhere but the reverse direction. - if (forward_direction == ReverseDiagDir(tunnel_bridge_direction)) { - return; - } - - // For tunnels we need to come directly towards the tunnel though. - if (IsTunnel(current_tile) && forward_direction != tunnel_bridge_direction) { - return; - } - aystar->neighbours[aystar->num_neighbours].tile = tunnel_bridge_end; aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; aystar->num_neighbours++; - - // Handle regular neighbors for bridges thanks to custom bridge heads. - if (IsBridge(current_tile)) { - for (DiagDirection d = DIAGDIR_BEGIN; d < DIAGDIR_END; d++) { - if (d == tunnel_bridge_direction) { - // We already added this direction. - // The direct neighbor in that direction makes no sense for a bridge. - continue; - } - - const auto neighbour = current_tile + TileOffsByDiagDir(d); - - if (neighbour == previous_tile) { - // That's where we came from. - continue; - } - - if (IsValidNeighbourOfPreviousTile(neighbour, current_tile)) { - aystar->neighbours[aystar->num_neighbours].tile = neighbour; - aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; - aystar->num_neighbours++; - } - } - } } else { // Handle regular neighbors. for (DiagDirection d = DIAGDIR_BEGIN; d < DIAGDIR_END; d++) { @@ -723,11 +687,11 @@ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) } // Check if we can turn this into a tunnel or a bridge. - if (current->path.parent != nullptr) { + if (IsValidTile(previous_tile)) { if (IsUpwardsSlope(current_tile, forward_direction)) { const auto tunnel_end = BuildTunnel(¤t->path); - if (tunnel_end != INVALID_TILE && + if (IsValidTile(tunnel_end) && !IsBlockedByPreviousBridgeOrTunnel(current, current_tile, tunnel_end) && !IsSteepSlope(GetTileSlope(tunnel_end)) && !IsHalftileSlope(GetTileSlope(tunnel_end)) && @@ -741,7 +705,7 @@ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) else if (IsDownwardsSlope(current_tile, forward_direction)) { const auto bridge_end = BuildBridge(¤t->path, forward_direction); - if (bridge_end != INVALID_TILE && + if (IsValidTile(bridge_end) && !IsBlockedByPreviousBridgeOrTunnel(current, current_tile, bridge_end) && !IsSteepSlope(GetTileSlope(bridge_end)) && !IsHalftileSlope(GetTileSlope(bridge_end)) && @@ -756,9 +720,9 @@ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) { // Check if we could bridge a river from a flat tile. Not looking pretty on the map but you gotta do what you gotta do. const auto bridge_end = BuildRiverBridge(¤t->path, forward_direction); - assert(bridge_end == INVALID_TILE || GetTileSlope(bridge_end) == SLOPE_FLAT); + assert(IsValidTile(bridge_end) || GetTileSlope(bridge_end) == SLOPE_FLAT); - if (bridge_end != INVALID_TILE && + if (IsValidTile(bridge_end) && !IsBlockedByPreviousBridgeOrTunnel(current, current_tile, bridge_end)) { assert(IsValidDiagDirection(DiagdirBetweenTiles(current_tile, bridge_end))); aystar->neighbours[aystar->num_neighbours].tile = bridge_end; From ac8da77d0bc9c13cd4ca7e249fb92203206dbbee Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Tue, 15 Jun 2021 21:49:39 +0200 Subject: [PATCH 20/45] Fixing a stupid error --- src/road.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/road.cpp b/src/road.cpp index 5af3dea7c8..31c302480b 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -720,7 +720,7 @@ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) { // Check if we could bridge a river from a flat tile. Not looking pretty on the map but you gotta do what you gotta do. const auto bridge_end = BuildRiverBridge(¤t->path, forward_direction); - assert(IsValidTile(bridge_end) || GetTileSlope(bridge_end) == SLOPE_FLAT); + assert(!IsValidTile(bridge_end) || GetTileSlope(bridge_end) == SLOPE_FLAT); if (IsValidTile(bridge_end) && !IsBlockedByPreviousBridgeOrTunnel(current, current_tile, bridge_end)) { From 1732e0a36693e9ded3fa7df64680ece8a35d88bd Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Wed, 16 Jun 2021 21:42:32 +0100 Subject: [PATCH 21/45] Remove trailing whitespace --- src/road.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index 31c302480b..319cc40f23 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -393,7 +393,7 @@ static TileIndex BuildTunnel(PathNode *current, TileIndex end_tile = INVALID_TIL if (!_cheats.crossing_tunnels.value && IsTunnelInWay(end_tile, start_z)) return INVALID_TILE; } - + // No too long or super-short tunnels and always ending up on a matching upwards slope. if (IsSteepSlope(GetTileSlope(end_tile)) || IsHalftileSlope(GetTileSlope(end_tile))) return INVALID_TILE; if (GetTileSlope(start_tile) != ComplementSlope(GetTileSlope(end_tile))) return INVALID_TILE; @@ -616,7 +616,7 @@ static bool IsBlockedByPreviousBridgeOrTunnel(OpenListNode *current, TileIndex s { PathNode* start = ¤t->path; PathNode* end = current->path.parent; - + while (end != nullptr) { Point start_a {}; start_a.x = TileX(start->node.tile); @@ -631,12 +631,12 @@ static bool IsBlockedByPreviousBridgeOrTunnel(OpenListNode *current, TileIndex s Point end_b {}; end_b.x = TileX(end_tile); end_b.y = TileY(end_tile); - + if (!AreTilesAdjacent(start->node.tile, end->node.tile) && (AreIntersecting(start_a, end_a, start_b, end_b) || AreParallelOverlapping(start_a, end_a, start_b, end_b))) { return true; } - + start = end; end = start->parent; } @@ -647,7 +647,7 @@ static bool IsBlockedByPreviousBridgeOrTunnel(OpenListNode *current, TileIndex s /** AyStar callback for getting the neighbouring nodes of the given node. */ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) { - const auto current_tile = current->path.node.tile; + const auto current_tile = current->path.node.tile; const auto previous_tile = current->path.parent != nullptr ? current->path.parent->node.tile : INVALID_TILE; const auto forward_direction = DiagdirBetweenTiles(previous_tile, current_tile); @@ -655,7 +655,7 @@ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) // Check if we just went through a tunnel or a bridge. if (IsValidTile(previous_tile) && !AreTilesAdjacent(current_tile, previous_tile)) { - + // We went through a tunnel or bridge, this limits our options to proceed to only forward. const TileIndex next_tile = current_tile + TileOffsByDiagDir(forward_direction); @@ -685,7 +685,7 @@ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) aystar->num_neighbours++; } } - + // Check if we can turn this into a tunnel or a bridge. if (IsValidTile(previous_tile)) { if (IsUpwardsSlope(current_tile, forward_direction)) { @@ -694,7 +694,7 @@ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) if (IsValidTile(tunnel_end) && !IsBlockedByPreviousBridgeOrTunnel(current, current_tile, tunnel_end) && !IsSteepSlope(GetTileSlope(tunnel_end)) && - !IsHalftileSlope(GetTileSlope(tunnel_end)) && + !IsHalftileSlope(GetTileSlope(tunnel_end)) && (GetTileSlope(tunnel_end) == ComplementSlope(GetTileSlope(current_tile)))) { assert(IsValidDiagDirection(DiagdirBetweenTiles(current_tile, tunnel_end))); aystar->neighbours[aystar->num_neighbours].tile = tunnel_end; @@ -708,7 +708,7 @@ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) if (IsValidTile(bridge_end) && !IsBlockedByPreviousBridgeOrTunnel(current, current_tile, bridge_end) && !IsSteepSlope(GetTileSlope(bridge_end)) && - !IsHalftileSlope(GetTileSlope(bridge_end)) && + !IsHalftileSlope(GetTileSlope(bridge_end)) && (GetTileSlope(bridge_end) == ComplementSlope(GetTileSlope(current_tile)))) { assert(IsValidDiagDirection(DiagdirBetweenTiles(current_tile, bridge_end))); aystar->neighbours[aystar->num_neighbours].tile = bridge_end; @@ -796,7 +796,7 @@ static void PublicRoad_FoundEndNode(AyStar *aystar, OpenListNode *current) const DiagDirection road_direction = DiagdirBetweenTiles(tile, path->parent->node.tile); auto end_tile = INVALID_TILE; - + if (IsUpwardsSlope(tile, road_direction)) { end_tile = BuildTunnel(path, path->parent->node.tile, true); assert(IsValidTile(end_tile) && IsDownwardsSlope(end_tile, road_direction)); @@ -866,14 +866,14 @@ bool FindPath(AyStar& finder, const TileIndex from, TileIndex to) finder.max_search_nodes = 1 << 20; finder.Init(1 << _public_road_hash_size); - + AyStarNode start {}; start.tile = from; start.direction = INVALID_TRACKDIR; finder.AddStartNode(&start, 0); int result = AYSTAR_STILL_BUSY; - + while (result == AYSTAR_STILL_BUSY) { result = finder.Main(); } @@ -913,7 +913,7 @@ void GeneratePublicRoads() if (towns.empty()) { return; } - + SetGeneratingWorldProgress(GWP_PUBLIC_ROADS, uint(towns.size())); // Create a list of networks which also contain a value indicating how many times we failed to connect to them. @@ -947,7 +947,7 @@ void GeneratePublicRoads() sort(towns.begin(), towns.end(), [&](auto a, auto b) { return DistanceManhattan(a, main_town) < DistanceManhattan(b, main_town); }); - for (auto start_town : towns) { + for (auto start_town : towns) { // Check if we can connect to any of the networks. _towns_visited_along_the_way.clear(); @@ -979,7 +979,7 @@ void GeneratePublicRoads() for (const TileIndex visited_town : _towns_visited_along_the_way) { town_to_network_map[visited_town] = reachable_network; } - + } else { town_to_network_map.erase(reachable_from_town); reachable_network->failures_to_connect++; @@ -996,7 +996,7 @@ void GeneratePublicRoads() if (reachable_from_town != town_to_network_map.end() && network.get() == reachable_from_town->second.get()) { return false; } - + // Try to connect to the town in the network that is closest to us. // If we can't connect to that one, we can't connect to any of them since they are all interconnected. sort(network->towns.begin(), network->towns.end(), [&](auto a, auto b) { return DistanceManhattan(start_town, a) < DistanceManhattan(start_town, b); }); From e46fb1aa995615868bcabcb78d64c23f13b2deb7 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Wed, 16 Jun 2021 21:42:57 +0100 Subject: [PATCH 22/45] Fix loading of build public roads setting from JokerPP savegames --- src/table/settings.ini | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/table/settings.ini b/src/table/settings.ini index bf3054f19a..14de0db585 100644 --- a/src/table/settings.ini +++ b/src/table/settings.ini @@ -4072,7 +4072,10 @@ strhelp = STR_CONFIG_SETTING_HEIGHT_ROCKS_HELPTEXT strval = STR_JUST_COMMA patxname = ""rocks.game_creation.height_affects_rocks"" -;;game_creation.build_public_roads +[SDT_XREF] +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_JOKERPP) +xref = ""game_creation.build_public_roads"" + [SDT_VAR] base = GameSettings var = game_creation.build_public_roads @@ -4085,7 +4088,6 @@ str = STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS strhelp = STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS_HELPTEXT strval = STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS_NONE patxname = ""public_roads.game_creation.build_public_roads"" -extver = SlXvFeatureTest(XSLFTO_OR, XSLFI_JOKERPP) ; locale From e95447edac3096b330cee5f492750ab03db1d5d7 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Wed, 16 Jun 2021 21:44:37 +0100 Subject: [PATCH 23/45] Avoid unnecessarily converting lambda to std::function --- src/road.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/road.cpp b/src/road.cpp index 319cc40f23..c7f410f01e 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -992,7 +992,7 @@ void GeneratePublicRoads() return town_network_distance(start_town, a) < town_network_distance(start_town, b); }); - std::function can_reach = [&](const std::shared_ptr &network) { + auto can_reach = [&](const std::shared_ptr &network) { if (reachable_from_town != town_to_network_map.end() && network.get() == reachable_from_town->second.get()) { return false; } From 1647f9a616e224faf59bb1a222830e565b83d242 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Wed, 16 Jun 2021 22:04:19 +0100 Subject: [PATCH 24/45] Avoid redundant calls to DistanceManhattan in town_network_distance --- src/road.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index c7f410f01e..d8a134698b 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -938,11 +938,11 @@ void GeneratePublicRoads() IncreaseGeneratingWorldProgress(GWP_PUBLIC_ROADS); auto town_network_distance = [](const TileIndex town, const std::shared_ptr &network) { - const auto min_element = std::min_element(network->towns.begin(), network->towns.end(), [&](const TileIndex town_a, const TileIndex town_b) { - return DistanceManhattan(town, town_a) < DistanceManhattan(town, town_b); - }); - - return DistanceManhattan(*min_element, town); + int32 best = INT32_MAX; + for (TileIndex t : network->towns) { + best = std::min(best, DistanceManhattan(t, town)); + } + return best; }; sort(towns.begin(), towns.end(), [&](auto a, auto b) { return DistanceManhattan(a, main_town) < DistanceManhattan(b, main_town); }); From d182b0b138e06c1d2d4b1aa2b129ed433921ff9f Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Wed, 16 Jun 2021 22:13:06 +0100 Subject: [PATCH 25/45] Remove "using namespace std" --- src/road.cpp | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index d8a134698b..dd2f562e11 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -894,14 +894,12 @@ struct TownNetwork */ void GeneratePublicRoads() { - using namespace std; - if (_settings_game.game_creation.build_public_roads == PRC_NONE) return; _town_centers.clear(); _towns_visited_along_the_way.clear(); - vector towns; + std::vector towns; towns.clear(); { for (const Town *town : Town::Iterate()) { @@ -917,10 +915,10 @@ void GeneratePublicRoads() SetGeneratingWorldProgress(GWP_PUBLIC_ROADS, uint(towns.size())); // Create a list of networks which also contain a value indicating how many times we failed to connect to them. - vector> networks; - unordered_map> town_to_network_map; + std::vector> networks; + std::unordered_map> town_to_network_map; - sort(towns.begin(), towns.end(), [&](auto a, auto b) { return DistanceFromEdge(a) > DistanceFromEdge(b); }); + std::sort(towns.begin(), towns.end(), [&](auto a, auto b) { return DistanceFromEdge(a) > DistanceFromEdge(b); }); TileIndex main_town = *towns.begin(); towns.erase(towns.begin()); @@ -928,7 +926,7 @@ void GeneratePublicRoads() _public_road_type = GetTownRoadType(Town::GetByTile(main_town)); std::unordered_set checked_towns; - auto main_network = make_shared(); + auto main_network = std::make_shared(); main_network->towns.push_back(main_town); main_network->failures_to_connect = 0; @@ -945,7 +943,7 @@ void GeneratePublicRoads() return best; }; - sort(towns.begin(), towns.end(), [&](auto a, auto b) { return DistanceManhattan(a, main_town) < DistanceManhattan(b, main_town); }); + std::sort(towns.begin(), towns.end(), [&](auto a, auto b) { return DistanceManhattan(a, main_town) < DistanceManhattan(b, main_town); }); for (auto start_town : towns) { // Check if we can connect to any of the networks. @@ -959,7 +957,7 @@ void GeneratePublicRoads() if (reachable_from_town != town_to_network_map.end()) { auto reachable_network = reachable_from_town->second; - sort(reachable_network->towns.begin(), reachable_network->towns.end(), [&](auto a, auto b) { return DistanceManhattan(start_town, a) < DistanceManhattan(start_town, b); }); + std::sort(reachable_network->towns.begin(), reachable_network->towns.end(), [&](auto a, auto b) { return DistanceManhattan(start_town, a) < DistanceManhattan(start_town, b); }); const TileIndex end_town = *reachable_network->towns.begin(); checked_towns.emplace(end_town); @@ -988,7 +986,7 @@ void GeneratePublicRoads() if (!found_path) { // Sort networks by failed connection attempts, so we try the most likely one first. - sort(networks.begin(), networks.end(), [&](const std::shared_ptr &a, const std::shared_ptr &b) { + std::sort(networks.begin(), networks.end(), [&](const std::shared_ptr &a, const std::shared_ptr &b) { return town_network_distance(start_town, a) < town_network_distance(start_town, b); }); @@ -1027,7 +1025,7 @@ void GeneratePublicRoads() return found_path; }; - vector>::iterator networks_end; + std::vector>::iterator networks_end; if (networks.size() > 5) { networks_end = networks.begin() + 5; @@ -1035,15 +1033,15 @@ void GeneratePublicRoads() networks_end = networks.end(); } - vector> sampled_networks; + std::vector> sampled_networks; std::copy(networks.begin(), networks_end, std::back_inserter(sampled_networks)); - sort(sampled_networks.begin(), sampled_networks.end(), [&](const std::shared_ptr &a, const std::shared_ptr &b) { + std::sort(sampled_networks.begin(), sampled_networks.end(), [&](const std::shared_ptr &a, const std::shared_ptr &b) { return a->failures_to_connect < b->failures_to_connect; }); - if (!any_of(sampled_networks.begin(), sampled_networks.end(), can_reach)) { + if (!std::any_of(sampled_networks.begin(), sampled_networks.end(), can_reach)) { // We failed so many networks, we are a separate network. Let future towns try to connect to us. - auto new_network = make_shared(); + auto new_network = std::make_shared(); new_network->towns.push_back(start_town); new_network->failures_to_connect = 0; From a6f74788735a79d31860a3df4550738b34d9743e Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Wed, 16 Jun 2021 22:25:01 +0100 Subject: [PATCH 26/45] Adjust comment for BASE_COST_PER_TILE --- src/road.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/road.cpp b/src/road.cpp index dd2f562e11..da656a34d8 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -816,7 +816,7 @@ static void PublicRoad_FoundEndNode(AyStar *aystar, OpenListNode *current) } } -static const int32 BASE_COST_PER_TILE = 1; // Cost for building a new road. +static const int32 BASE_COST_PER_TILE = 1; // Cost for existing road or tunnel/bridge. static const int32 COST_FOR_NEW_ROAD = 100; // Cost for building a new road. static const int32 COST_FOR_SLOPE = 50; // Additional cost if the road heads up or down a slope. From 63534f97bd58316dd860397382aba68f5efe6fd6 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Wed, 16 Jun 2021 22:54:40 +0100 Subject: [PATCH 27/45] Make bridges and tunnels significantly more costly --- src/road.cpp | 46 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index da656a34d8..779b02e1c9 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -816,28 +816,54 @@ static void PublicRoad_FoundEndNode(AyStar *aystar, OpenListNode *current) } } -static const int32 BASE_COST_PER_TILE = 1; // Cost for existing road or tunnel/bridge. -static const int32 COST_FOR_NEW_ROAD = 100; // Cost for building a new road. -static const int32 COST_FOR_SLOPE = 50; // Additional cost if the road heads up or down a slope. +static const int32 BASE_COST_PER_TILE = 1; // Cost for existing road or tunnel/bridge. +static const int32 COST_FOR_NEW_ROAD = 100; // Cost for building a new road. +static const int32 COST_FOR_NEW_BRIDGE = 500; // Cost for building a new bridge (per tile). +static const int32 COST_FOR_NEW_TUNNEL = 500; // Cost for building a new tunnel (per tile, with non-linear increase with length as in CmdBuildTunnel costs). +static const int32 COST_FOR_SLOPE = 50; // Additional cost if the road heads up or down a slope. /** AyStar callback for getting the cost of the current node. */ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode *parent) { int32 cost = 0; + int32 distance = DistanceManhattan(parent->path.node.tile, current->tile); + if (IsTileType(current->tile, MP_ROAD) || IsTileType(current->tile, MP_TUNNELBRIDGE)) { - cost += (DistanceManhattan(parent->path.node.tile, current->tile)) * BASE_COST_PER_TILE; + cost += distance * BASE_COST_PER_TILE; } else { - cost += (DistanceManhattan(parent->path.node.tile, current->tile)) * COST_FOR_NEW_ROAD; + if (distance > 1) { + /* We are planning to build a bridge or tunnel. Make that much more expensive. */ + const DiagDirection road_direction = DiagdirBetweenTiles(parent->path.node.tile, current->tile); + if (IsUpwardsSlope(parent->path.node.tile, road_direction)) { + /* Tunnel */ + /* Prevent excessively long tunnels */ + if (distance > 30) return AYSTAR_INVALID_NODE; + + cost += 2 * COST_FOR_NEW_TUNNEL; + int32 middle_cost = 0; + int tiles_coef = 3; + int tiles_bump = 25; + for (int i = 1; i <= distance; i++) { + if (i == tiles_bump) { + tiles_coef++; + tiles_bump *= 2; + } + middle_cost += COST_FOR_NEW_TUNNEL; + middle_cost += middle_cost >> tiles_coef; + } + cost += middle_cost; + } else { + /* Bridge */ + cost += distance * COST_FOR_NEW_BRIDGE; + } + } else { + cost += distance * COST_FOR_NEW_ROAD; + } if (GetTileZ(parent->path.node.tile) != GetTileZ(current->tile)) { cost += COST_FOR_SLOPE; } - - if (DistanceManhattan(parent->path.node.tile, current->tile) > 1) { - // We are planning to build a bridge or tunnel. Make that a bit more expensive. - cost += 6 * COST_FOR_SLOPE; - } } if (_settings_game.game_creation.build_public_roads == PRC_AVOID_CURVES && From bfdabf4cb21f38c953442f28687ea18ffea67fb6 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Thu, 17 Jun 2021 00:44:43 +0100 Subject: [PATCH 28/45] Allow building on coast tiles --- src/road.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index 779b02e1c9..6fb4f9510b 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -447,7 +447,7 @@ static TileIndex BuildBridge(PathNode *current, TileIndex end_tile = INVALID_TIL if (!IsValidTile(end_tile)) return INVALID_TILE; if (GetTileSlope(start_tile) != ComplementSlope(GetTileSlope(end_tile))) return INVALID_TILE; - if (!IsTileType(end_tile, MP_CLEAR) && !IsTileType(end_tile, MP_TREES)) return INVALID_TILE; + if (!IsTileType(end_tile, MP_CLEAR) && !IsTileType(end_tile, MP_TREES) && !IsCoastTile(end_tile)) return INVALID_TILE; } assert(!build_bridge || (IsValidTile(end_tile) && GetTileSlope(start_tile) == ComplementSlope(GetTileSlope(end_tile)))); @@ -498,7 +498,7 @@ static TileIndex BuildRiverBridge(PathNode *current, const DiagDirection road_di (GetTileZ(tile) <= GetTileZ(start_tile)); tile += TileOffsByDiagDir(road_direction)) { - if ((IsTileType(tile, MP_CLEAR) || IsTileType(tile, MP_TREES)) && + if ((IsTileType(tile, MP_CLEAR) || IsTileType(tile, MP_TREES) || IsCoastTile(tile)) && GetTileZ(tile) <= GetTileZ(start_tile) && GetTileSlope(tile) == SLOPE_FLAT) { end_tile = tile; @@ -507,7 +507,7 @@ static TileIndex BuildRiverBridge(PathNode *current, const DiagDirection road_di } if (!IsValidTile(end_tile)) return INVALID_TILE; - if (!IsTileType(end_tile, MP_CLEAR) && !IsTileType(end_tile, MP_TREES)) return INVALID_TILE; + if (!IsTileType(end_tile, MP_CLEAR) && !IsTileType(end_tile, MP_TREES) && !IsCoastTile(end_tile)) return INVALID_TILE; } assert(!build_bridge || IsValidTile(end_tile)); @@ -549,7 +549,7 @@ static bool IsValidNeighbourOfPreviousTile(const TileIndex tile, const TileIndex return (tunnel_direction == forward_direction); } - if (!IsTileType(tile, MP_CLEAR) && !IsTileType(tile, MP_TREES) && !IsTileType(tile, MP_ROAD)) return false; + if (!IsTileType(tile, MP_CLEAR) && !IsTileType(tile, MP_TREES) && !IsTileType(tile, MP_ROAD) && !IsCoastTile(tile)) return false; const auto slope = GetTileSlope(tile); From 0f336bba57f570fcd179f4bb39cfb4e6108fed67 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Thu, 17 Jun 2021 00:52:53 +0100 Subject: [PATCH 29/45] Give bridges over the sea a more generous length limit --- src/road.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index 6fb4f9510b..2824537461 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -425,13 +425,18 @@ static TileIndex BuildBridge(PathNode *current, TileIndex end_tile = INVALID_TIL if (!build_bridge) { const DiagDirection direction = ReverseDiagDir(GetInclinedSlopeDirection(GetTileSlope(start_tile))); + TileIndex tile = start_tile + TileOffsByDiagDir(direction); + const bool is_over_water = IsValidTile(tile) && IsTileType(tile, MP_WATER) && IsSea(tile); + uint bridge_length = 0; + const uint bridge_length_limit = std::min(_settings_game.construction.max_bridge_length, is_over_water ? 20 : 10); + // We are not building yet, so we still need to find the end_tile. - for (TileIndex tile = start_tile + TileOffsByDiagDir(direction); + for (; IsValidTile(tile) && - (GetTunnelBridgeLength(start_tile, tile) <= std::min(_settings_game.construction.max_bridge_length, (uint16)10)) && + (bridge_length <= bridge_length_limit) && (GetTileZ(start_tile) < (GetTileZ(tile) + _settings_game.construction.max_bridge_height)) && (GetTileZ(tile) <= GetTileZ(start_tile)); - tile += TileOffsByDiagDir(direction)) { + tile += TileOffsByDiagDir(direction), bridge_length++) { auto is_complementary_slope = !IsSteepSlope(GetTileSlope(tile)) && From bd41cb618a3c16c61640fc75cdc3095f2230590c Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Thu, 17 Jun 2021 00:54:37 +0100 Subject: [PATCH 30/45] Allow using and building trivial foundations (3 corners raised and 2 opposite corners raised) --- src/road.cpp | 125 +++++++++++++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 54 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index 2824537461..c24feb5b3c 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -341,11 +341,9 @@ static std::vector _towns_visited_along_the_way; static RoadType _public_road_type; static const uint _public_road_hash_size = 8U; ///< The number of bits the hash for river finding should have. -/** Helper function to check if a tile along a certain direction is going up an inclined slope. */ -static bool IsUpwardsSlope(TileIndex tile, DiagDirection road_direction) +/** Helper function to check if a slope along a certain direction is going up an inclined slope. */ +static bool IsUpwardsSlope(const Slope slope, DiagDirection road_direction) { - const auto slope = GetTileSlope(tile); - if (!IsInclinedSlope(slope)) return false; const auto slope_direction = GetInclinedSlopeDirection(slope); @@ -353,11 +351,9 @@ static bool IsUpwardsSlope(TileIndex tile, DiagDirection road_direction) return road_direction == slope_direction; } -/** Helper function to check if a tile along a certain direction is going down an inclined slope. */ -static bool IsDownwardsSlope(const TileIndex tile, const DiagDirection road_direction) +/** Helper function to check if a slope along a certain direction is going down an inclined slope. */ +static bool IsDownwardsSlope(const Slope slope, const DiagDirection road_direction) { - const auto slope = GetTileSlope(tile); - if (!IsInclinedSlope(slope)) return false; const auto slope_direction = GetInclinedSlopeDirection(slope); @@ -365,6 +361,12 @@ static bool IsDownwardsSlope(const TileIndex tile, const DiagDirection road_dire return road_direction == ReverseDiagDir(slope_direction); } +/** Helper function to check if a slope is effectively flat. */ +static bool IsSufficientlyFlatSlope(const Slope slope) +{ + return !IsSteepSlope(slope) && HasBit(VALID_LEVEL_CROSSING_SLOPES, slope); +} + static TileIndex BuildTunnel(PathNode *current, TileIndex end_tile = INVALID_TILE, const bool build_tunnel = false) { const TileIndex start_tile = current->node.tile; @@ -485,6 +487,7 @@ static TileIndex BuildBridge(PathNode *current, TileIndex end_tile = INVALID_TIL static TileIndex BuildRiverBridge(PathNode *current, const DiagDirection road_direction, TileIndex end_tile = INVALID_TILE, const bool build_bridge = false) { const TileIndex start_tile = current->node.tile; + const int start_tile_z = GetTileMaxZ(start_tile); if (!build_bridge) { // We are not building yet, so we still need to find the end_tile. @@ -499,13 +502,13 @@ static TileIndex BuildRiverBridge(PathNode *current, const DiagDirection road_di for (; IsValidTile(tile) && (GetTunnelBridgeLength(start_tile, tile) <= std::min(_settings_game.construction.max_bridge_length, (uint16)3)) && - (GetTileZ(start_tile) < (GetTileZ(tile) + _settings_game.construction.max_bridge_height)) && - (GetTileZ(tile) <= GetTileZ(start_tile)); + (start_tile_z < (GetTileZ(tile) + _settings_game.construction.max_bridge_height)) && + (GetTileZ(tile) <= start_tile_z); tile += TileOffsByDiagDir(road_direction)) { if ((IsTileType(tile, MP_CLEAR) || IsTileType(tile, MP_TREES) || IsCoastTile(tile)) && - GetTileZ(tile) <= GetTileZ(start_tile) && - GetTileSlope(tile) == SLOPE_FLAT) { + GetTileZ(tile) <= start_tile_z && + IsSufficientlyFlatSlope(GetTileSlope(tile))) { end_tile = tile; break; } @@ -556,12 +559,13 @@ static bool IsValidNeighbourOfPreviousTile(const TileIndex tile, const TileIndex if (!IsTileType(tile, MP_CLEAR) && !IsTileType(tile, MP_TREES) && !IsTileType(tile, MP_ROAD) && !IsCoastTile(tile)) return false; - const auto slope = GetTileSlope(tile); + const Slope slope = GetTileSlope(tile); + if (IsSteepSlope(slope)) return false; - // Do not allow foundations. We'll mess things up later. - const bool has_foundation = GetFoundationSlope(tile) != slope; + const Slope foundationSlope = GetFoundationSlope(tile); - if (has_foundation) return false; + /* Allow only trivial foundations (3 corners raised or 2 opposite corners raised -> flat) */ + if (slope != foundationSlope && !HasBit(VALID_LEVEL_CROSSING_SLOPES, slope)) return false; if (IsInclinedSlope(slope)) { const auto slope_direction = GetInclinedSlopeDirection(slope); @@ -569,8 +573,17 @@ static bool IsValidNeighbourOfPreviousTile(const TileIndex tile, const TileIndex if (slope_direction != forward_direction && ReverseDiagDir(slope_direction) != forward_direction) { return false; } - } else if (slope != SLOPE_FLAT) { + } else if (!HasBit(VALID_LEVEL_CROSSING_SLOPES, slope)) { return false; + } else { + /* Check whether the previous tile was an inclined slope, and whether we are leaving the previous tile from a valid direction */ + if (slope != SLOPE_FLAT) { + const Slope previous_slope = GetTileSlope(previous_tile); + if (IsInclinedSlope(previous_slope)) { + const DiagDirection slope_direction = GetInclinedSlopeDirection(previous_slope); + if (slope_direction != forward_direction && ReverseDiagDir(slope_direction) != forward_direction) return false; + } + } } return true; @@ -693,42 +706,44 @@ static void PublicRoad_GetNeighbours(AyStar *aystar, OpenListNode *current) // Check if we can turn this into a tunnel or a bridge. if (IsValidTile(previous_tile)) { - if (IsUpwardsSlope(current_tile, forward_direction)) { - const auto tunnel_end = BuildTunnel(¤t->path); - - if (IsValidTile(tunnel_end) && - !IsBlockedByPreviousBridgeOrTunnel(current, current_tile, tunnel_end) && - !IsSteepSlope(GetTileSlope(tunnel_end)) && - !IsHalftileSlope(GetTileSlope(tunnel_end)) && - (GetTileSlope(tunnel_end) == ComplementSlope(GetTileSlope(current_tile)))) { - assert(IsValidDiagDirection(DiagdirBetweenTiles(current_tile, tunnel_end))); - aystar->neighbours[aystar->num_neighbours].tile = tunnel_end; - aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; - aystar->num_neighbours++; + const Slope current_tile_slope = GetTileSlope(current_tile); + if (IsUpwardsSlope(current_tile_slope, forward_direction)) { + const TileIndex tunnel_end = BuildTunnel(¤t->path); + + if (IsValidTile(tunnel_end)) { + const Slope tunnel_end_slope = GetTileSlope(tunnel_end); + if (!IsBlockedByPreviousBridgeOrTunnel(current, current_tile, tunnel_end) && + !IsSteepSlope(tunnel_end_slope) && + !IsHalftileSlope(tunnel_end_slope) && + (tunnel_end_slope == ComplementSlope(current_tile_slope))) { + assert(IsValidDiagDirection(DiagdirBetweenTiles(current_tile, tunnel_end))); + aystar->neighbours[aystar->num_neighbours].tile = tunnel_end; + aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; + aystar->num_neighbours++; + } } - } - else if (IsDownwardsSlope(current_tile, forward_direction)) { - const auto bridge_end = BuildBridge(¤t->path, forward_direction); - - if (IsValidTile(bridge_end) && - !IsBlockedByPreviousBridgeOrTunnel(current, current_tile, bridge_end) && - !IsSteepSlope(GetTileSlope(bridge_end)) && - !IsHalftileSlope(GetTileSlope(bridge_end)) && - (GetTileSlope(bridge_end) == ComplementSlope(GetTileSlope(current_tile)))) { - assert(IsValidDiagDirection(DiagdirBetweenTiles(current_tile, bridge_end))); - aystar->neighbours[aystar->num_neighbours].tile = bridge_end; - aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; - aystar->num_neighbours++; + } else if (IsDownwardsSlope(current_tile_slope, forward_direction)) { + const TileIndex bridge_end = BuildBridge(¤t->path, forward_direction); + + if (IsValidTile(bridge_end)) { + const Slope bridge_end_slope = GetTileSlope(bridge_end); + if (!IsBlockedByPreviousBridgeOrTunnel(current, current_tile, bridge_end) && + !IsSteepSlope(bridge_end_slope) && + !IsHalftileSlope(bridge_end_slope) && + (bridge_end_slope == ComplementSlope(current_tile_slope))) { + assert(IsValidDiagDirection(DiagdirBetweenTiles(current_tile, bridge_end))); + aystar->neighbours[aystar->num_neighbours].tile = bridge_end; + aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; + aystar->num_neighbours++; + } } - } - else if (GetTileSlope(current_tile) == SLOPE_FLAT) - { + } else if (IsSufficientlyFlatSlope(current_tile_slope)) { // Check if we could bridge a river from a flat tile. Not looking pretty on the map but you gotta do what you gotta do. const auto bridge_end = BuildRiverBridge(¤t->path, forward_direction); - assert(!IsValidTile(bridge_end) || GetTileSlope(bridge_end) == SLOPE_FLAT); + assert(!IsValidTile(bridge_end) || IsSufficientlyFlatSlope(GetTileSlope(bridge_end))); if (IsValidTile(bridge_end) && - !IsBlockedByPreviousBridgeOrTunnel(current, current_tile, bridge_end)) { + !IsBlockedByPreviousBridgeOrTunnel(current, current_tile, bridge_end)) { assert(IsValidDiagDirection(DiagdirBetweenTiles(current_tile, bridge_end))); aystar->neighbours[aystar->num_neighbours].tile = bridge_end; aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; @@ -802,18 +817,19 @@ static void PublicRoad_FoundEndNode(AyStar *aystar, OpenListNode *current) auto end_tile = INVALID_TILE; - if (IsUpwardsSlope(tile, road_direction)) { + const Slope tile_slope = GetTileSlope(tile); + if (IsUpwardsSlope(tile_slope, road_direction)) { end_tile = BuildTunnel(path, path->parent->node.tile, true); - assert(IsValidTile(end_tile) && IsDownwardsSlope(end_tile, road_direction)); - } else if (IsDownwardsSlope(tile, road_direction)) { + assert(IsValidTile(end_tile) && IsDownwardsSlope(GetTileSlope(end_tile), road_direction)); + } else if (IsDownwardsSlope(tile_slope, road_direction)) { // Provide the function with the end tile, since we already know it, but still check the result. end_tile = BuildBridge(path, path->parent->node.tile, true); - assert(IsValidTile(end_tile) && IsUpwardsSlope(end_tile, road_direction)); + assert(IsValidTile(end_tile) && IsUpwardsSlope(GetTileSlope(end_tile), road_direction)); } else { // River bridge is the last possibility. - assert(GetTileSlope(tile) == SLOPE_FLAT); + assert(IsSufficientlyFlatSlope(tile_slope)); end_tile = BuildRiverBridge(path, road_direction, path->parent->node.tile, true); - assert(IsValidTile(end_tile) && GetTileSlope(end_tile) == SLOPE_FLAT); + assert(IsValidTile(end_tile) && IsSufficientlyFlatSlope(GetTileSlope(end_tile))); } } @@ -840,8 +856,9 @@ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode * if (distance > 1) { /* We are planning to build a bridge or tunnel. Make that much more expensive. */ const DiagDirection road_direction = DiagdirBetweenTiles(parent->path.node.tile, current->tile); - if (IsUpwardsSlope(parent->path.node.tile, road_direction)) { + if (IsUpwardsSlope(GetTileSlope(parent->path.node.tile), road_direction)) { /* Tunnel */ + /* Prevent excessively long tunnels */ if (distance > 30) return AYSTAR_INVALID_NODE; From 198ef11a2b8a601988c3d28064dad92ad42ad759 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Thu, 17 Jun 2021 02:52:36 +0200 Subject: [PATCH 31/45] Force the pathfinder to build serpentine roads --- src/road.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/road.cpp b/src/road.cpp index c24feb5b3c..73397c0aa4 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -885,6 +885,23 @@ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode * if (GetTileZ(parent->path.node.tile) != GetTileZ(current->tile)) { cost += COST_FOR_SLOPE; + + auto current_node = &parent->path; + auto parent_node = parent->path.parent; + + // Force the pathfinder to build serpentine roads by punishing every slope in the last couple of tiles. + for (int i = 0; i < 3; ++i) { + if (current_node == nullptr || parent_node == nullptr) { + break; + } + + if (GetTileZ(current_node->node.tile) != GetTileZ(parent_node->node.tile)) { + cost += COST_FOR_SLOPE; + } + + current_node = parent_node; + parent_node = current_node->parent; + } } } From a73cb9969560ab9336bb553647eaf4656013ab95 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Thu, 17 Jun 2021 03:23:27 +0200 Subject: [PATCH 32/45] Do not build bridges with additional specs Prior to this change, additional bridge sets like modular bridges would be used. Those might not be suitable for public roads. Those bridges should be left to the player. --- src/road.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index 73397c0aa4..b8fad7a926 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -461,7 +461,7 @@ static TileIndex BuildBridge(PathNode *current, TileIndex end_tile = INVALID_TIL std::vector available_bridge_types; - for (uint i = 0; i < MAX_BRIDGES; ++i) { + for (uint i = 0; i < 13; ++i) { if (CheckBridgeAvailability(i, GetTunnelBridgeLength(start_tile, end_tile)).Succeeded()) { available_bridge_types.push_back(i); } @@ -522,7 +522,7 @@ static TileIndex BuildRiverBridge(PathNode *current, const DiagDirection road_di std::vector available_bridge_types; - for (uint i = 0; i < MAX_BRIDGES; ++i) { + for (uint i = 0; i < 13; ++i) { if (CheckBridgeAvailability(i, GetTunnelBridgeLength(start_tile, end_tile)).Succeeded()) { available_bridge_types.push_back(i); } From b175203d54289ecb8a724ee43ca583e8d03864d8 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Thu, 17 Jun 2021 03:24:29 +0200 Subject: [PATCH 33/45] Add comment --- src/road.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/road.cpp b/src/road.cpp index b8fad7a926..8bf540e8b5 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -461,6 +461,8 @@ static TileIndex BuildBridge(PathNode *current, TileIndex end_tile = INVALID_TIL std::vector available_bridge_types; + // Only use the original first 13 bridge types since + // there might be stuff like modular bridges etc in the other ones. for (uint i = 0; i < 13; ++i) { if (CheckBridgeAvailability(i, GetTunnelBridgeLength(start_tile, end_tile)).Succeeded()) { available_bridge_types.push_back(i); @@ -522,6 +524,8 @@ static TileIndex BuildRiverBridge(PathNode *current, const DiagDirection road_di std::vector available_bridge_types; + // Only use the original first 13 bridge types since + // there might be stuff like modular bridges etc in the other ones. for (uint i = 0; i < 13; ++i) { if (CheckBridgeAvailability(i, GetTunnelBridgeLength(start_tile, end_tile)).Succeeded()) { available_bridge_types.push_back(i); From 3e771dc18d48c0fa0c96f016fc73b48231cc70ca Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Thu, 17 Jun 2021 09:33:15 +0200 Subject: [PATCH 34/45] Revert bug inducing change --- src/road.cpp | 39 +++++++-------------------------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index 8bf540e8b5..119b567f5d 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -843,8 +843,6 @@ static void PublicRoad_FoundEndNode(AyStar *aystar, OpenListNode *current) static const int32 BASE_COST_PER_TILE = 1; // Cost for existing road or tunnel/bridge. static const int32 COST_FOR_NEW_ROAD = 100; // Cost for building a new road. -static const int32 COST_FOR_NEW_BRIDGE = 500; // Cost for building a new bridge (per tile). -static const int32 COST_FOR_NEW_TUNNEL = 500; // Cost for building a new tunnel (per tile, with non-linear increase with length as in CmdBuildTunnel costs). static const int32 COST_FOR_SLOPE = 50; // Additional cost if the road heads up or down a slope. /** AyStar callback for getting the cost of the current node. */ @@ -852,40 +850,12 @@ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode * { int32 cost = 0; - int32 distance = DistanceManhattan(parent->path.node.tile, current->tile); + const int32 distance = DistanceManhattan(parent->path.node.tile, current->tile); if (IsTileType(current->tile, MP_ROAD) || IsTileType(current->tile, MP_TUNNELBRIDGE)) { cost += distance * BASE_COST_PER_TILE; } else { - if (distance > 1) { - /* We are planning to build a bridge or tunnel. Make that much more expensive. */ - const DiagDirection road_direction = DiagdirBetweenTiles(parent->path.node.tile, current->tile); - if (IsUpwardsSlope(GetTileSlope(parent->path.node.tile), road_direction)) { - /* Tunnel */ - - /* Prevent excessively long tunnels */ - if (distance > 30) return AYSTAR_INVALID_NODE; - - cost += 2 * COST_FOR_NEW_TUNNEL; - int32 middle_cost = 0; - int tiles_coef = 3; - int tiles_bump = 25; - for (int i = 1; i <= distance; i++) { - if (i == tiles_bump) { - tiles_coef++; - tiles_bump *= 2; - } - middle_cost += COST_FOR_NEW_TUNNEL; - middle_cost += middle_cost >> tiles_coef; - } - cost += middle_cost; - } else { - /* Bridge */ - cost += distance * COST_FOR_NEW_BRIDGE; - } - } else { - cost += distance * COST_FOR_NEW_ROAD; - } + cost += distance * COST_FOR_NEW_ROAD; if (GetTileZ(parent->path.node.tile) != GetTileZ(current->tile)) { cost += COST_FOR_SLOPE; @@ -907,6 +877,11 @@ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode * parent_node = current_node->parent; } } + + if (distance > 1) { + // We are planning to build a bridge or tunnel. Make that a bit more expensive. + cost += 6 * COST_FOR_SLOPE; + } } if (_settings_game.game_creation.build_public_roads == PRC_AVOID_CURVES && From a3d1b916d1b6e3c4bbb3877f0fc9c72644a58861 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Thu, 17 Jun 2021 09:40:16 +0200 Subject: [PATCH 35/45] Limit tunnel length at the proper place --- src/road.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/road.cpp b/src/road.cpp index 119b567f5d..c2315f03e1 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -382,12 +382,13 @@ static TileIndex BuildTunnel(PathNode *current, TileIndex end_tile = INVALID_TIL const TileIndexDiff delta = TileOffsByDiagDir(direction); end_tile = start_tile; int end_z; + const uint tunnel_length_limit = std::min(_settings_game.construction.max_tunnel_length, 30); for (int tunnel_length = 1;;tunnel_length++) { end_tile += delta; if (!IsValidTile(end_tile)) return INVALID_TILE; - if (tunnel_length > _settings_game.construction.max_tunnel_length) return INVALID_TILE; + if (tunnel_length > tunnel_length_limit) return INVALID_TILE; GetTileSlope(end_tile, &end_z); From 96bfcd587b62785ab4901c11f5c5aa0736e2b752 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Thu, 17 Jun 2021 17:00:33 +0100 Subject: [PATCH 36/45] Fix signedness mismatch warning in tunnel length check --- src/road.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/road.cpp b/src/road.cpp index c2315f03e1..51b5c0309a 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -384,7 +384,7 @@ static TileIndex BuildTunnel(PathNode *current, TileIndex end_tile = INVALID_TIL int end_z; const uint tunnel_length_limit = std::min(_settings_game.construction.max_tunnel_length, 30); - for (int tunnel_length = 1;;tunnel_length++) { + for (uint tunnel_length = 1;; tunnel_length++) { end_tile += delta; if (!IsValidTile(end_tile)) return INVALID_TILE; From 724474e4354229b870a360e263c1e172f00595f0 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Thu, 17 Jun 2021 17:01:47 +0100 Subject: [PATCH 37/45] Use MayTownBuildBridgeType/BSCF_NOT_AVAILABLE_TOWN for bridge type check --- src/road.cpp | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index 51b5c0309a..16786a6242 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -460,12 +460,11 @@ static TileIndex BuildBridge(PathNode *current, TileIndex end_tile = INVALID_TIL assert(!build_bridge || (IsValidTile(end_tile) && GetTileSlope(start_tile) == ComplementSlope(GetTileSlope(end_tile)))); - std::vector available_bridge_types; + const uint length = GetTunnelBridgeLength(start_tile, end_tile); - // Only use the original first 13 bridge types since - // there might be stuff like modular bridges etc in the other ones. - for (uint i = 0; i < 13; ++i) { - if (CheckBridgeAvailability(i, GetTunnelBridgeLength(start_tile, end_tile)).Succeeded()) { + std::vector available_bridge_types; + for (BridgeType i = 0; i < MAX_BRIDGES; ++i) { + if (MayTownBuildBridgeType(i) && CheckBridgeAvailability(i, length).Succeeded()) { available_bridge_types.push_back(i); } } @@ -523,12 +522,11 @@ static TileIndex BuildRiverBridge(PathNode *current, const DiagDirection road_di assert(!build_bridge || IsValidTile(end_tile)); - std::vector available_bridge_types; + const uint length = GetTunnelBridgeLength(start_tile, end_tile); - // Only use the original first 13 bridge types since - // there might be stuff like modular bridges etc in the other ones. - for (uint i = 0; i < 13; ++i) { - if (CheckBridgeAvailability(i, GetTunnelBridgeLength(start_tile, end_tile)).Succeeded()) { + std::vector available_bridge_types; + for (BridgeType i = 0; i < MAX_BRIDGES; ++i) { + if (MayTownBuildBridgeType(i) && CheckBridgeAvailability(i, length).Succeeded()) { available_bridge_types.push_back(i); } } @@ -860,7 +858,7 @@ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode * if (GetTileZ(parent->path.node.tile) != GetTileZ(current->tile)) { cost += COST_FOR_SLOPE; - + auto current_node = &parent->path; auto parent_node = parent->path.parent; @@ -869,11 +867,11 @@ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode * if (current_node == nullptr || parent_node == nullptr) { break; } - + if (GetTileZ(current_node->node.tile) != GetTileZ(parent_node->node.tile)) { cost += COST_FOR_SLOPE; } - + current_node = parent_node; parent_node = current_node->parent; } From 90e75871ad88ba5f44c818af11cb6ef52b1ae60b Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Thu, 17 Jun 2021 17:10:52 +0100 Subject: [PATCH 38/45] Fix bridge/tunnel building not using DC_AUTO This could result in existing infrastructure being removed at the bridge/tunnel far end --- src/road.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index 16786a6242..7d0a0e545c 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -408,7 +408,7 @@ static TileIndex BuildTunnel(PathNode *current, TileIndex end_tile = INVALID_TIL assert(!build_tunnel || (IsValidTile(end_tile) && GetTileSlope(start_tile) == ComplementSlope(GetTileSlope(end_tile)))); Backup cur_company(_current_company, OWNER_DEITY, FILE_LINE); - const auto build_tunnel_cmd = CmdBuildTunnel(start_tile, build_tunnel ? DC_EXEC : DC_NONE, _public_road_type | (TRANSPORT_ROAD << 8), 0); + const auto build_tunnel_cmd = CmdBuildTunnel(start_tile, DC_AUTO | (build_tunnel ? DC_EXEC : DC_NONE), _public_road_type | (TRANSPORT_ROAD << 8), 0); cur_company.Restore(); assert(!build_tunnel || build_tunnel_cmd.Succeeded()); @@ -475,7 +475,7 @@ static TileIndex BuildBridge(PathNode *current, TileIndex end_tile = INVALID_TIL const auto bridge_type = available_bridge_types[build_bridge ? RandomRange(uint32(available_bridge_types.size())) : 0]; Backup cur_company(_current_company, OWNER_DEITY, FILE_LINE); - const auto build_bridge_cmd = CmdBuildBridge(end_tile, build_bridge ? DC_EXEC : DC_NONE, start_tile, bridge_type | (_public_road_type << 8) | (TRANSPORT_ROAD << 15)); + const auto build_bridge_cmd = CmdBuildBridge(end_tile, DC_AUTO | (build_bridge ? DC_EXEC : DC_NONE), start_tile, bridge_type | (_public_road_type << 8) | (TRANSPORT_ROAD << 15)); cur_company.Restore(); assert(!build_bridge || build_bridge_cmd.Succeeded()); @@ -534,7 +534,7 @@ static TileIndex BuildRiverBridge(PathNode *current, const DiagDirection road_di const auto bridge_type = available_bridge_types[build_bridge ? RandomRange(uint32(available_bridge_types.size())) : 0]; Backup cur_company(_current_company, OWNER_DEITY, FILE_LINE); - const auto build_bridge_cmd = CmdBuildBridge(end_tile, build_bridge ? DC_EXEC : DC_NONE, start_tile, bridge_type | (_public_road_type << 8) | (TRANSPORT_ROAD << 15)); + const auto build_bridge_cmd = CmdBuildBridge(end_tile, DC_AUTO | (build_bridge ? DC_EXEC : DC_NONE), start_tile, bridge_type | (_public_road_type << 8) | (TRANSPORT_ROAD << 15)); cur_company.Restore(); assert(!build_bridge || build_bridge_cmd.Succeeded()); From 5e7b8ccd430d736e8556d7c2e4063017305b2219 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Thu, 17 Jun 2021 17:18:53 +0100 Subject: [PATCH 39/45] Only calculate input points once in IsBlockedByPreviousBridgeOrTunnel --- src/road.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index 7d0a0e545c..d4ea3a6c36 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -638,6 +638,13 @@ static bool IsBlockedByPreviousBridgeOrTunnel(OpenListNode *current, TileIndex s PathNode* start = ¤t->path; PathNode* end = current->path.parent; + Point start_b {}; + start_b.x = TileX(start_tile); + start_b.y = TileY(start_tile); + Point end_b {}; + end_b.x = TileX(end_tile); + end_b.y = TileY(end_tile); + while (end != nullptr) { Point start_a {}; start_a.x = TileX(start->node.tile); @@ -646,13 +653,6 @@ static bool IsBlockedByPreviousBridgeOrTunnel(OpenListNode *current, TileIndex s end_a.x = TileX(end->node.tile); end_a.y = TileY(end->node.tile); - Point start_b {}; - start_b.x = TileX(start_tile); - start_b.y = TileY(start_tile); - Point end_b {}; - end_b.x = TileX(end_tile); - end_b.y = TileY(end_tile); - if (!AreTilesAdjacent(start->node.tile, end->node.tile) && (AreIntersecting(start_a, end_a, start_b, end_b) || AreParallelOverlapping(start_a, end_a, start_b, end_b))) { return true; From 9683676df11e6887fbff1788d289842148c8a49f Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Thu, 17 Jun 2021 17:33:36 +0100 Subject: [PATCH 40/45] Use GetTileMaxZ for slope cost check to avoid overly penalising foundations --- src/road.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index d4ea3a6c36..26699fab65 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -856,7 +856,7 @@ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode * } else { cost += distance * COST_FOR_NEW_ROAD; - if (GetTileZ(parent->path.node.tile) != GetTileZ(current->tile)) { + if (GetTileMaxZ(parent->path.node.tile) != GetTileMaxZ(current->tile)) { cost += COST_FOR_SLOPE; auto current_node = &parent->path; @@ -868,7 +868,7 @@ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode * break; } - if (GetTileZ(current_node->node.tile) != GetTileZ(parent_node->node.tile)) { + if (GetTileMaxZ(current_node->node.tile) != GetTileMaxZ(parent_node->node.tile)) { cost += COST_FOR_SLOPE; } From 3a67065332cb1cd87aaeffb3135519b85d91350f Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Thu, 17 Jun 2021 22:35:27 +0100 Subject: [PATCH 41/45] Add a build public roads button to the scenario editor --- src/lang/english.txt | 2 ++ src/settings.cpp | 5 +++++ src/table/settings.ini | 4 +++- src/terraform_gui.cpp | 37 +++++++++++++++++++++++++++++++--- src/widgets/terraform_widget.h | 2 ++ 5 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/lang/english.txt b/src/lang/english.txt index 1ab7354042..0a8eb3665c 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -3396,6 +3396,8 @@ STR_TERRAFORM_TOOLTIP_GENERATE_RANDOM_LAND :{BLACK}Generate STR_TERRAFORM_SE_NEW_WORLD :{BLACK}Create new scenario STR_TERRAFORM_RESET_LANDSCAPE :{BLACK}Reset landscape STR_TERRAFORM_RESET_LANDSCAPE_TOOLTIP :{BLACK}Remove all company-owned property from the map +STR_TERRAFORM_PUBLIC_ROADS :{BLACK}Build public roads +STR_TERRAFORM_PUBLIC_ROADS_TOOLTIP :{BLACK}Build public roads between the towns on the map STR_QUERY_RESET_LANDSCAPE_CAPTION :{WHITE}Reset Landscape STR_RESET_LANDSCAPE_CONFIRMATION_TEXT :{WHITE}Are you sure you want to remove all company-owned property? diff --git a/src/settings.cpp b/src/settings.cpp index 6ec480aecc..1b42726e7d 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1536,6 +1536,11 @@ static bool ChangeTrackTypeSortMode(int32 p1) { return true; } +static bool PublicRoadsSettingChange(int32 p1) { + InvalidateWindowClassesData(WC_SCEN_LAND_GEN); + return true; +} + /** Checks if any settings are set to incorrect values, and sets them to correct values in that case. */ static void ValidateSettings() { diff --git a/src/table/settings.ini b/src/table/settings.ini index 14de0db585..befe6cfe2b 100644 --- a/src/table/settings.ini +++ b/src/table/settings.ini @@ -63,6 +63,7 @@ static bool UpdateLinkgraphColours(int32 p1); static bool ClimateThresholdModeChanged(int32 p1); static bool VelocityUnitsChanged(int32 p1); static bool ChangeTrackTypeSortMode(int32 p1); +static bool PublicRoadsSettingChange(int32 p1); static bool UpdateClientName(int32 p1); static bool UpdateServerPassword(int32 p1); @@ -4080,13 +4081,14 @@ xref = ""game_creation.build_public_roads"" base = GameSettings var = game_creation.build_public_roads type = SLE_UINT8 -guiflags = SGF_MULTISTRING | SGF_NEWGAME_ONLY +guiflags = SGF_MULTISTRING | SGF_NEWGAME_ONLY | SGF_SCENEDIT_TOO def = 0 min = 0 max = 2 str = STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS strhelp = STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS_HELPTEXT strval = STR_CONFIG_SETTING_BUILD_PUBLIC_ROADS_NONE +proc = PublicRoadsSettingChange patxname = ""public_roads.game_creation.build_public_roads"" ; locale diff --git a/src/terraform_gui.cpp b/src/terraform_gui.cpp index 41a71b7096..4b2f646a87 100644 --- a/src/terraform_gui.cpp +++ b/src/terraform_gui.cpp @@ -572,7 +572,12 @@ static const NWidgetPart _nested_scen_edit_land_gen_widgets[] = { NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_ETT_NEW_SCENARIO), SetMinimalSize(160, 12), SetFill(1, 0), SetDataTip(STR_TERRAFORM_SE_NEW_WORLD, STR_TERRAFORM_TOOLTIP_GENERATE_RANDOM_LAND), SetPadding(0, 2, 0, 2), NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_ETT_RESET_LANDSCAPE), SetMinimalSize(160, 12), - SetFill(1, 0), SetDataTip(STR_TERRAFORM_RESET_LANDSCAPE, STR_TERRAFORM_RESET_LANDSCAPE_TOOLTIP), SetPadding(1, 2, 2, 2), + SetFill(1, 0), SetDataTip(STR_TERRAFORM_RESET_LANDSCAPE, STR_TERRAFORM_RESET_LANDSCAPE_TOOLTIP), SetPadding(1, 2, 0, 2), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_ETT_SHOW_PUBLIC_ROADS), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_ETT_PUBLIC_ROADS), SetMinimalSize(160, 12), + SetFill(1, 0), SetDataTip(STR_TERRAFORM_PUBLIC_ROADS, STR_TERRAFORM_PUBLIC_ROADS_TOOLTIP), SetPadding(1, 2, 0, 2), + EndContainer(), + NWidget(NWID_SPACER), SetMinimalSize(0, 2), EndContainer(), }; @@ -617,8 +622,7 @@ struct ScenarioEditorLandscapeGenerationWindow : Window { ScenarioEditorLandscapeGenerationWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc) { this->CreateNestedTree(); - NWidgetStacked *show_desert = this->GetWidget(WID_ETT_SHOW_PLACE_DESERT); - show_desert->SetDisplayedPlane(_settings_game.game_creation.landscape == LT_TROPIC ? 0 : SZSP_NONE); + this->SetButtonStates(); this->FinishInitNested(window_number); this->last_user_action = WIDGET_LIST_END; } @@ -723,6 +727,12 @@ struct ScenarioEditorLandscapeGenerationWindow : Window { ShowQuery(STR_QUERY_RESET_LANDSCAPE_CAPTION, STR_RESET_LANDSCAPE_CONFIRMATION_TEXT, nullptr, ResetLandscapeConfirmationCallback); break; + case WID_ETT_PUBLIC_ROADS: { // Build public roads + extern void GeneratePublicRoads(); + GeneratePublicRoads(); + break; + } + default: NOT_REACHED(); } } @@ -798,6 +808,27 @@ struct ScenarioEditorLandscapeGenerationWindow : Window { this->SetDirty(); } + /** + * Some data on this window has become invalid. + * @param data Information about the changed data. + * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details. + */ + void OnInvalidateData(int data = 0, bool gui_scope = true) override + { + if (!gui_scope) return; + + this->SetButtonStates(); + this->ReInit(); + } + + void SetButtonStates() + { + NWidgetStacked *show_desert = this->GetWidget(WID_ETT_SHOW_PLACE_DESERT); + show_desert->SetDisplayedPlane(_settings_game.game_creation.landscape == LT_TROPIC ? 0 : SZSP_NONE); + NWidgetStacked *show_public_roads = this->GetWidget(WID_ETT_SHOW_PUBLIC_ROADS); + show_public_roads->SetDisplayedPlane(_settings_game.game_creation.build_public_roads != 0 ? 0 : SZSP_NONE); + } + static HotkeyList hotkeys; }; diff --git a/src/widgets/terraform_widget.h b/src/widgets/terraform_widget.h index 009d312458..b715e84b31 100644 --- a/src/widgets/terraform_widget.h +++ b/src/widgets/terraform_widget.h @@ -28,6 +28,7 @@ enum TerraformToolbarWidgets { /** Widgets of the #ScenarioEditorLandscapeGenerationWindow class. */ enum EditorTerraformToolbarWidgets { WID_ETT_SHOW_PLACE_DESERT, ///< Should the place desert button be shown? + WID_ETT_SHOW_PUBLIC_ROADS, ///< Should the public roads button be shown? WID_ETT_START, ///< Used for iterations. WID_ETT_DOTS = WID_ETT_START, ///< Invisible widget for rendering the terraform size on. WID_ETT_BUTTONS_START, ///< Start of pushable buttons. @@ -44,6 +45,7 @@ enum EditorTerraformToolbarWidgets { WID_ETT_DECREASE_SIZE, ///< Downwards arrow button to decrease terraforming size. WID_ETT_NEW_SCENARIO, ///< Button for generating a new scenario. WID_ETT_RESET_LANDSCAPE, ///< Button for removing all company-owned property. + WID_ETT_PUBLIC_ROADS, ///< Button for creating public roads. }; #endif /* WIDGETS_TERRAFORM_WIDGET_H */ From f53ed562006cf909095619e236e50c7bc779ee93 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Thu, 17 Jun 2021 22:48:23 +0100 Subject: [PATCH 42/45] Increase bridge/tunnel per-tile cost --- src/road.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/road.cpp b/src/road.cpp index 26699fab65..ec6453d559 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -880,6 +880,7 @@ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode * if (distance > 1) { // We are planning to build a bridge or tunnel. Make that a bit more expensive. cost += 6 * COST_FOR_SLOPE; + cost += distance * (COST_FOR_NEW_ROAD / 2); } } From d4fd7f62d1052fb8633af016e045262fa774adda Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Fri, 18 Jun 2021 04:24:28 +0200 Subject: [PATCH 43/45] Build a couple of cycles in the network by connecting each town to the three closest ones in its network --- src/road.cpp | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index ec6453d559..e0235dae6e 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -841,8 +841,8 @@ static void PublicRoad_FoundEndNode(AyStar *aystar, OpenListNode *current) } static const int32 BASE_COST_PER_TILE = 1; // Cost for existing road or tunnel/bridge. -static const int32 COST_FOR_NEW_ROAD = 100; // Cost for building a new road. -static const int32 COST_FOR_SLOPE = 50; // Additional cost if the road heads up or down a slope. +static const int32 COST_FOR_NEW_ROAD = 10; // Cost for building a new road. +static const int32 COST_FOR_SLOPE = 5; // Additional cost if the road heads up or down a slope. /** AyStar callback for getting the cost of the current node. */ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode *parent) @@ -880,7 +880,7 @@ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode * if (distance > 1) { // We are planning to build a bridge or tunnel. Make that a bit more expensive. cost += 6 * COST_FOR_SLOPE; - cost += distance * (COST_FOR_NEW_ROAD / 2); + cost += distance * COST_FOR_NEW_ROAD; } } @@ -933,6 +933,30 @@ struct TownNetwork std::vector towns; }; +void PostProcessNetworks(const std::vector>& town_networks) +{ + for (auto network : town_networks) { + std::vector towns(network->towns); + + for (auto town_a : network->towns) { + std::sort(towns.begin(), towns.end(), [&](const TileIndex& a, const TileIndex& b) { return DistanceManhattan(a, town_a) < DistanceManhattan(b, town_a); }); + + const auto second_clostest_town = *(towns.begin() + 2); + const auto third_clostest_town = *(towns.begin() + 3); + + AyStar finder {}; + { + FindPath(finder, town_a, second_clostest_town); + finder.Clear(); + FindPath(finder, town_a, third_clostest_town); + } + finder.Free(); + + IncreaseGeneratingWorldProgress(GWP_PUBLIC_ROADS); + } + } +} + /** * Build the public road network connecting towns using AyStar. */ @@ -956,7 +980,7 @@ void GeneratePublicRoads() return; } - SetGeneratingWorldProgress(GWP_PUBLIC_ROADS, uint(towns.size())); + SetGeneratingWorldProgress(GWP_PUBLIC_ROADS, uint(towns.size() * 2)); // Create a list of networks which also contain a value indicating how many times we failed to connect to them. std::vector> networks; @@ -1106,6 +1130,8 @@ void GeneratePublicRoads() IncreaseGeneratingWorldProgress(GWP_PUBLIC_ROADS); } + + PostProcessNetworks(networks); } /* ========================================================================= */ From 7a0150735e0c78ae5fbf0c564326fcc312466c9b Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Fri, 18 Jun 2021 18:39:18 +0200 Subject: [PATCH 44/45] Adjust the cost function again for better bridge and tunnel use --- src/road.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/road.cpp b/src/road.cpp index e0235dae6e..ac04f3d4c0 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -842,7 +842,7 @@ static void PublicRoad_FoundEndNode(AyStar *aystar, OpenListNode *current) static const int32 BASE_COST_PER_TILE = 1; // Cost for existing road or tunnel/bridge. static const int32 COST_FOR_NEW_ROAD = 10; // Cost for building a new road. -static const int32 COST_FOR_SLOPE = 5; // Additional cost if the road heads up or down a slope. +static const int32 COST_FOR_SLOPE = 50; // Additional cost if the road heads up or down a slope. /** AyStar callback for getting the cost of the current node. */ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode *parent) @@ -880,7 +880,7 @@ static int32 PublicRoad_CalculateG(AyStar *, AyStarNode *current, OpenListNode * if (distance > 1) { // We are planning to build a bridge or tunnel. Make that a bit more expensive. cost += 6 * COST_FOR_SLOPE; - cost += distance * COST_FOR_NEW_ROAD; + cost += distance * 2 * COST_FOR_NEW_ROAD; } } From c475fb12c043bd2d45343de118a8343815f7fd8f Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Fri, 18 Jun 2021 19:19:02 +0200 Subject: [PATCH 45/45] Add sanity check --- src/road.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/road.cpp b/src/road.cpp index ac04f3d4c0..a7f0f804dd 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -936,6 +936,10 @@ struct TownNetwork void PostProcessNetworks(const std::vector>& town_networks) { for (auto network : town_networks) { + if (network->towns.size() <= 3) { + continue; + } + std::vector towns(network->towns); for (auto town_a : network->towns) {