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/landscape.cpp b/src/landscape.cpp index 301e63472b..70b06161b4 100644 --- a/src/landscape.cpp +++ b/src/landscape.cpp @@ -1209,15 +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) { - /* 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; if (!IsWaterTile(tile)) { MakeRiver(tile, Random()); @@ -1241,17 +1233,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 +1248,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/lang/english.txt b/src/lang/english.txt index 08d7dc5b4b..0a8eb3665c 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 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 + 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 @@ -3390,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? @@ -3774,6 +3782,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/pathfinder/npf/aystar.cpp b/src/pathfinder/npf/aystar.cpp index b14053b7c0..e68146bbde 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); @@ -132,8 +138,9 @@ 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 */ 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"); @@ -290,11 +310,14 @@ 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); + /* 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..f8cb187c83 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; @@ -144,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 */ @@ -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); 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 a578bd953d..a7f0f804dd 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -8,6 +8,12 @@ /** @file road.cpp Generic road related functions. */ #include "stdafx.h" +#include +#include +#include +#include +#include +#include #include "rail_map.h" #include "road_map.h" #include "water_map.h" @@ -18,13 +24,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. * @@ -303,3 +326,818 @@ RoadTypes ExistingRoadTypes(CompanyID c) return known_roadtypes; } + + +/* ========================================================================= */ +/* 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 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 slope along a certain direction is going up an inclined slope. */ +static bool IsUpwardsSlope(const Slope slope, DiagDirection road_direction) +{ + if (!IsInclinedSlope(slope)) return false; + + const auto slope_direction = GetInclinedSlopeDirection(slope); + + return road_direction == slope_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) +{ + if (!IsInclinedSlope(slope)) return false; + + const auto slope_direction = GetInclinedSlopeDirection(slope); + + 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; + 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; + const uint tunnel_length_limit = std::min(_settings_game.construction.max_tunnel_length, 30); + + for (uint tunnel_length = 1;; tunnel_length++) { + end_tile += delta; + + if (!IsValidTile(end_tile)) return INVALID_TILE; + if (tunnel_length > tunnel_length_limit) 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, 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()); + 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; + + // 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 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 (; + IsValidTile(tile) && + (bridge_length <= bridge_length_limit) && + (GetTileZ(start_tile) < (GetTileZ(tile) + _settings_game.construction.max_bridge_height)) && + (GetTileZ(tile) <= GetTileZ(start_tile)); + tile += TileOffsByDiagDir(direction), bridge_length++) { + + 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) && !IsCoastTile(end_tile)) return INVALID_TILE; + } + + assert(!build_bridge || (IsValidTile(end_tile) && GetTileSlope(start_tile) == ComplementSlope(GetTileSlope(end_tile)))); + + const uint length = GetTunnelBridgeLength(start_tile, end_tile); + + 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); + } + } + + 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, 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()); + 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; + 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. + // 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) <= std::min(_settings_game.construction.max_bridge_length, (uint16)3)) && + (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) <= start_tile_z && + IsSufficientlyFlatSlope(GetTileSlope(tile))) { + end_tile = tile; + break; + } + } + + if (!IsValidTile(end_tile)) 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)); + + const uint length = GetTunnelBridgeLength(start_tile, end_tile); + + 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); + } + } + + 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, 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()); + 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; + + 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); + + return (tunnel_direction == forward_direction); + } + + if (!IsTileType(tile, MP_CLEAR) && !IsTileType(tile, MP_TREES) && !IsTileType(tile, MP_ROAD) && !IsCoastTile(tile)) return false; + + const Slope slope = GetTileSlope(tile); + if (IsSteepSlope(slope)) return false; + + const Slope foundationSlope = GetFoundationSlope(tile); + + /* 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); + + if (slope_direction != forward_direction && ReverseDiagDir(slope_direction) != forward_direction) { + return false; + } + } 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; +} + +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; + + 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); + start_a.y = TileY(start->node.tile); + Point end_a {}; + end_a.x = TileX(end->node.tile); + end_a.y = TileY(end->node.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) +{ + 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 (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); + + 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); + aystar->neighbours[aystar->num_neighbours].tile = tunnel_bridge_end; + 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); + + if (neighbour == previous_tile) { + continue; + } + + if (IsValidNeighbourOfPreviousTile(neighbour, current_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 (IsValidTile(previous_tile)) { + 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_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 (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) || IsSufficientlyFlatSlope(GetTileSlope(bridge_end))); + + 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; + 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; + + 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(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(GetTileSlope(end_tile), road_direction)); + } else { + // River bridge is the last possibility. + assert(IsSufficientlyFlatSlope(tile_slope)); + end_tile = BuildRiverBridge(path, road_direction, path->parent->node.tile, true); + assert(IsValidTile(end_tile) && IsSufficientlyFlatSlope(GetTileSlope(end_tile))); + } + } + + child = path; + } +} + +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 = 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; + + 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 { + cost += distance * COST_FOR_NEW_ROAD; + + if (GetTileMaxZ(parent->path.node.tile) != GetTileMaxZ(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 (GetTileMaxZ(current_node->node.tile) != GetTileMaxZ(parent_node->node.tile)) { + cost += COST_FOR_SLOPE; + } + + current_node = parent_node; + 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; + cost += distance * 2 * 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; +} + +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; + + 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(); + } + + const bool found_path = (result == AYSTAR_FOUND_END_NODE); + + return found_path; +} + +struct TownNetwork +{ + uint failures_to_connect {}; + std::vector towns; +}; + +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) { + 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. +*/ +void GeneratePublicRoads() +{ + if (_settings_game.game_creation.build_public_roads == PRC_NONE) return; + + _town_centers.clear(); + _towns_visited_along_the_way.clear(); + + std::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() * 2)); + + // Create a list of networks which also contain a value indicating how many times we failed to connect to them. + std::vector> networks; + std::unordered_map> town_to_network_map; + + std::sort(towns.begin(), towns.end(), [&](auto a, auto b) { return DistanceFromEdge(a) > DistanceFromEdge(b); }); + + TileIndex main_town = *towns.begin(); + towns.erase(towns.begin()); + + _public_road_type = GetTownRoadType(Town::GetByTile(main_town)); + std::unordered_set checked_towns; + + auto main_network = std::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; + + IncreaseGeneratingWorldProgress(GWP_PUBLIC_ROADS); + + auto town_network_distance = [](const TileIndex town, const std::shared_ptr &network) { + int32 best = INT32_MAX; + for (TileIndex t : network->towns) { + best = std::min(best, DistanceManhattan(t, town)); + } + return best; + }; + + 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. + _towns_visited_along_the_way.clear(); + + checked_towns.clear(); + + auto reachable_from_town = town_to_network_map.find(start_town); + bool found_path = false; + + if (reachable_from_town != town_to_network_map.end()) { + auto reachable_network = reachable_from_town->second; + + 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); + + AyStar finder {}; + { + found_path = FindPath(finder, start_town, end_town); + } + 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; + } + + } else { + town_to_network_map.erase(reachable_from_town); + reachable_network->failures_to_connect++; + } + } + + if (!found_path) { + // Sort networks by failed connection attempts, so we try the most likely one first. + 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); + }); + + 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; + } + + // 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()) { + return false; + } + + checked_towns.emplace(end_town); + + AyStar finder {}; + { + found_path = FindPath(finder, start_town, end_town); + } + 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++; + } + + return found_path; + }; + + std::vector>::iterator networks_end; + + if (networks.size() > 5) { + networks_end = networks.begin() + 5; + } else { + networks_end = networks.end(); + } + + std::vector> sampled_networks; + std::copy(networks.begin(), networks_end, std::back_inserter(sampled_networks)); + 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 (!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 = std::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 += towns_already_in_networks; + town_to_network_map[start_town] = new_network; + networks.push_back(new_network); + + for (const TileIndex visited_town : _towns_visited_along_the_way) { + town_to_network_map[visited_town] = new_network; + } + } + } + + IncreaseGeneratingWorldProgress(GWP_PUBLIC_ROADS); + } + + PostProcessNetworks(networks); +} + +/* ========================================================================= */ +/* END PUBLIC ROADS */ +/* ========================================================================= */ 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/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..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); @@ -4072,10 +4073,23 @@ strhelp = STR_CONFIG_SETTING_HEIGHT_ROCKS_HELPTEXT strval = STR_JUST_COMMA patxname = ""rocks.game_creation.height_affects_rocks"" -;;game_creation.build_public_roads -[SDT_NULL] -length = 1 +[SDT_XREF] extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_JOKERPP) +xref = ""game_creation.build_public_roads"" + +[SDT_VAR] +base = GameSettings +var = game_creation.build_public_roads +type = SLE_UINT8 +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 */