1
0
Fork 0

Codechange: Replace CircularTileSearch with SpiralTileSequence.

pull/14041/head
frosch 2025-04-19 15:58:48 +02:00 committed by frosch
parent 0dada5a750
commit b956af631e
10 changed files with 223 additions and 412 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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