mirror of https://github.com/OpenTTD/OpenTTD
Codechange: Replace CircularTileSearch with SpiralTileSequence.
parent
0dada5a750
commit
b956af631e
|
@ -351,37 +351,6 @@ void GenerateWorld(GenWorldMode mode, uint size_x, uint size_y, bool reset_setti
|
|||
_GenerateWorld();
|
||||
}
|
||||
|
||||
/** Town data imported from JSON files and used to place towns. */
|
||||
struct ExternalTownData {
|
||||
TownID town_id; ///< The TownID of the town in OpenTTD. Not imported, but set during the founding proceess and stored here for convenience.
|
||||
std::string name; ///< The name of the town.
|
||||
uint population; ///< The target population of the town when created in OpenTTD. If input is blank, defaults to 0.
|
||||
bool is_city; ///< Should it be created as a city in OpenTTD? If input is blank, defaults to false.
|
||||
float x_proportion; ///< The X coordinate of the town, as a proportion 0..1 of the maximum X coordinate.
|
||||
float y_proportion; ///< The Y coordinate of the town, as a proportion 0..1 of the maximum Y coordinate.
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper for CircularTileSearch to found a town on or near a given tile.
|
||||
* @param tile The tile to try founding the town upon.
|
||||
* @param user_data The ExternalTownData to attempt to found.
|
||||
* @return True if the town was founded successfully.
|
||||
*/
|
||||
static bool TryFoundTownNearby(TileIndex tile, void *user_data)
|
||||
{
|
||||
ExternalTownData &town = *static_cast<ExternalTownData *>(user_data);
|
||||
std::tuple<CommandCost, Money, TownID> result = Command<CMD_FOUND_TOWN>::Do(DoCommandFlag::Execute, tile, TSZ_SMALL, town.is_city, _settings_game.economy.town_layout, false, 0, town.name);
|
||||
|
||||
TownID id = std::get<TownID>(result);
|
||||
|
||||
/* Check if the command failed. */
|
||||
if (id == TownID::Invalid()) return false;
|
||||
|
||||
/* The command succeeded, send the ID back through user_data. */
|
||||
town.town_id = id;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load town data from _file_to_saveload, place towns at the appropriate locations, and expand them to their target populations.
|
||||
*/
|
||||
|
@ -426,7 +395,11 @@ void LoadTownData()
|
|||
|
||||
/* Iterate through towns and attempt to found them. */
|
||||
for (auto &feature : town_data) {
|
||||
ExternalTownData town;
|
||||
std::string name; // The name of the town.
|
||||
uint population; // The target population of the town when created in OpenTTD. If input is blank, defaults to 0.
|
||||
bool is_city; // Should it be created as a city in OpenTTD? If input is blank, defaults to false.
|
||||
float x_proportion; // The X coordinate of the town, as a proportion 0..1 of the maximum X coordinate.
|
||||
float y_proportion; // The Y coordinate of the town, as a proportion 0..1 of the maximum Y coordinate.
|
||||
|
||||
/* Ensure JSON is formatted properly. */
|
||||
if (!feature.is_object()) {
|
||||
|
@ -446,56 +419,63 @@ void LoadTownData()
|
|||
!feature.contains("city") || !feature.at("city").is_boolean() ||
|
||||
!feature.contains("x") || !feature.at("x").is_number() ||
|
||||
!feature.contains("y") || !feature.at("y").is_number()) {
|
||||
feature.at("name").get_to(town.name);
|
||||
feature.at("name").get_to(name);
|
||||
ShowErrorMessage(GetEncodedString(STR_TOWN_DATA_ERROR_LOAD_FAILED),
|
||||
GetEncodedString(STR_TOWN_DATA_ERROR_TOWN_FORMATTED_INCORRECTLY, town.name), WL_ERROR);
|
||||
GetEncodedString(STR_TOWN_DATA_ERROR_TOWN_FORMATTED_INCORRECTLY, name), WL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set town properties. */
|
||||
feature.at("name").get_to(town.name);
|
||||
feature.at("population").get_to(town.population);
|
||||
feature.at("city").get_to(town.is_city);
|
||||
feature.at("name").get_to(name);
|
||||
feature.at("population").get_to(population);
|
||||
feature.at("city").get_to(is_city);
|
||||
|
||||
/* Set town coordinates. */
|
||||
feature.at("x").get_to(town.x_proportion);
|
||||
feature.at("y").get_to(town.y_proportion);
|
||||
feature.at("x").get_to(x_proportion);
|
||||
feature.at("y").get_to(y_proportion);
|
||||
|
||||
/* Check for improper coordinates and warn the player. */
|
||||
if (town.x_proportion <= 0.0f || town.y_proportion <= 0.0f || town.x_proportion >= 1.0f || town.y_proportion >= 1.0f) {
|
||||
if (x_proportion <= 0.0f || y_proportion <= 0.0f || x_proportion >= 1.0f || y_proportion >= 1.0f) {
|
||||
ShowErrorMessage(GetEncodedString(STR_TOWN_DATA_ERROR_LOAD_FAILED),
|
||||
GetEncodedString(STR_TOWN_DATA_ERROR_BAD_COORDINATE, town.name), WL_ERROR);
|
||||
GetEncodedString(STR_TOWN_DATA_ERROR_BAD_COORDINATE, name), WL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find the target tile for the town. */
|
||||
TileIndex tile;
|
||||
TileIndex target_tile;
|
||||
switch (_settings_game.game_creation.heightmap_rotation) {
|
||||
case HM_CLOCKWISE:
|
||||
/* Tile coordinates align with what we expect. */
|
||||
tile = TileXY(town.x_proportion * Map::MaxX(), town.y_proportion * Map::MaxY());
|
||||
target_tile = TileXY(x_proportion * Map::MaxX(), y_proportion * Map::MaxY());
|
||||
break;
|
||||
case HM_COUNTER_CLOCKWISE:
|
||||
/* Tile coordinates are rotated and must be adjusted. */
|
||||
tile = TileXY((1 - town.y_proportion * Map::MaxX()), town.x_proportion * Map::MaxY());
|
||||
target_tile = TileXY((1 - y_proportion * Map::MaxX()), x_proportion * Map::MaxY());
|
||||
break;
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
|
||||
TownID town_id; // The TownID of the town in OpenTTD. Not imported, but set during the founding proceess and stored here for convenience.
|
||||
/* Try founding on the target tile, and if that doesn't work, find the nearest suitable tile up to 16 tiles away.
|
||||
* The target might be on water, blocked somehow, or on a steep slope that can't be terraformed by the founding command. */
|
||||
TileIndex search_tile = tile;
|
||||
bool success = CircularTileSearch(&search_tile, 16, 0, 0, TryFoundTownNearby, &town);
|
||||
for (auto tile : SpiralTileSequence(target_tile, 16, 0, 0)) {
|
||||
std::tuple<CommandCost, Money, TownID> result = Command<CMD_FOUND_TOWN>::Do(DoCommandFlag::Execute, tile, TSZ_SMALL, is_city, _settings_game.economy.town_layout, false, 0, name);
|
||||
|
||||
town_id = std::get<TownID>(result);
|
||||
|
||||
/* Check if the command succeeded. */
|
||||
if (town_id != TownID::Invalid()) break;
|
||||
}
|
||||
|
||||
/* If we still fail to found the town, we'll create a sign at the intended location and tell the player how many towns we failed to create in an error message.
|
||||
* This allows the player to diagnose a heightmap misalignment, if towns end up in the sea, or place towns manually, if in rough terrain. */
|
||||
if (!success) {
|
||||
Command<CMD_PLACE_SIGN>::Post(tile, town.name);
|
||||
if (town_id == TownID::Invalid()) {
|
||||
Command<CMD_PLACE_SIGN>::Post(target_tile, name);
|
||||
failed_towns++;
|
||||
continue;
|
||||
}
|
||||
|
||||
towns.emplace_back(std::make_pair(Town::Get(town.town_id), town.population));
|
||||
towns.emplace_back(std::make_pair(Town::Get(town_id), population));
|
||||
}
|
||||
|
||||
/* If we couldn't found a town (or multiple), display a message to the player with the number of failed towns. */
|
||||
|
|
|
@ -1103,30 +1103,6 @@ void PlantRandomFarmField(const Industry *i)
|
|||
if (tile != INVALID_TILE) PlantFarmField(tile, i->index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search callback function for ChopLumberMillTrees
|
||||
* @param tile to test
|
||||
* @return the result of the test
|
||||
*/
|
||||
static bool SearchLumberMillTrees(TileIndex tile, void *)
|
||||
{
|
||||
if (IsTileType(tile, MP_TREES) && GetTreeGrowth(tile) >= TreeGrowthStage::Grown) {
|
||||
/* found a tree */
|
||||
|
||||
Backup<CompanyID> cur_company(_current_company, OWNER_NONE);
|
||||
|
||||
_industry_sound_ctr = 1;
|
||||
_industry_sound_tile = tile;
|
||||
if (_settings_client.sound.ambient) SndPlayTileFx(SND_38_LUMBER_MILL_1, tile);
|
||||
|
||||
Command<CMD_LANDSCAPE_CLEAR>::Do(DoCommandFlag::Execute, tile);
|
||||
|
||||
cur_company.Restore();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a circular search around the Lumber Mill in order to find trees to cut
|
||||
* @param i industry
|
||||
|
@ -1144,9 +1120,20 @@ static void ChopLumberMillTrees(Industry *i)
|
|||
}
|
||||
}
|
||||
|
||||
TileIndex tile = i->location.tile;
|
||||
if (CircularTileSearch(&tile, 40, SearchLumberMillTrees, nullptr)) { // 40x40 tiles to search.
|
||||
itp->waiting = ClampTo<uint16_t>(itp->waiting + ScaleByCargoScale(45, false)); // Found a tree, add according value to waiting cargo.
|
||||
for (auto tile : SpiralTileSequence(i->location.tile, 40)) { // 40x40 tiles to search.
|
||||
if (!IsTileType(tile, MP_TREES) || GetTreeGrowth(tile) < TreeGrowthStage::Grown) continue;
|
||||
|
||||
/* found a tree */
|
||||
_industry_sound_ctr = 1;
|
||||
_industry_sound_tile = tile;
|
||||
if (_settings_client.sound.ambient) SndPlayTileFx(SND_38_LUMBER_MILL_1, tile);
|
||||
|
||||
AutoRestoreBackup<CompanyID> cur_company(_current_company, OWNER_NONE);
|
||||
Command<CMD_LANDSCAPE_CLEAR>::Do(DoCommandFlag::Execute, tile);
|
||||
|
||||
/* Add according value to waiting cargo. */
|
||||
itp->waiting = ClampTo<uint16_t>(itp->waiting + ScaleByCargoScale(45, false));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -980,7 +980,7 @@ static void CreateDesertOrRainForest(uint desert_tropic_line)
|
|||
* @param tile The tile to consider for being the spring.
|
||||
* @return True iff it is suitable as a spring.
|
||||
*/
|
||||
static bool FindSpring(TileIndex tile, void *)
|
||||
static bool FindSpring(TileIndex tile)
|
||||
{
|
||||
int reference_height;
|
||||
if (!IsTileFlat(tile, &reference_height) || IsWaterTile(tile)) return false;
|
||||
|
@ -1013,54 +1013,48 @@ static bool FindSpring(TileIndex tile, void *)
|
|||
/**
|
||||
* Make a connected lake; fill all tiles in the circular tile search that are connected.
|
||||
* @param tile The tile to consider for lake making.
|
||||
* @param user_data The height of the lake.
|
||||
* @return Always false, so it continues searching.
|
||||
* @param height_lake The height of the lake.
|
||||
*/
|
||||
static bool MakeLake(TileIndex tile, void *user_data)
|
||||
static void MakeLake(TileIndex tile, uint height_lake)
|
||||
{
|
||||
uint height_lake = *static_cast<uint *>(user_data);
|
||||
if (!IsValidTile(tile) || TileHeight(tile) != height_lake || !IsTileFlat(tile)) return false;
|
||||
if (_settings_game.game_creation.landscape == LandscapeType::Tropic && GetTropicZone(tile) == TROPICZONE_DESERT) return false;
|
||||
if (!IsValidTile(tile) || TileHeight(tile) != height_lake || !IsTileFlat(tile)) return;
|
||||
if (_settings_game.game_creation.landscape == LandscapeType::Tropic && GetTropicZone(tile) == TROPICZONE_DESERT) return;
|
||||
|
||||
for (DiagDirection d = DIAGDIR_BEGIN; d < DIAGDIR_END; d++) {
|
||||
TileIndex t = tile + TileOffsByDiagDir(d);
|
||||
if (IsWaterTile(t)) {
|
||||
MakeRiverAndModifyDesertZoneAround(tile);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Widen a river by expanding into adjacent tiles via circular tile search.
|
||||
* @param tile The tile to try expanding the river into.
|
||||
* @param user_data The tile to try surrounding the river around.
|
||||
* @return Always false, so it continues searching.
|
||||
* @param origin_tile The tile to try surrounding the river around.
|
||||
*/
|
||||
static bool RiverMakeWider(TileIndex tile, void *user_data)
|
||||
static void RiverMakeWider(TileIndex tile, TileIndex origin_tile)
|
||||
{
|
||||
/* Don't expand into void tiles. */
|
||||
if (!IsValidTile(tile)) return false;
|
||||
if (!IsValidTile(tile)) return;
|
||||
|
||||
/* If the tile is already sea or river, don't expand. */
|
||||
if (IsWaterTile(tile)) return false;
|
||||
if (IsWaterTile(tile)) return;
|
||||
|
||||
/* If the tile is at height 0 after terraforming but the ocean hasn't flooded yet, don't build river. */
|
||||
if (GetTileMaxZ(tile) == 0) return false;
|
||||
if (GetTileMaxZ(tile) == 0) return;
|
||||
|
||||
TileIndex origin_tile = *static_cast<TileIndex *>(user_data);
|
||||
Slope cur_slope = GetTileSlope(tile);
|
||||
Slope desired_slope = GetTileSlope(origin_tile); // Initialize matching the origin tile as a shortcut if no terraforming is needed.
|
||||
|
||||
/* Never flow uphill. */
|
||||
if (GetTileMaxZ(tile) > GetTileMaxZ(origin_tile)) return false;
|
||||
if (GetTileMaxZ(tile) > GetTileMaxZ(origin_tile)) return;
|
||||
|
||||
/* If the new tile can't hold a river tile, try terraforming. */
|
||||
if (!IsTileFlat(tile) && !IsInclinedSlope(cur_slope)) {
|
||||
/* Don't try to terraform steep slopes. */
|
||||
if (IsSteepSlope(cur_slope)) return false;
|
||||
if (IsSteepSlope(cur_slope)) return;
|
||||
|
||||
bool flat_river_found = false;
|
||||
bool sloped_river_found = false;
|
||||
|
@ -1070,7 +1064,7 @@ static bool RiverMakeWider(TileIndex tile, void *user_data)
|
|||
* 2. River descending, adjacent tile has either one or three corners raised.
|
||||
*/
|
||||
|
||||
/* First, determine the desired slope based on adjacent river tiles. This doesn't necessarily match the origin tile for the CircularTileSearch. */
|
||||
/* First, determine the desired slope based on adjacent river tiles. This doesn't necessarily match the origin tile for the SpiralTileSequence. */
|
||||
for (DiagDirection d = DIAGDIR_BEGIN; d < DIAGDIR_END; d++) {
|
||||
TileIndex other_tile = TileAddByDiagDir(tile, d);
|
||||
Slope other_slope = GetTileSlope(other_tile);
|
||||
|
@ -1092,7 +1086,7 @@ static bool RiverMakeWider(TileIndex tile, void *user_data)
|
|||
}
|
||||
}
|
||||
/* We didn't find either an inclined or flat river, so we're climbing the wrong slope. Bail out. */
|
||||
if (!sloped_river_found && !flat_river_found) return false;
|
||||
if (!sloped_river_found && !flat_river_found) return;
|
||||
|
||||
/* We didn't find an inclined river, but there is a flat river. */
|
||||
if (!sloped_river_found && flat_river_found) desired_slope = SLOPE_FLAT;
|
||||
|
@ -1104,7 +1098,7 @@ static bool RiverMakeWider(TileIndex tile, void *user_data)
|
|||
/* Make sure we're not affecting an existing river slope tile. */
|
||||
for (DiagDirection d = DIAGDIR_BEGIN; d < DIAGDIR_END; d++) {
|
||||
TileIndex other_tile = TileAddByDiagDir(tile, d);
|
||||
if (IsInclinedSlope(GetTileSlope(other_tile)) && IsWaterTile(other_tile)) return false;
|
||||
if (IsInclinedSlope(GetTileSlope(other_tile)) && IsWaterTile(other_tile)) return;
|
||||
}
|
||||
Command<CMD_TERRAFORM_LAND>::Do({DoCommandFlag::Execute, DoCommandFlag::Auto}, tile, ComplementSlope(cur_slope), true);
|
||||
|
||||
|
@ -1118,7 +1112,7 @@ static bool RiverMakeWider(TileIndex tile, void *user_data)
|
|||
if (d == DIAGDIRDIFF_SAME || d == DIAGDIRDIFF_REVERSE) continue;
|
||||
|
||||
TileIndex other_tile = (TileAddByDiagDir(tile, ChangeDiagDir(river_direction, d)));
|
||||
if (IsWaterTile(other_tile) && IsRiver(other_tile) && IsTileFlat(other_tile)) return false;
|
||||
if (IsWaterTile(other_tile) && IsRiver(other_tile) && IsTileFlat(other_tile)) return;
|
||||
}
|
||||
|
||||
/* Get the corners which are different between the current and desired slope. */
|
||||
|
@ -1149,7 +1143,7 @@ static bool RiverMakeWider(TileIndex tile, void *user_data)
|
|||
TileIndex downstream_tile = TileAddByDiagDir(tile, ReverseDiagDir(slope_direction));
|
||||
|
||||
/* Don't look outside the map. */
|
||||
if (!IsValidTile(upstream_tile) || !IsValidTile(downstream_tile)) return false;
|
||||
if (!IsValidTile(upstream_tile) || !IsValidTile(downstream_tile)) return;
|
||||
|
||||
/* Downstream might be new ocean created by our terraforming, and it hasn't flooded yet. */
|
||||
bool downstream_is_ocean = GetTileZ(downstream_tile) == 0 && (GetTileSlope(downstream_tile) == SLOPE_FLAT || IsSlopeWithOneCornerRaised(GetTileSlope(downstream_tile)));
|
||||
|
@ -1157,7 +1151,7 @@ static bool RiverMakeWider(TileIndex tile, void *user_data)
|
|||
/* If downstream is dry, flat, and not ocean, try making it a river tile. */
|
||||
if (!IsWaterTile(downstream_tile) && !downstream_is_ocean) {
|
||||
/* If the tile upstream isn't flat, don't bother. */
|
||||
if (GetTileSlope(downstream_tile) != SLOPE_FLAT) return false;
|
||||
if (GetTileSlope(downstream_tile) != SLOPE_FLAT) return;
|
||||
|
||||
MakeRiverAndModifyDesertZoneAround(downstream_tile);
|
||||
}
|
||||
|
@ -1165,7 +1159,7 @@ static bool RiverMakeWider(TileIndex tile, void *user_data)
|
|||
/* If upstream is dry and flat, try making it a river tile. */
|
||||
if (!IsWaterTile(upstream_tile)) {
|
||||
/* If the tile upstream isn't flat, don't bother. */
|
||||
if (GetTileSlope(upstream_tile) != SLOPE_FLAT) return false;
|
||||
if (GetTileSlope(upstream_tile) != SLOPE_FLAT) return;
|
||||
|
||||
MakeRiverAndModifyDesertZoneAround(upstream_tile);
|
||||
}
|
||||
|
@ -1175,9 +1169,6 @@ static bool RiverMakeWider(TileIndex tile, void *user_data)
|
|||
if (cur_slope == desired_slope) {
|
||||
MakeRiverAndModifyDesertZoneAround(tile);
|
||||
}
|
||||
|
||||
/* Always return false to keep searching. */
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1257,13 +1248,16 @@ protected:
|
|||
const uint long_river_length = _settings_game.game_creation.min_river_length * 4;
|
||||
|
||||
for (PathNode *path = current.parent; path != nullptr; path = path->parent) {
|
||||
TileIndex tile = path->GetTile();
|
||||
TileIndex origin_tile = path->GetTile();
|
||||
|
||||
/* Check if we should widen river depending on how far we are away from the source. */
|
||||
uint current_river_length = DistanceManhattan(this->spring, tile);
|
||||
uint current_river_length = DistanceManhattan(this->spring, origin_tile);
|
||||
uint diameter = std::min(3u, (current_river_length / (long_river_length / 3u)) + 1u);
|
||||
if (diameter <= 1) continue;
|
||||
|
||||
if (diameter > 1) CircularTileSearch(&tile, diameter, RiverMakeWider, &path->key.tile);
|
||||
for (auto tile : SpiralTileSequence(origin_tile, diameter)) {
|
||||
RiverMakeWider(tile, origin_tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1363,10 +1357,14 @@ static std::tuple<bool, bool> FlowRiver(TileIndex spring, TileIndex begin, uint
|
|||
end = lake_centre;
|
||||
MakeRiverAndModifyDesertZoneAround(lake_centre);
|
||||
uint diameter = RandomRange(8) + 3;
|
||||
CircularTileSearch(&lake_centre, diameter, MakeLake, &height_begin);
|
||||
/* Call the search a second time so artefacts from going circular in one direction get (mostly) hidden. */
|
||||
lake_centre = end;
|
||||
CircularTileSearch(&lake_centre, diameter, MakeLake, &height_begin);
|
||||
|
||||
/* Run the loop twice, so artefacts from going circular in one direction get (mostly) hidden. */
|
||||
for (uint loops = 0; loops < 2; ++loops) {
|
||||
for (auto tile : SpiralTileSequence(lake_centre, diameter)) {
|
||||
MakeLake(tile, height_begin);
|
||||
}
|
||||
}
|
||||
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
@ -1391,20 +1389,30 @@ static void CreateRivers()
|
|||
/* Try to create long rivers. */
|
||||
for (; wells > num_short_rivers; wells--) {
|
||||
IncreaseGeneratingWorldProgress(GWP_RIVER);
|
||||
bool done = false;
|
||||
for (int tries = 0; tries < 512; tries++) {
|
||||
TileIndex t = RandomTile();
|
||||
if (!CircularTileSearch(&t, 8, FindSpring, nullptr)) continue;
|
||||
if (std::get<0>(FlowRiver(t, t, _settings_game.game_creation.min_river_length * 4))) break;
|
||||
for (auto t : SpiralTileSequence(RandomTile(), 8)) {
|
||||
if (FindSpring(t)) {
|
||||
done = std::get<0>(FlowRiver(t, t, _settings_game.game_creation.min_river_length * 4));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (done) break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to create short rivers. */
|
||||
for (; wells != 0; wells--) {
|
||||
IncreaseGeneratingWorldProgress(GWP_RIVER);
|
||||
bool done = false;
|
||||
for (int tries = 0; tries < 128; tries++) {
|
||||
TileIndex t = RandomTile();
|
||||
if (!CircularTileSearch(&t, 8, FindSpring, nullptr)) continue;
|
||||
if (std::get<0>(FlowRiver(t, t, _settings_game.game_creation.min_river_length))) break;
|
||||
for (auto t : SpiralTileSequence(RandomTile(), 8)) {
|
||||
if (FindSpring(t)) {
|
||||
done = std::get<0>(FlowRiver(t, t, _settings_game.game_creation.min_river_length));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (done) break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -257,113 +257,49 @@ static uint32_t GetNearbyTileInformation(uint8_t parameter, TileIndex tile, bool
|
|||
return GetNearbyTileInformation(tile, grf_version8);
|
||||
}
|
||||
|
||||
/** Structure with user-data for SearchNearbyHouseXXX - functions */
|
||||
struct SearchNearbyHouseData {
|
||||
const HouseSpec *hs; ///< Specs of the house that started the search.
|
||||
TileIndex north_tile; ///< Northern tile of the house.
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback function to search a house by its HouseID
|
||||
* @param tile TileIndex to be examined
|
||||
* @param user_data SearchNearbyHouseData
|
||||
* @return true or false, if found or not
|
||||
*/
|
||||
static bool SearchNearbyHouseID(TileIndex tile, void *user_data)
|
||||
{
|
||||
if (IsTileType(tile, MP_HOUSE)) {
|
||||
HouseID house = GetHouseType(tile); // tile been examined
|
||||
const HouseSpec *hs = HouseSpec::Get(house);
|
||||
if (hs->grf_prop.HasGrfFile()) { // must be one from a grf file
|
||||
SearchNearbyHouseData *nbhd = (SearchNearbyHouseData *)user_data;
|
||||
|
||||
TileIndex north_tile = tile + GetHouseNorthPart(house); // modifies 'house'!
|
||||
if (north_tile == nbhd->north_tile) return false; // Always ignore origin house
|
||||
|
||||
return hs->grf_prop.local_id == nbhd->hs->grf_prop.local_id && // same local id as the one requested
|
||||
hs->grf_prop.grfid == nbhd->hs->grf_prop.grfid; // from the same grf
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function to search a house by its classID
|
||||
* @param tile TileIndex to be examined
|
||||
* @param user_data SearchNearbyHouseData
|
||||
* @return true or false, if found or not
|
||||
*/
|
||||
static bool SearchNearbyHouseClass(TileIndex tile, void *user_data)
|
||||
{
|
||||
if (IsTileType(tile, MP_HOUSE)) {
|
||||
HouseID house = GetHouseType(tile); // tile been examined
|
||||
const HouseSpec *hs = HouseSpec::Get(house);
|
||||
if (hs->grf_prop.HasGrfFile()) { // must be one from a grf file
|
||||
SearchNearbyHouseData *nbhd = (SearchNearbyHouseData *)user_data;
|
||||
|
||||
TileIndex north_tile = tile + GetHouseNorthPart(house); // modifies 'house'!
|
||||
if (north_tile == nbhd->north_tile) return false; // Always ignore origin house
|
||||
|
||||
return hs->class_id == nbhd->hs->class_id && // same classid as the one requested
|
||||
hs->grf_prop.grfid == nbhd->hs->grf_prop.grfid; // from the same grf
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function to search a house by its grfID
|
||||
* @param tile TileIndex to be examined
|
||||
* @param user_data SearchNearbyHouseData
|
||||
* @return true or false, if found or not
|
||||
*/
|
||||
static bool SearchNearbyHouseGRFID(TileIndex tile, void *user_data)
|
||||
{
|
||||
if (IsTileType(tile, MP_HOUSE)) {
|
||||
HouseID house = GetHouseType(tile); // tile been examined
|
||||
const HouseSpec *hs = HouseSpec::Get(house);
|
||||
if (hs->grf_prop.HasGrfFile()) { // must be one from a grf file
|
||||
SearchNearbyHouseData *nbhd = (SearchNearbyHouseData *)user_data;
|
||||
|
||||
TileIndex north_tile = tile + GetHouseNorthPart(house); // modifies 'house'!
|
||||
if (north_tile == nbhd->north_tile) return false; // Always ignore origin house
|
||||
|
||||
return hs->grf_prop.grfid == nbhd->hs->grf_prop.grfid; // from the same grf
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will activate a search around a central tile, looking for some houses
|
||||
* that fit the requested characteristics
|
||||
* @param parameter that is given by the callback.
|
||||
* bits 0..6 radius of the search
|
||||
* bits 7..8 search type i.e.: 0 = houseID/ 1 = classID/ 2 = grfID
|
||||
* @param tile TileIndex from which to start the search
|
||||
* @param house the HouseID that is associated to the house, the callback is called for
|
||||
* @param start_tile TileIndex from which to start the search
|
||||
* @param start_house the HouseID that is associated to the house, the callback is called for
|
||||
* @return the Manhattan distance from the center tile, if any, and 0 if failure
|
||||
*/
|
||||
static uint32_t GetDistanceFromNearbyHouse(uint8_t parameter, TileIndex tile, HouseID house)
|
||||
static uint32_t GetDistanceFromNearbyHouse(uint8_t parameter, TileIndex start_tile, HouseID start_house)
|
||||
{
|
||||
static TestTileOnSearchProc * const search_procs[3] = {
|
||||
SearchNearbyHouseID,
|
||||
SearchNearbyHouseClass,
|
||||
SearchNearbyHouseGRFID,
|
||||
};
|
||||
TileIndex found_tile = tile;
|
||||
uint8_t searchtype = GB(parameter, 6, 2);
|
||||
uint8_t searchradius = GB(parameter, 0, 6);
|
||||
if (searchtype >= lengthof(search_procs)) return 0; // do not run on ill-defined code
|
||||
if (searchradius < 1) return 0; // do not use a too low radius
|
||||
|
||||
SearchNearbyHouseData nbhd;
|
||||
nbhd.hs = HouseSpec::Get(house);
|
||||
nbhd.north_tile = tile + GetHouseNorthPart(house); // modifies 'house'!
|
||||
const auto *start_hs = HouseSpec::Get(start_house);
|
||||
const auto start_north_tile = start_tile + GetHouseNorthPart(start_house); // modifies 'start_house'!
|
||||
|
||||
/* Use a pointer for the tile to start the search. Will be required for calculating the distance*/
|
||||
if (CircularTileSearch(&found_tile, 2 * searchradius + 1, search_procs[searchtype], &nbhd)) {
|
||||
return DistanceManhattan(found_tile, tile);
|
||||
for (auto tile : SpiralTileSequence(start_tile, 2 * searchradius + 1)) {
|
||||
if (!IsTileType(tile, MP_HOUSE)) continue;
|
||||
HouseID house = GetHouseType(tile);
|
||||
const HouseSpec *hs = HouseSpec::Get(house);
|
||||
if (!hs->grf_prop.HasGrfFile()) continue; // must be one from a grf file
|
||||
if (start_north_tile == tile + GetHouseNorthPart(house)) continue; // Always ignore origin house
|
||||
|
||||
bool match;
|
||||
switch (searchtype) {
|
||||
case 0:
|
||||
match = hs->grf_prop.local_id == start_hs->grf_prop.local_id && // same local id as the one requested
|
||||
hs->grf_prop.grfid == start_hs->grf_prop.grfid; // from the same grf
|
||||
break;
|
||||
case 1:
|
||||
match = hs->class_id == start_hs->class_id && // same classid as the one requested
|
||||
hs->grf_prop.grfid == start_hs->grf_prop.grfid; // from the same grf
|
||||
break;
|
||||
case 2:
|
||||
match = hs->grf_prop.grfid == start_hs->grf_prop.grfid; // from the same grf
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
if (match) return DistanceManhattan(tile, start_tile);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -744,16 +744,6 @@ static void AnimateTile_Object(TileIndex tile)
|
|||
AnimateNewObjectTile(tile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for \c CircularTileSearch.
|
||||
* @param tile The tile to check.
|
||||
* @return True iff the tile has a radio tower.
|
||||
*/
|
||||
static bool HasTransmitter(TileIndex tile, void *)
|
||||
{
|
||||
return IsObjectTypeTile(tile, OBJECT_TRANSMITTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to build a lighthouse.
|
||||
* @return True iff building a lighthouse succeeded.
|
||||
|
@ -805,9 +795,9 @@ static bool TryBuildTransmitter()
|
|||
TileIndex tile = RandomTile();
|
||||
int h;
|
||||
if (IsTileType(tile, MP_CLEAR) && IsTileFlat(tile, &h) && h >= 4 && !IsBridgeAbove(tile)) {
|
||||
TileIndex t = tile;
|
||||
if (CircularTileSearch(&t, 9, HasTransmitter, nullptr)) return false;
|
||||
|
||||
for (auto t : SpiralTileSequence(tile, 9)) {
|
||||
if (IsObjectTypeTile(t, OBJECT_TRANSMITTER)) return false;
|
||||
}
|
||||
BuildObject(OBJECT_TRANSMITTER, tile);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -387,7 +387,7 @@ Rect Station::GetCatchmentRect() const
|
|||
*/
|
||||
void Station::AddIndustryToDeliver(Industry *ind, TileIndex tile)
|
||||
{
|
||||
/* Using DistanceMax to get about the same order as with previously used CircularTileSearch. */
|
||||
/* Using DistanceMax to get about the same order as with previously used SpiralTileSequence. */
|
||||
uint distance = DistanceMax(this->xy, tile);
|
||||
|
||||
/* Don't check further if this industry is already in the list but update the distance if it's closer */
|
||||
|
|
|
@ -237,31 +237,6 @@ struct StationNameInformation {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Find a station action 0 property 24 station name, or reduce the
|
||||
* free_names if needed.
|
||||
* @param tile the tile to search
|
||||
* @param user_data the StationNameInformation to base the search on
|
||||
* @return true if the tile contains an industry that has not given
|
||||
* its name to one of the other stations in town.
|
||||
*/
|
||||
static bool FindNearIndustryName(TileIndex tile, void *user_data)
|
||||
{
|
||||
/* All already found industry types */
|
||||
StationNameInformation *sni = (StationNameInformation*)user_data;
|
||||
if (!IsTileType(tile, MP_INDUSTRY)) return false;
|
||||
|
||||
/* If the station name is undefined it means that it doesn't name a station */
|
||||
IndustryType indtype = GetIndustryType(tile);
|
||||
if (GetIndustrySpec(indtype)->station_name == STR_UNDEFINED) return false;
|
||||
|
||||
/* In all cases if an industry that provides a name is found two of
|
||||
* the standard names will be disabled. */
|
||||
sni->SetUsed(STR_SV_STNAME_OILFIELD);
|
||||
sni->SetUsed(STR_SV_STNAME_MINES);
|
||||
return !sni->indtypes[indtype];
|
||||
}
|
||||
|
||||
static StringID GenerateStationName(Station *st, TileIndex tile, StationNaming name_class)
|
||||
{
|
||||
const Town *t = st->town;
|
||||
|
@ -290,21 +265,30 @@ static StringID GenerateStationName(Station *st, TileIndex tile, StationNaming n
|
|||
}
|
||||
}
|
||||
|
||||
TileIndex indtile = tile;
|
||||
if (CircularTileSearch(&indtile, 7, FindNearIndustryName, &sni)) {
|
||||
/* An industry has been found nearby */
|
||||
for (auto indtile : SpiralTileSequence(tile, 7)) {
|
||||
if (!IsTileType(indtile, MP_INDUSTRY)) continue;
|
||||
|
||||
/* If the station name is undefined it means that it doesn't name a station */
|
||||
IndustryType indtype = GetIndustryType(indtile);
|
||||
const IndustrySpec *indsp = GetIndustrySpec(indtype);
|
||||
if (indsp->station_name == STR_UNDEFINED) continue;
|
||||
|
||||
/* In all cases if an industry that provides a name is found two of
|
||||
* the standard names will be disabled. */
|
||||
sni.SetUsed(STR_SV_STNAME_OILFIELD);
|
||||
sni.SetUsed(STR_SV_STNAME_MINES);
|
||||
if (sni.indtypes[indtype]) continue;
|
||||
|
||||
/* STR_NULL means it only disables oil rig/mines */
|
||||
if (indsp->station_name != STR_NULL) {
|
||||
st->indtype = indtype;
|
||||
return STR_SV_STNAME_FALLBACK;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Oil rigs/mines name could be marked not free by looking for a near by industry. */
|
||||
|
||||
/* check default names */
|
||||
/* check default names
|
||||
* Oil rigs/mines name could be marked not free by looking for a near by industry. */
|
||||
switch (name_class) {
|
||||
case STATIONNAMING_AIRPORT:
|
||||
if (sni.IsAvailable(STR_SV_STNAME_AIRPORT)) return STR_SV_STNAME_AIRPORT;
|
||||
|
|
|
@ -2178,14 +2178,12 @@ static std::vector<StationID> _stations_nearby_list;
|
|||
* Add station on this tile to _stations_nearby_list if it's fully within the
|
||||
* station spread.
|
||||
* @param tile Tile just being checked
|
||||
* @param user_data Pointer to TileArea context
|
||||
* @param ctx Pointer to TileArea context
|
||||
* @tparam T the station filter type
|
||||
*/
|
||||
template <class T>
|
||||
static bool AddNearbyStation(TileIndex tile, void *user_data)
|
||||
static void AddNearbyStation(TileIndex tile, TileArea *ctx)
|
||||
{
|
||||
TileArea *ctx = (TileArea *)user_data;
|
||||
|
||||
/* First check if there were deleted stations here */
|
||||
for (auto it = _deleted_stations_nearby.begin(); it != _deleted_stations_nearby.end(); /* nothing */) {
|
||||
if (it->tile == tile) {
|
||||
|
@ -2197,21 +2195,19 @@ static bool AddNearbyStation(TileIndex tile, void *user_data)
|
|||
}
|
||||
|
||||
/* Check if own station and if we stay within station spread */
|
||||
if (!IsTileType(tile, MP_STATION)) return false;
|
||||
if (!IsTileType(tile, MP_STATION)) return;
|
||||
|
||||
StationID sid = GetStationIndex(tile);
|
||||
|
||||
/* This station is (likely) a waypoint */
|
||||
if (!T::IsValidID(sid)) return false;
|
||||
if (!T::IsValidID(sid)) return;
|
||||
|
||||
BaseStation *st = BaseStation::Get(sid);
|
||||
if (st->owner != _local_company || std::ranges::find(_stations_nearby_list, sid) != _stations_nearby_list.end()) return false;
|
||||
if (st->owner != _local_company || std::ranges::find(_stations_nearby_list, sid) != _stations_nearby_list.end()) return;
|
||||
|
||||
if (st->rect.BeforeAddRect(ctx->tile, ctx->w, ctx->h, StationRect::ADD_TEST).Succeeded()) {
|
||||
_stations_nearby_list.push_back(sid);
|
||||
}
|
||||
|
||||
return false; // We want to include *all* nearby stations
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2259,8 +2255,9 @@ static const BaseStation *FindStationsNearby(TileArea ta, bool distant_join)
|
|||
if (distant_join && std::min(ta.w, ta.h) >= _settings_game.station.station_spread) return nullptr;
|
||||
uint max_dist = distant_join ? _settings_game.station.station_spread - std::min(ta.w, ta.h) : 1;
|
||||
|
||||
TileIndex tile = TileAddByDir(ctx.tile, DIR_N);
|
||||
CircularTileSearch(&tile, max_dist, ta.w, ta.h, AddNearbyStation<T>, &ctx);
|
||||
for (auto tile : SpiralTileSequence(TileAddByDir(ctx.tile, DIR_N), max_dist, ta.w, ta.h)) {
|
||||
AddNearbyStation<T>(tile, &ctx);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
|
202
src/town_cmd.cpp
202
src/town_cmd.cpp
|
@ -1307,27 +1307,6 @@ static bool CanRoadContinueIntoNextTile(const Town *t, const TileIndex tile, con
|
|||
return Command<CMD_BUILD_ROAD>::Do({DoCommandFlag::Auto, DoCommandFlag::NoWater}, next_tile, rcmd, rt, DRD_NONE, t->index).Succeeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* CircularTileSearch proc which checks for a nearby parallel bridge to avoid building redundant bridges.
|
||||
* @param tile The tile to search.
|
||||
* @param user_data Reference to the valid direction of the proposed bridge.
|
||||
* @return true if another bridge exists, else false.
|
||||
*/
|
||||
static bool RedundantBridgeExistsNearby(TileIndex tile, void *user_data)
|
||||
{
|
||||
/* Don't look into the void. */
|
||||
if (!IsValidTile(tile)) return false;
|
||||
|
||||
/* Only consider bridge head tiles. */
|
||||
if (!IsBridgeTile(tile)) return false;
|
||||
|
||||
/* Only consider road bridges. */
|
||||
if (GetTunnelBridgeTransportType(tile) != TRANSPORT_ROAD) return false;
|
||||
|
||||
/* If the bridge is facing the same direction as the proposed bridge, we've found a redundant bridge. */
|
||||
return (GetTileSlope(tile) & InclinedSlope(ReverseDiagDir(*(DiagDirection *)user_data)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Grows the town with a bridge.
|
||||
* At first we check if a bridge is reasonable.
|
||||
|
@ -1390,9 +1369,18 @@ static bool GrowTownWithBridge(const Town *t, const TileIndex tile, const DiagDi
|
|||
if (!CanRoadContinueIntoNextTile(t, bridge_tile, bridge_dir)) return false;
|
||||
|
||||
/* If another parallel bridge exists nearby, this one would be redundant and shouldn't be built. We don't care about flat bridges. */
|
||||
TileIndex search = tile;
|
||||
DiagDirection direction_to_match = bridge_dir;
|
||||
if (slope != SLOPE_FLAT && CircularTileSearch(&search, bridge_length, 0, 0, RedundantBridgeExistsNearby, &direction_to_match)) return false;
|
||||
if (slope != SLOPE_FLAT) {
|
||||
for (auto search : SpiralTileSequence(tile, bridge_length, 0, 0)) {
|
||||
/* Only consider bridge head tiles. */
|
||||
if (!IsBridgeTile(search)) continue;
|
||||
|
||||
/* Only consider road bridges. */
|
||||
if (GetTunnelBridgeTransportType(search) != TRANSPORT_ROAD) continue;
|
||||
|
||||
/* If the bridge is facing the same direction as the proposed bridge, we've found a redundant bridge. */
|
||||
if (GetTileSlope(search) & InclinedSlope(ReverseDiagDir(bridge_dir))) return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8_t times = 0; times <= 22; times++) {
|
||||
uint8_t bridge_type = RandomRange(MAX_BRIDGES - 1);
|
||||
|
@ -2280,56 +2268,6 @@ static bool IsTileAlignedToGrid(TileIndex tile, TownLayout layout)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used as the user_data for FindFurthestFromWater
|
||||
*/
|
||||
struct SpotData {
|
||||
TileIndex tile; ///< holds the tile that was found
|
||||
uint max_dist; ///< holds the distance that tile is from the water
|
||||
TownLayout layout; ///< tells us what kind of town we're building
|
||||
};
|
||||
|
||||
/**
|
||||
* CircularTileSearch callback; finds the tile furthest from any
|
||||
* water. slightly bit tricky, since it has to do a search of its own
|
||||
* in order to find the distance to the water from each square in the
|
||||
* radius.
|
||||
*
|
||||
* Also, this never returns true, because it needs to take into
|
||||
* account all locations being searched before it knows which is the
|
||||
* furthest.
|
||||
*
|
||||
* @param tile Start looking from this tile
|
||||
* @param user_data Storage area for data that must last across calls;
|
||||
* must be a pointer to struct SpotData
|
||||
*
|
||||
* @return always false
|
||||
*/
|
||||
static bool FindFurthestFromWater(TileIndex tile, void *user_data)
|
||||
{
|
||||
SpotData *sp = (SpotData*)user_data;
|
||||
uint dist = GetClosestWaterDistance(tile, true);
|
||||
|
||||
if (IsTileType(tile, MP_CLEAR) &&
|
||||
IsTileFlat(tile) &&
|
||||
IsTileAlignedToGrid(tile, sp->layout) &&
|
||||
dist > sp->max_dist) {
|
||||
sp->tile = tile;
|
||||
sp->max_dist = dist;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* CircularTileSearch callback to find the nearest land tile.
|
||||
* @param tile Start looking from this tile
|
||||
*/
|
||||
static bool FindNearestEmptyLand(TileIndex tile, void *)
|
||||
{
|
||||
return IsTileType(tile, MP_CLEAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a spot on the map (presumed to be a water tile), find a good
|
||||
* coastal spot to build a city. We don't want to build too close to
|
||||
|
@ -2344,12 +2282,22 @@ static bool FindNearestEmptyLand(TileIndex tile, void *)
|
|||
*/
|
||||
static TileIndex FindNearestGoodCoastalTownSpot(TileIndex tile, TownLayout layout)
|
||||
{
|
||||
SpotData sp = { INVALID_TILE, 0, layout };
|
||||
for (auto coast : SpiralTileSequence(tile, 40)) {
|
||||
/* Find nearest land tile */
|
||||
if (!IsTileType(tile, MP_CLEAR)) continue;
|
||||
|
||||
TileIndex coast = tile;
|
||||
if (CircularTileSearch(&coast, 40, FindNearestEmptyLand, nullptr)) {
|
||||
CircularTileSearch(&coast, 10, FindFurthestFromWater, &sp);
|
||||
return sp.tile;
|
||||
TileIndex furthest = INVALID_TILE;
|
||||
uint max_dist = 0;
|
||||
for (auto test : SpiralTileSequence(coast, 10)) {
|
||||
if (!IsTileType(test, MP_CLEAR) || !IsTileFlat(test) || !IsTileAlignedToGrid(test, layout)) continue;
|
||||
|
||||
uint dist = GetClosestWaterDistance(test, true);
|
||||
if (dist > max_dist) {
|
||||
furthest = test;
|
||||
max_dist = dist;
|
||||
}
|
||||
}
|
||||
return furthest;
|
||||
}
|
||||
|
||||
/* if we get here just give up */
|
||||
|
@ -3428,56 +3376,6 @@ static bool CheckClearTile(TileIndex tile)
|
|||
return r.Succeeded();
|
||||
}
|
||||
|
||||
/** Structure for storing data while searching the best place to build a statue. */
|
||||
struct StatueBuildSearchData {
|
||||
TileIndex best_position; ///< Best position found so far.
|
||||
int tile_count; ///< Number of tiles tried.
|
||||
|
||||
StatueBuildSearchData(TileIndex best_pos, int count) : best_position(best_pos), tile_count(count) { }
|
||||
};
|
||||
|
||||
/**
|
||||
* Search callback function for #TownActionBuildStatue.
|
||||
* @param tile Tile on which to perform the search.
|
||||
* @param user_data Reference to the statue search data.
|
||||
* @return Result of the test.
|
||||
*/
|
||||
static bool SearchTileForStatue(TileIndex tile, void *user_data)
|
||||
{
|
||||
static const int STATUE_NUMBER_INNER_TILES = 25; // Number of tiles int the center of the city, where we try to protect houses.
|
||||
|
||||
StatueBuildSearchData *statue_data = (StatueBuildSearchData *)user_data;
|
||||
statue_data->tile_count++;
|
||||
|
||||
/* Statues can be build on slopes, just like houses. Only the steep slopes is a no go. */
|
||||
if (IsSteepSlope(GetTileSlope(tile))) return false;
|
||||
/* Don't build statues under bridges. */
|
||||
if (IsBridgeAbove(tile)) return false;
|
||||
|
||||
/* A clear-able open space is always preferred. */
|
||||
if ((IsTileType(tile, MP_CLEAR) || IsTileType(tile, MP_TREES)) && CheckClearTile(tile)) {
|
||||
statue_data->best_position = tile;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool house = IsTileType(tile, MP_HOUSE);
|
||||
|
||||
/* Searching inside the inner circle. */
|
||||
if (statue_data->tile_count <= STATUE_NUMBER_INNER_TILES) {
|
||||
/* Save first house in inner circle. */
|
||||
if (house && statue_data->best_position == INVALID_TILE && CheckClearTile(tile)) {
|
||||
statue_data->best_position = tile;
|
||||
}
|
||||
|
||||
/* If we have reached the end of the inner circle, and have a saved house, terminate the search. */
|
||||
return statue_data->tile_count == STATUE_NUMBER_INNER_TILES && statue_data->best_position != INVALID_TILE;
|
||||
}
|
||||
|
||||
/* Searching outside the circle, just pick the first possible spot. */
|
||||
statue_data->best_position = tile; // Is optimistic, the condition below must also hold.
|
||||
return house && CheckClearTile(tile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a 9x9 tiles circular search from the center of the town
|
||||
* in order to find a free tile to place a statue
|
||||
|
@ -3489,17 +3387,51 @@ static CommandCost TownActionBuildStatue(Town *t, DoCommandFlags flags)
|
|||
{
|
||||
if (!Object::CanAllocateItem()) return CommandCost(STR_ERROR_TOO_MANY_OBJECTS);
|
||||
|
||||
TileIndex tile = t->xy;
|
||||
StatueBuildSearchData statue_data(INVALID_TILE, 0);
|
||||
if (!CircularTileSearch(&tile, 9, SearchTileForStatue, &statue_data)) return CommandCost(STR_ERROR_STATUE_NO_SUITABLE_PLACE);
|
||||
static const int STATUE_NUMBER_INNER_TILES = 25; // Number of tiles int the center of the city, where we try to protect houses.
|
||||
|
||||
TileIndex best_position = INVALID_TILE; ///< Best position found so far.
|
||||
uint tile_count = 0; ///< Number of tiles tried.
|
||||
for (auto tile : SpiralTileSequence(t->xy, 9)) {
|
||||
tile_count++;
|
||||
|
||||
/* Statues can be build on slopes, just like houses. Only the steep slopes is a no go. */
|
||||
if (IsSteepSlope(GetTileSlope(tile))) continue;
|
||||
/* Don't build statues under bridges. */
|
||||
if (IsBridgeAbove(tile)) continue;
|
||||
|
||||
/* A clear-able open space is always preferred. */
|
||||
if ((IsTileType(tile, MP_CLEAR) || IsTileType(tile, MP_TREES)) && CheckClearTile(tile)) {
|
||||
best_position = tile;
|
||||
break;
|
||||
}
|
||||
|
||||
bool house = IsTileType(tile, MP_HOUSE);
|
||||
|
||||
/* Searching inside the inner circle. */
|
||||
if (tile_count <= STATUE_NUMBER_INNER_TILES) {
|
||||
/* Save first house in inner circle. */
|
||||
if (house && best_position == INVALID_TILE && CheckClearTile(tile)) {
|
||||
best_position = tile;
|
||||
}
|
||||
|
||||
/* If we have reached the end of the inner circle, and have a saved house, terminate the search. */
|
||||
if (tile_count == STATUE_NUMBER_INNER_TILES && best_position != INVALID_TILE) break;
|
||||
}
|
||||
|
||||
/* Searching outside the circle, just pick the first possible spot. */
|
||||
if (!house || !CheckClearTile(tile)) continue;
|
||||
best_position = tile;
|
||||
break;
|
||||
}
|
||||
if (best_position == INVALID_TILE) return CommandCost(STR_ERROR_STATUE_NO_SUITABLE_PLACE);
|
||||
|
||||
if (flags.Test(DoCommandFlag::Execute)) {
|
||||
Backup<CompanyID> cur_company(_current_company, OWNER_NONE);
|
||||
Command<CMD_LANDSCAPE_CLEAR>::Do(DoCommandFlag::Execute, statue_data.best_position);
|
||||
Command<CMD_LANDSCAPE_CLEAR>::Do(DoCommandFlag::Execute, best_position);
|
||||
cur_company.Restore();
|
||||
BuildObject(OBJECT_STATUE, statue_data.best_position, _current_company, t);
|
||||
BuildObject(OBJECT_STATUE, best_position, _current_company, t);
|
||||
t->statues.Set(_current_company); // Once found and built, "inform" the Town.
|
||||
MarkTileDirtyByTile(statue_data.best_position);
|
||||
MarkTileDirtyByTile(best_position);
|
||||
}
|
||||
return CommandCost();
|
||||
}
|
||||
|
|
|
@ -439,13 +439,6 @@ CommandCost CmdBuildLock(DoCommandFlags flags, TileIndex tile)
|
|||
return DoBuildLock(tile, dir, flags);
|
||||
}
|
||||
|
||||
/** Callback to create non-desert around a river tile. */
|
||||
static bool RiverModifyDesertZone(TileIndex tile, void *)
|
||||
{
|
||||
if (GetTropicZone(tile) == TROPICZONE_DESERT) SetTropicZone(tile, TROPICZONE_NORMAL);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a river tile and remove desert directly around it.
|
||||
* @param tile The tile to change into river and create non-desert around
|
||||
|
@ -456,7 +449,9 @@ void MakeRiverAndModifyDesertZoneAround(TileIndex tile)
|
|||
MarkTileDirtyByTile(tile);
|
||||
|
||||
/* Remove desert directly around the river tile. */
|
||||
CircularTileSearch(&tile, RIVER_OFFSET_DESERT_DISTANCE, RiverModifyDesertZone, nullptr);
|
||||
for (auto t : SpiralTileSequence(tile, RIVER_OFFSET_DESERT_DISTANCE)) {
|
||||
if (GetTropicZone(t) == TROPICZONE_DESERT) SetTropicZone(t, TROPICZONE_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -510,8 +505,10 @@ CommandCost CmdBuildCanal(DoCommandFlags flags, TileIndex tile, TileIndex start_
|
|||
case WATER_CLASS_RIVER:
|
||||
MakeRiver(current_tile, Random());
|
||||
if (_game_mode == GM_EDITOR) {
|
||||
TileIndex tile2 = current_tile;
|
||||
CircularTileSearch(&tile2, RIVER_OFFSET_DESERT_DISTANCE, RiverModifyDesertZone, nullptr);
|
||||
/* Remove desert directly around the river tile. */
|
||||
for (auto t : SpiralTileSequence(current_tile, RIVER_OFFSET_DESERT_DISTANCE)) {
|
||||
if (GetTropicZone(t) == TROPICZONE_DESERT) SetTropicZone(t, TROPICZONE_NORMAL);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
Loading…
Reference in New Issue