Add generation of public roads linking towns

pull/272/head
Andreas Schmitt 3 years ago committed by Jonathan G Rennison
parent d718ba62ef
commit 150e502cf9

@ -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();
}
}

@ -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

@ -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);

@ -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

@ -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.

@ -8,6 +8,11 @@
/** @file road.cpp Generic road related functions. */
#include "stdafx.h"
#include <algorithm>
#include <memory>
#include <numeric>
#include <unordered_map>
#include <vector>
#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 <numeric>
#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<TileIndex> _town_centers;
static std::vector<TileIndex> _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<TileIndex*>(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<BridgeType> 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<BridgeType> 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<TileIndex> 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(&current->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(&current->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(&current->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<TileIndex*>(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 = &current->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<TileIndex> 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<pair<uint, shared_ptr<vector<TileIndex>>>> town_networks;
unordered_map<TileIndex, shared_ptr<vector<TileIndex>>> 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<vector<TileIndex>>();
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<bool(pair<uint, shared_ptr<vector<TileIndex>>>)> 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<vector<TileIndex>>();
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<int>(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

@ -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"));

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

@ -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

Loading…
Cancel
Save