From b956af631e86890ab9b4e66318418cd3b7a983e8 Mon Sep 17 00:00:00 2001 From: frosch Date: Sat, 19 Apr 2025 15:58:48 +0200 Subject: [PATCH] Codechange: Replace CircularTileSearch with SpiralTileSequence. --- src/genworld.cpp | 78 +++++++---------- src/industry_cmd.cpp | 41 +++------ src/landscape.cpp | 92 +++++++++++--------- src/newgrf_house.cpp | 122 +++++++------------------- src/object_cmd.cpp | 16 +--- src/station.cpp | 2 +- src/station_cmd.cpp | 46 ++++------ src/station_gui.cpp | 19 ++-- src/town_cmd.cpp | 202 ++++++++++++++----------------------------- src/water_cmd.cpp | 17 ++-- 10 files changed, 223 insertions(+), 412 deletions(-) diff --git a/src/genworld.cpp b/src/genworld.cpp index a8ef616a47..870e9dc082 100644 --- a/src/genworld.cpp +++ b/src/genworld.cpp @@ -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(user_data); - std::tuple result = Command::Do(DoCommandFlag::Execute, tile, TSZ_SMALL, town.is_city, _settings_game.economy.town_layout, false, 0, town.name); - - TownID id = std::get(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 result = Command::Do(DoCommandFlag::Execute, tile, TSZ_SMALL, is_city, _settings_game.economy.town_layout, false, 0, name); + + town_id = std::get(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::Post(tile, town.name); + if (town_id == TownID::Invalid()) { + Command::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. */ diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp index c702948c51..d58a577b32 100644 --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -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 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::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(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 cur_company(_current_company, OWNER_NONE); + Command::Do(DoCommandFlag::Execute, tile); + + /* Add according value to waiting cargo. */ + itp->waiting = ClampTo(itp->waiting + ScaleByCargoScale(45, false)); + break; } } diff --git a/src/landscape.cpp b/src/landscape.cpp index 99521b2c99..7865895cab 100644 --- a/src/landscape.cpp +++ b/src/landscape.cpp @@ -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(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(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::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 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; } } diff --git a/src/newgrf_house.cpp b/src/newgrf_house.cpp index 1a61804a2d..d9d67beee7 100644 --- a/src/newgrf_house.cpp +++ b/src/newgrf_house.cpp @@ -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; } diff --git a/src/object_cmd.cpp b/src/object_cmd.cpp index 4417d76f4e..395a622890 100644 --- a/src/object_cmd.cpp +++ b/src/object_cmd.cpp @@ -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; } diff --git a/src/station.cpp b/src/station.cpp index 2dbb04c312..737a37de4e 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -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 */ diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 22e648c8f1..92d20d6e8f 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -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; diff --git a/src/station_gui.cpp b/src/station_gui.cpp index fa35efccb5..0644d0a2c1 100644 --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -2178,14 +2178,12 @@ static std::vector _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 -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, &ctx); + for (auto tile : SpiralTileSequence(TileAddByDir(ctx.tile, DIR_N), max_dist, ta.w, ta.h)) { + AddNearbyStation(tile, &ctx); + } return nullptr; } diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index 50f868ba87..6cb0b5ff27 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -1307,27 +1307,6 @@ static bool CanRoadContinueIntoNextTile(const Town *t, const TileIndex tile, con return Command::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 cur_company(_current_company, OWNER_NONE); - Command::Do(DoCommandFlag::Execute, statue_data.best_position); + Command::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(); } diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp index 5c00ea0bfe..6fb6b6889e 100644 --- a/src/water_cmd.cpp +++ b/src/water_cmd.cpp @@ -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;