Merge PR #272 (public roads) into jgrpp
This commit is contained in:
@@ -42,6 +42,7 @@ void GenerateClearTile();
|
|||||||
void GenerateIndustries();
|
void GenerateIndustries();
|
||||||
void GenerateObjects();
|
void GenerateObjects();
|
||||||
void GenerateTrees();
|
void GenerateTrees();
|
||||||
|
void GeneratePublicRoads();
|
||||||
|
|
||||||
void StartupEconomy();
|
void StartupEconomy();
|
||||||
void StartupCompanies();
|
void StartupCompanies();
|
||||||
@@ -140,6 +141,7 @@ static void _GenerateWorld()
|
|||||||
GenerateIndustries();
|
GenerateIndustries();
|
||||||
GenerateObjects();
|
GenerateObjects();
|
||||||
GenerateTrees();
|
GenerateTrees();
|
||||||
|
GeneratePublicRoads();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -78,6 +78,7 @@ enum GenWorldProgress {
|
|||||||
GWP_INDUSTRY, ///< Generate industries
|
GWP_INDUSTRY, ///< Generate industries
|
||||||
GWP_OBJECT, ///< Generate objects (radio tower, light houses)
|
GWP_OBJECT, ///< Generate objects (radio tower, light houses)
|
||||||
GWP_TREE, ///< Generate trees
|
GWP_TREE, ///< Generate trees
|
||||||
|
GWP_PUBLIC_ROADS,///< Generate public roads
|
||||||
GWP_GAME_INIT, ///< Initialize the game
|
GWP_GAME_INIT, ///< Initialize the game
|
||||||
GWP_RUNTILELOOP, ///< Runs the tile loop 1280 times to make snow etc
|
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
|
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_SCENEDIT_TOOLBAR_INDUSTRY_GENERATION,
|
||||||
STR_GENERATION_OBJECT_GENERATION,
|
STR_GENERATION_OBJECT_GENERATION,
|
||||||
STR_GENERATION_TREE_GENERATION,
|
STR_GENERATION_TREE_GENERATION,
|
||||||
|
STR_GENERATION_PUBLIC_ROADS_GENERATION,
|
||||||
STR_GENERATION_SETTINGUP_GAME,
|
STR_GENERATION_SETTINGUP_GAME,
|
||||||
STR_GENERATION_PREPARING_TILELOOP,
|
STR_GENERATION_PREPARING_TILELOOP,
|
||||||
STR_GENERATION_PREPARING_SCRIPT,
|
STR_GENERATION_PREPARING_SCRIPT,
|
||||||
@@ -1568,7 +1569,7 @@ void ShowGenerateWorldProgress()
|
|||||||
|
|
||||||
static void _SetGeneratingWorldProgress(GenWorldProgress cls, uint progress, uint total)
|
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);
|
static_assert(lengthof(percent_table) == GWP_CLASS_COUNT + 1);
|
||||||
assert(cls < GWP_CLASS_COUNT);
|
assert(cls < GWP_CLASS_COUNT);
|
||||||
|
|
||||||
|
@@ -1209,15 +1209,7 @@ static bool RiverMakeWider(TileIndex tile, void *data)
|
|||||||
/* AyStar callback when an route has been found. */
|
/* AyStar callback when an route has been found. */
|
||||||
static void River_FoundEndNode(AyStar *aystar, OpenListNode *current)
|
static void River_FoundEndNode(AyStar *aystar, OpenListNode *current)
|
||||||
{
|
{
|
||||||
/* Count river length. */
|
|
||||||
uint length = 0;
|
|
||||||
|
|
||||||
for (PathNode *path = ¤t->path; path != nullptr; path = path->parent) {
|
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;
|
TileIndex tile = path->node.tile;
|
||||||
if (!IsWaterTile(tile)) {
|
if (!IsWaterTile(tile)) {
|
||||||
MakeRiver(tile, Random());
|
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.
|
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.
|
* Actually build the river between the begin and end tiles using AyStar.
|
||||||
* @param begin The begin of the river.
|
* @param begin The begin of the river.
|
||||||
@@ -1267,7 +1248,7 @@ static void BuildRiver(TileIndex begin, TileIndex end)
|
|||||||
finder.FoundEndNode = River_FoundEndNode;
|
finder.FoundEndNode = River_FoundEndNode;
|
||||||
finder.user_target = &end;
|
finder.user_target = &end;
|
||||||
|
|
||||||
finder.Init(River_Hash, 1 << RIVER_HASH_SIZE);
|
finder.Init(1 << RIVER_HASH_SIZE);
|
||||||
|
|
||||||
AyStarNode start;
|
AyStarNode start;
|
||||||
start.tile = begin;
|
start.tile = begin;
|
||||||
|
@@ -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 :Arctic tree range: {STRING2}
|
||||||
STR_CONFIG_SETTING_TREES_AROUND_SNOWLINE_RANGE_HELPTEXT :Approximate range of arctic trees around snow line
|
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 :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_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
|
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_SE_NEW_WORLD :{BLACK}Create new scenario
|
||||||
STR_TERRAFORM_RESET_LANDSCAPE :{BLACK}Reset landscape
|
STR_TERRAFORM_RESET_LANDSCAPE :{BLACK}Reset landscape
|
||||||
STR_TERRAFORM_RESET_LANDSCAPE_TOOLTIP :{BLACK}Remove all company-owned property from the map
|
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_QUERY_RESET_LANDSCAPE_CAPTION :{WHITE}Reset Landscape
|
||||||
STR_RESET_LANDSCAPE_CONFIRMATION_TEXT :{WHITE}Are you sure you want to remove all company-owned property?
|
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_WORLD_GENERATION :{BLACK}World generation
|
||||||
STR_GENERATION_RIVER_GENERATION :{BLACK}River generation
|
STR_GENERATION_RIVER_GENERATION :{BLACK}River generation
|
||||||
STR_GENERATION_TREE_GENERATION :{BLACK}Tree 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_OBJECT_GENERATION :{BLACK}Object generation
|
||||||
STR_GENERATION_CLEARING_TILES :{BLACK}Rough and rocky area generation
|
STR_GENERATION_CLEARING_TILES :{BLACK}Rough and rocky area generation
|
||||||
STR_GENERATION_SETTINGUP_GAME :{BLACK}Setting up game
|
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));
|
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.
|
* Determines the DiagDirection to get from one tile to another.
|
||||||
* The tiles do not necessarily have to be adjacent.
|
* The tiles do not necessarily have to be adjacent.
|
||||||
|
@@ -26,6 +26,7 @@
|
|||||||
#include "aystar.h"
|
#include "aystar.h"
|
||||||
|
|
||||||
#include "../../safeguards.h"
|
#include "../../safeguards.h"
|
||||||
|
#include "core/mem_func.hpp"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This looks in the hash whether a node exists in the closed list.
|
* This looks in the hash whether a node exists in the closed list.
|
||||||
@@ -34,7 +35,9 @@
|
|||||||
*/
|
*/
|
||||||
PathNode *AyStar::ClosedListIsInList(const AyStarNode *node)
|
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)
|
void AyStar::ClosedListAdd(const PathNode *node)
|
||||||
{
|
{
|
||||||
/* Add a node to the ClosedList */
|
/* Add a node to the ClosedList */
|
||||||
PathNode *new_node = MallocT<PathNode>(1);
|
const auto new_node = MallocT<PathNode>(1);
|
||||||
*new_node = *node;
|
*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)
|
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. */
|
/* Return the item the Queue returns.. the best next OpenList item. */
|
||||||
OpenListNode *res = (OpenListNode*)this->openlist_queue.Pop();
|
OpenListNode *res = (OpenListNode*)this->openlist_queue.Pop();
|
||||||
if (res != nullptr) {
|
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;
|
return res;
|
||||||
@@ -87,7 +93,7 @@ void AyStar::OpenListAdd(PathNode *parent, const AyStarNode *node, int f, int g)
|
|||||||
new_node->g = g;
|
new_node->g = g;
|
||||||
new_node->path.parent = parent;
|
new_node->path.parent = parent;
|
||||||
new_node->path.node = *node;
|
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 */
|
/* Add it to the queue */
|
||||||
this->openlist_queue.Push(new_node, f);
|
this->openlist_queue.Push(new_node, f);
|
||||||
@@ -132,8 +138,9 @@ void AyStar::CheckTile(AyStarNode *current, OpenListNode *parent)
|
|||||||
if (check != nullptr) {
|
if (check != nullptr) {
|
||||||
uint i;
|
uint i;
|
||||||
/* Yes, check if this g value is lower.. */
|
/* 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);
|
this->openlist_queue.Delete(check, 0);
|
||||||
|
|
||||||
/* It is lower, so change it to this item */
|
/* It is lower, so change it to this item */
|
||||||
check->g = new_g;
|
check->g = new_g;
|
||||||
check->path.parent = closedlist_parent;
|
check->path.parent = closedlist_parent;
|
||||||
@@ -191,7 +198,7 @@ int AyStar::Loop()
|
|||||||
/* Free the node */
|
/* Free the node */
|
||||||
free(current);
|
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 */
|
/* We've expanded enough nodes */
|
||||||
return AYSTAR_LIMIT_REACHED;
|
return AYSTAR_LIMIT_REACHED;
|
||||||
} else {
|
} else {
|
||||||
@@ -208,8 +215,14 @@ void AyStar::Free()
|
|||||||
this->openlist_queue.Free(false);
|
this->openlist_queue.Free(false);
|
||||||
/* 2nd argument above is false, below is true, to free the values only
|
/* 2nd argument above is false, below is true, to free the values only
|
||||||
* once */
|
* once */
|
||||||
this->openlist_hash.Delete(true);
|
for (const auto& pair : this->openlist_hash) {
|
||||||
this->closedlist_hash.Delete(true);
|
free(pair.second);
|
||||||
|
}
|
||||||
|
this->openlist_hash.clear();
|
||||||
|
for (const auto& pair : this->closedlist_hash) {
|
||||||
|
free(pair.second);
|
||||||
|
}
|
||||||
|
this->closedlist_hash.clear();
|
||||||
#ifdef AYSTAR_DEBUG
|
#ifdef AYSTAR_DEBUG
|
||||||
printf("[AyStar] Memory free'd\n");
|
printf("[AyStar] Memory free'd\n");
|
||||||
#endif
|
#endif
|
||||||
@@ -225,8 +238,15 @@ void AyStar::Clear()
|
|||||||
* the hash. */
|
* the hash. */
|
||||||
this->openlist_queue.Clear(false);
|
this->openlist_queue.Clear(false);
|
||||||
/* Clean the hashes */
|
/* Clean the hashes */
|
||||||
this->openlist_hash.Clear(true);
|
for (const auto& pair : this->openlist_hash) {
|
||||||
this->closedlist_hash.Clear(true);
|
free(pair.second);
|
||||||
|
}
|
||||||
|
this->openlist_hash.clear();
|
||||||
|
|
||||||
|
for (const auto& pair : this->closedlist_hash) {
|
||||||
|
free(pair.second);
|
||||||
|
}
|
||||||
|
this->closedlist_hash.clear();
|
||||||
|
|
||||||
#ifdef AYSTAR_DEBUG
|
#ifdef AYSTAR_DEBUG
|
||||||
printf("[AyStar] Cleared AyStar\n");
|
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
|
* Initialize an #AyStar. You should fill all appropriate fields before
|
||||||
* calling #Init (see the declaration of #AyStar for which fields are internal).
|
* 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 */
|
/* Allocated the Hash for the OpenList and ClosedList */
|
||||||
this->openlist_hash.Init(hash, num_buckets);
|
this->openlist_hash.reserve(num_buckets);
|
||||||
this->closedlist_hash.Init(hash, num_buckets);
|
this->closedlist_hash.reserve(num_buckets);
|
||||||
|
|
||||||
/* Set up our sorting queue
|
/* Set up our sorting queue
|
||||||
* BinaryHeap allocates a block of 1024 nodes
|
* BinaryHeap allocates a block of 1024 nodes
|
||||||
|
@@ -17,6 +17,9 @@
|
|||||||
#define AYSTAR_H
|
#define AYSTAR_H
|
||||||
|
|
||||||
#include "queue.h"
|
#include "queue.h"
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "../../tile_type.h"
|
#include "../../tile_type.h"
|
||||||
#include "../../track_type.h"
|
#include "../../track_type.h"
|
||||||
|
|
||||||
@@ -57,6 +60,15 @@ struct OpenListNode {
|
|||||||
PathNode path;
|
PathNode path;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PairHash {
|
||||||
|
public:
|
||||||
|
template <typename T, typename U>
|
||||||
|
std::size_t operator()(const std::pair<T, U> &x) const
|
||||||
|
{
|
||||||
|
return std::hash<T>()(x.first) ^ std::hash<U>()(x.second);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
bool CheckIgnoreFirstTile(const PathNode *node);
|
bool CheckIgnoreFirstTile(const PathNode *node);
|
||||||
|
|
||||||
struct AyStar;
|
struct AyStar;
|
||||||
@@ -144,7 +156,7 @@ struct AyStar {
|
|||||||
AyStarNode neighbours[12];
|
AyStarNode neighbours[12];
|
||||||
byte num_neighbours;
|
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
|
/* These will contain the methods for manipulating the AyStar. Only
|
||||||
* Main() should be called externally */
|
* Main() should be called externally */
|
||||||
@@ -156,9 +168,9 @@ struct AyStar {
|
|||||||
void CheckTile(AyStarNode *current, OpenListNode *parent);
|
void CheckTile(AyStarNode *current, OpenListNode *parent);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Hash closedlist_hash; ///< The actual closed list.
|
std::unordered_map<std::pair<TileIndex, Trackdir>, PathNode*, PairHash> closedlist_hash;
|
||||||
BinaryHeap openlist_queue; ///< The open queue.
|
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<std::pair<TileIndex, Trackdir>, OpenListNode*, PairHash> openlist_hash;
|
||||||
|
|
||||||
void OpenListAdd(PathNode *parent, const AyStarNode *node, int f, int g);
|
void OpenListAdd(PathNode *parent, const AyStarNode *node, int f, int g);
|
||||||
OpenListNode *OpenListIsInList(const AyStarNode *node);
|
OpenListNode *OpenListIsInList(const AyStarNode *node);
|
||||||
|
@@ -131,24 +131,6 @@ static uint NPFDistanceTrack(TileIndex t0, TileIndex t1)
|
|||||||
return diagTracks * NPF_TILE_LENGTH + straightTracks * NPF_TILE_LENGTH * STRAIGHT_TRACK_LENGTH;
|
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)
|
static int32 NPFCalcZero(AyStar *as, AyStarNode *current, OpenListNode *parent)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1127,7 +1109,7 @@ void InitializeNPF()
|
|||||||
static bool first_init = true;
|
static bool first_init = true;
|
||||||
if (first_init) {
|
if (first_init) {
|
||||||
first_init = false;
|
first_init = false;
|
||||||
_npf_aystar.Init(NPFHash, NPF_HASH_SIZE);
|
_npf_aystar.Init(NPF_HASH_SIZE);
|
||||||
} else {
|
} else {
|
||||||
_npf_aystar.Clear();
|
_npf_aystar.Clear();
|
||||||
}
|
}
|
||||||
|
838
src/road.cpp
838
src/road.cpp
@@ -8,6 +8,12 @@
|
|||||||
/** @file road.cpp Generic road related functions. */
|
/** @file road.cpp Generic road related functions. */
|
||||||
|
|
||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
|
#include <numeric>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
#include "rail_map.h"
|
#include "rail_map.h"
|
||||||
#include "road_map.h"
|
#include "road_map.h"
|
||||||
#include "water_map.h"
|
#include "water_map.h"
|
||||||
@@ -18,13 +24,30 @@
|
|||||||
#include "date_func.h"
|
#include "date_func.h"
|
||||||
#include "landscape.h"
|
#include "landscape.h"
|
||||||
#include "road.h"
|
#include "road.h"
|
||||||
|
#include "town.h"
|
||||||
|
#include "pathfinder/npf/aystar.h"
|
||||||
|
#include "tunnelbridge.h"
|
||||||
#include "road_func.h"
|
#include "road_func.h"
|
||||||
#include "roadveh.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"
|
#include "safeguards.h"
|
||||||
|
|
||||||
uint32 _road_layout_change_counter = 0;
|
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.
|
* Return if the tile is a valid tile for a crossing.
|
||||||
*
|
*
|
||||||
@@ -303,3 +326,818 @@ RoadTypes ExistingRoadTypes(CompanyID c)
|
|||||||
|
|
||||||
return known_roadtypes;
|
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<TileIndex> _town_centers;
|
||||||
|
static std::vector<TileIndex> _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<uint>(_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<uint>(_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<BridgeType> 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<BridgeType> 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<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 = ¤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<TileIndex*>(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<TileIndex> towns;
|
||||||
|
};
|
||||||
|
|
||||||
|
void PostProcessNetworks(const std::vector<std::shared_ptr<TownNetwork>>& 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<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() * 2));
|
||||||
|
|
||||||
|
// Create a list of networks which also contain a value indicating how many times we failed to connect to them.
|
||||||
|
std::vector<std::shared_ptr<TownNetwork>> networks;
|
||||||
|
std::unordered_map<TileIndex, std::shared_ptr<TownNetwork>> 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<TileIndex> checked_towns;
|
||||||
|
|
||||||
|
auto main_network = std::make_shared<TownNetwork>();
|
||||||
|
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<TownNetwork> &network) {
|
||||||
|
int32 best = INT32_MAX;
|
||||||
|
for (TileIndex t : network->towns) {
|
||||||
|
best = std::min<int32>(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<TownNetwork> &a, const std::shared_ptr<TownNetwork> &b) {
|
||||||
|
return town_network_distance(start_town, a) < town_network_distance(start_town, b);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto can_reach = [&](const std::shared_ptr<TownNetwork> &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<std::shared_ptr<TownNetwork>>::iterator networks_end;
|
||||||
|
|
||||||
|
if (networks.size() > 5) {
|
||||||
|
networks_end = networks.begin() + 5;
|
||||||
|
} else {
|
||||||
|
networks_end = networks.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<TownNetwork>> 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<TownNetwork> &a, const std::shared_ptr<TownNetwork> &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<TownNetwork>();
|
||||||
|
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<TownNetwork> &network) {
|
||||||
|
return accumulator + static_cast<int>(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 */
|
||||||
|
/* ========================================================================= */
|
||||||
|
@@ -1536,6 +1536,11 @@ static bool ChangeTrackTypeSortMode(int32 p1) {
|
|||||||
return true;
|
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. */
|
/** Checks if any settings are set to incorrect values, and sets them to correct values in that case. */
|
||||||
static void ValidateSettings()
|
static void ValidateSettings()
|
||||||
{
|
{
|
||||||
|
@@ -2036,6 +2036,7 @@ static SettingsContainer &GetSettingsTree()
|
|||||||
genworld->Add(new SettingEntry("economy.initial_city_size"));
|
genworld->Add(new SettingEntry("economy.initial_city_size"));
|
||||||
genworld->Add(new SettingEntry("economy.town_layout"));
|
genworld->Add(new SettingEntry("economy.town_layout"));
|
||||||
genworld->Add(new SettingEntry("economy.town_min_distance"));
|
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("difficulty.industry_density"));
|
||||||
genworld->Add(new SettingEntry("gui.pause_on_newgame"));
|
genworld->Add(new SettingEntry("gui.pause_on_newgame"));
|
||||||
genworld->Add(new SettingEntry("game_creation.ending_year"));
|
genworld->Add(new SettingEntry("game_creation.ending_year"));
|
||||||
|
@@ -388,6 +388,7 @@ struct GameCreationSettings {
|
|||||||
bool lakes_allowed_in_deserts; ///< are lakes allowed in deserts?
|
bool lakes_allowed_in_deserts; ///< are lakes allowed in deserts?
|
||||||
uint8 amount_of_rocks; ///< the amount of rocks
|
uint8 amount_of_rocks; ///< the amount of rocks
|
||||||
uint8 height_affects_rocks; ///< the affect that map height has on 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 */
|
/** Settings related to construction in-game */
|
||||||
|
@@ -63,6 +63,7 @@ static bool UpdateLinkgraphColours(int32 p1);
|
|||||||
static bool ClimateThresholdModeChanged(int32 p1);
|
static bool ClimateThresholdModeChanged(int32 p1);
|
||||||
static bool VelocityUnitsChanged(int32 p1);
|
static bool VelocityUnitsChanged(int32 p1);
|
||||||
static bool ChangeTrackTypeSortMode(int32 p1);
|
static bool ChangeTrackTypeSortMode(int32 p1);
|
||||||
|
static bool PublicRoadsSettingChange(int32 p1);
|
||||||
|
|
||||||
static bool UpdateClientName(int32 p1);
|
static bool UpdateClientName(int32 p1);
|
||||||
static bool UpdateServerPassword(int32 p1);
|
static bool UpdateServerPassword(int32 p1);
|
||||||
@@ -4072,10 +4073,23 @@ strhelp = STR_CONFIG_SETTING_HEIGHT_ROCKS_HELPTEXT
|
|||||||
strval = STR_JUST_COMMA
|
strval = STR_JUST_COMMA
|
||||||
patxname = ""rocks.game_creation.height_affects_rocks""
|
patxname = ""rocks.game_creation.height_affects_rocks""
|
||||||
|
|
||||||
;;game_creation.build_public_roads
|
[SDT_XREF]
|
||||||
[SDT_NULL]
|
|
||||||
length = 1
|
|
||||||
extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_JOKERPP)
|
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
|
; locale
|
||||||
|
|
||||||
|
@@ -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),
|
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),
|
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),
|
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(),
|
EndContainer(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -617,8 +622,7 @@ struct ScenarioEditorLandscapeGenerationWindow : Window {
|
|||||||
ScenarioEditorLandscapeGenerationWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc)
|
ScenarioEditorLandscapeGenerationWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc)
|
||||||
{
|
{
|
||||||
this->CreateNestedTree();
|
this->CreateNestedTree();
|
||||||
NWidgetStacked *show_desert = this->GetWidget<NWidgetStacked>(WID_ETT_SHOW_PLACE_DESERT);
|
this->SetButtonStates();
|
||||||
show_desert->SetDisplayedPlane(_settings_game.game_creation.landscape == LT_TROPIC ? 0 : SZSP_NONE);
|
|
||||||
this->FinishInitNested(window_number);
|
this->FinishInitNested(window_number);
|
||||||
this->last_user_action = WIDGET_LIST_END;
|
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);
|
ShowQuery(STR_QUERY_RESET_LANDSCAPE_CAPTION, STR_RESET_LANDSCAPE_CONFIRMATION_TEXT, nullptr, ResetLandscapeConfirmationCallback);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case WID_ETT_PUBLIC_ROADS: { // Build public roads
|
||||||
|
extern void GeneratePublicRoads();
|
||||||
|
GeneratePublicRoads();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default: NOT_REACHED();
|
default: NOT_REACHED();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -798,6 +808,27 @@ struct ScenarioEditorLandscapeGenerationWindow : Window {
|
|||||||
this->SetDirty();
|
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<NWidgetStacked>(WID_ETT_SHOW_PLACE_DESERT);
|
||||||
|
show_desert->SetDisplayedPlane(_settings_game.game_creation.landscape == LT_TROPIC ? 0 : SZSP_NONE);
|
||||||
|
NWidgetStacked *show_public_roads = this->GetWidget<NWidgetStacked>(WID_ETT_SHOW_PUBLIC_ROADS);
|
||||||
|
show_public_roads->SetDisplayedPlane(_settings_game.game_creation.build_public_roads != 0 ? 0 : SZSP_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
static HotkeyList hotkeys;
|
static HotkeyList hotkeys;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -28,6 +28,7 @@ enum TerraformToolbarWidgets {
|
|||||||
/** Widgets of the #ScenarioEditorLandscapeGenerationWindow class. */
|
/** Widgets of the #ScenarioEditorLandscapeGenerationWindow class. */
|
||||||
enum EditorTerraformToolbarWidgets {
|
enum EditorTerraformToolbarWidgets {
|
||||||
WID_ETT_SHOW_PLACE_DESERT, ///< Should the place desert button be shown?
|
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_START, ///< Used for iterations.
|
||||||
WID_ETT_DOTS = WID_ETT_START, ///< Invisible widget for rendering the terraform size on.
|
WID_ETT_DOTS = WID_ETT_START, ///< Invisible widget for rendering the terraform size on.
|
||||||
WID_ETT_BUTTONS_START, ///< Start of pushable buttons.
|
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_DECREASE_SIZE, ///< Downwards arrow button to decrease terraforming size.
|
||||||
WID_ETT_NEW_SCENARIO, ///< Button for generating a new scenario.
|
WID_ETT_NEW_SCENARIO, ///< Button for generating a new scenario.
|
||||||
WID_ETT_RESET_LANDSCAPE, ///< Button for removing all company-owned property.
|
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 */
|
#endif /* WIDGETS_TERRAFORM_WIDGET_H */
|
||||||
|
Reference in New Issue
Block a user