From 150e502cf9d812aae9878c6de7abec585acec200 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Sat, 12 Jun 2021 09:56:59 +0200 Subject: [PATCH] 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