From a1920fc225083a49769da70abac7df7a3f5a70e0 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Fri, 24 Oct 2025 21:32:09 +0100 Subject: [PATCH] Change: Scale towns/industries by amount of land tiles. (#10063) --- src/genworld.cpp | 2 + src/genworld.h | 3 +- src/genworld_gui.cpp | 5 +- src/industry_cmd.cpp | 111 ++++++++++++++++++++++++++++--------------- src/lang/english.txt | 3 +- src/map.cpp | 16 +++++++ src/map_func.h | 13 +++++ src/town_cmd.cpp | 5 +- 8 files changed, 115 insertions(+), 43 deletions(-) diff --git a/src/genworld.cpp b/src/genworld.cpp index effa9e16e7..1e52fd9a24 100644 --- a/src/genworld.cpp +++ b/src/genworld.cpp @@ -139,11 +139,13 @@ static void _GenerateWorld() if (_game_mode != GM_MENU) FlatEmptyWorld(_settings_game.game_creation.se_flat_world_height); ConvertGroundTilesIntoWaterTiles(); + Map::CountLandTiles(); IncreaseGeneratingWorldProgress(GWP_OBJECT); _settings_game.game_creation.snow_line_height = DEF_SNOWLINE_HEIGHT; } else { GenerateClearTile(); + Map::CountLandTiles(); /* Only generate towns, tree and industries in newgame mode. */ if (_game_mode != GM_EDITOR) { diff --git a/src/genworld.h b/src/genworld.h index 0bfd8455ec..f571971260 100644 --- a/src/genworld.h +++ b/src/genworld.h @@ -64,7 +64,8 @@ enum GenWorldProgress : uint8_t { GWP_RIVER, ///< Create the rivers GWP_ROUGH_ROCKY, ///< Make rough and rocky areas GWP_TOWN, ///< Generate towns - GWP_INDUSTRY, ///< Generate industries + GWP_LAND_INDUSTRY, ///< Generate industries + GWP_WATER_INDUSTRY, ///< Generate industries GWP_OBJECT, ///< Generate objects (radio tower, light houses) GWP_TREE, ///< Generate trees GWP_GAME_INIT, ///< Initialize the game diff --git a/src/genworld_gui.cpp b/src/genworld_gui.cpp index 8bef4e4c81..12d78035be 100644 --- a/src/genworld_gui.cpp +++ b/src/genworld_gui.cpp @@ -1349,7 +1349,8 @@ static const StringID _generation_class_table[] = { STR_GENERATION_RIVER_GENERATION, STR_GENERATION_CLEARING_TILES, STR_GENERATION_TOWN_GENERATION, - STR_GENERATION_INDUSTRY_GENERATION, + STR_GENERATION_LAND_INDUSTRY_GENERATION, + STR_GENERATION_WATER_INDUSTRY_GENERATION, STR_GENERATION_OBJECT_GENERATION, STR_GENERATION_TREE_GENERATION, STR_GENERATION_SETTINGUP_GAME, @@ -1456,7 +1457,7 @@ void ShowGenerateWorldProgress() static void _SetGeneratingWorldProgress(GenWorldProgress cls, uint progress, uint total) { - static const int percent_table[] = {0, 5, 14, 17, 20, 40, 60, 65, 80, 85, 95, 99, 100 }; + static const int percent_table[] = {0, 5, 14, 17, 20, 40, 55, 60, 65, 80, 85, 95, 99, 100 }; static_assert(lengthof(percent_table) == GWP_CLASS_COUNT + 1); assert(cls < GWP_CLASS_COUNT); diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp index 565d86c374..292f379f8e 100644 --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -45,6 +45,7 @@ #include "industry_cmd.h" #include "landscape_cmd.h" #include "terraform_cmd.h" +#include "map_func.h" #include "timer/timer.h" #include "timer/timer_game_calendar.h" #include "timer/timer_game_economy.h" @@ -2290,12 +2291,15 @@ static Industry *CreateNewIndustry(TileIndex tile, IndustryType type, IndustryAv /** * Compute the appearance probability for an industry during map creation. * @param it Industry type to compute. + * @param water Whether to get probability of land-based, water-based, (or both, if std::nullopt), industry types. * @param[out] force_at_least_one Returns whether at least one instance should be forced on map creation. * @return Relative probability for the industry to appear. */ -static uint32_t GetScaledIndustryGenerationProbability(IndustryType it, bool *force_at_least_one) +static uint32_t GetScaledIndustryGenerationProbability(IndustryType it, std::optional water, bool *force_at_least_one) { const IndustrySpec *ind_spc = GetIndustrySpec(it); + if (water.has_value() && ind_spc->behaviour.Test(IndustryBehaviour::BuiltOnWater) != *water) return 0; + uint32_t chance = ind_spc->appear_creation[to_underlying(_settings_game.game_creation.landscape)]; if (!ind_spc->enabled || ind_spc->layouts.empty() || (_game_mode != GM_EDITOR && _settings_game.difficulty.industry_density == ID_FUND_ONLY) || @@ -2387,11 +2391,11 @@ static Industry *PlaceIndustry(IndustryType type, IndustryAvailabilityCallType c * @param type IndustryType of the desired industry * @param try_hard Try very hard to find a place. (Used to place at least one industry per type) */ -static void PlaceInitialIndustry(IndustryType type, bool try_hard) +static void PlaceInitialIndustry(IndustryType type, bool water, bool try_hard) { Backup cur_company(_current_company, OWNER_NONE); - IncreaseGeneratingWorldProgress(GWP_INDUSTRY); + IncreaseGeneratingWorldProgress(water ? GWP_WATER_INDUSTRY : GWP_LAND_INDUSTRY); PlaceIndustry(type, IACT_MAPGENERATION, try_hard); cur_company.Restore(); @@ -2445,6 +2449,31 @@ void IndustryBuildData::EconomyMonthlyLoop() } } +struct IndustryGenerationProbabilities { + std::array probs{}; + std::array force_one{}; + uint64_t total = 0; + uint num_forced = 0; +}; + +/** + * Get scaled industry generation probabilities. + * @param water Whether to get land or water industry probabilities. + * @returns Probability information. + */ +static IndustryGenerationProbabilities GetScaledProbabilities(bool water) +{ + IndustryGenerationProbabilities p{}; + + for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) { + p.probs[it] = GetScaledIndustryGenerationProbability(it, water, &p.force_one[it]); + p.total += p.probs[it];; + if (p.force_one[it]) p.num_forced++; + } + + return p; +} + /** * This function will create random industries during game creation. * It will scale the amount of industries by mapsize and difficulty level. @@ -2453,46 +2482,54 @@ void GenerateIndustries() { if (_game_mode != GM_EDITOR && _settings_game.difficulty.industry_density == ID_FUND_ONLY) return; // No industries in the game. - uint32_t industry_probs[NUM_INDUSTRYTYPES]; - bool force_at_least_one[NUM_INDUSTRYTYPES]; - uint32_t total_prob = 0; - uint num_forced = 0; + /* Get the probabilities for all industries. This is done first as we need the total of + * both land and water for scaling later. */ + IndustryGenerationProbabilities lprob = GetScaledProbabilities(false); + IndustryGenerationProbabilities wprob = GetScaledProbabilities(true); - for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) { - industry_probs[it] = GetScaledIndustryGenerationProbability(it, force_at_least_one + it); - total_prob += industry_probs[it]; - if (force_at_least_one[it]) num_forced++; - } + /* Run generation twice, for land and water industries in turn. */ + for (bool water = false;; water = true) { + auto &p = water ? wprob : lprob; - uint total_amount = GetNumberOfIndustries(); - if (total_prob == 0 || total_amount < num_forced) { - /* Only place the forced ones */ - total_amount = num_forced; - } + /* Total number of industries scaled by land/water proportion. */ + uint total_amount = p.total * GetNumberOfIndustries() / (lprob.total + wprob.total); - SetGeneratingWorldProgress(GWP_INDUSTRY, total_amount); + /* Scale land-based industries to the land proportion. */ + if (!water) total_amount = Map::ScaleByLandProportion(total_amount); - /* Try to build one industry per type independent of any probabilities */ - for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) { - if (force_at_least_one[it]) { - assert(total_amount > 0); - total_amount--; - PlaceInitialIndustry(it, true); + /* Ensure that forced industries are generated even if the scaled amounts are too low. */ + if (p.total == 0 || total_amount < p.num_forced) { + /* Only place the forced ones */ + total_amount = p.num_forced; } + + SetGeneratingWorldProgress(water ? GWP_WATER_INDUSTRY : GWP_LAND_INDUSTRY, total_amount); + + /* Try to build one industry per type independent of any probabilities */ + for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) { + if (p.force_one[it]) { + assert(total_amount > 0); + total_amount--; + PlaceInitialIndustry(it, water, true); + } + } + + /* Add the remaining industries according to their probabilities */ + for (uint i = 0; i < total_amount; i++) { + uint32_t r = RandomRange(p.total); + IndustryType it = 0; + while (r >= p.probs[it]) { + r -= p.probs[it]; + it++; + assert(it < NUM_INDUSTRYTYPES); + } + assert(p.probs[it] > 0); + PlaceInitialIndustry(it, water, false); + } + + if (water) break; } - /* Add the remaining industries according to their probabilities */ - for (uint i = 0; i < total_amount; i++) { - uint32_t r = RandomRange(total_prob); - IndustryType it = 0; - while (r >= industry_probs[it]) { - r -= industry_probs[it]; - it++; - assert(it < NUM_INDUSTRYTYPES); - } - assert(industry_probs[it] > 0); - PlaceInitialIndustry(it, false); - } _industry_builder.Reset(); } @@ -3119,7 +3156,7 @@ void CheckIndustries() if (Industry::GetIndustryTypeCount(it) > 0) continue; // Types of existing industries can be skipped. bool force_at_least_one; - uint32_t chance = GetScaledIndustryGenerationProbability(it, &force_at_least_one); + uint32_t chance = GetScaledIndustryGenerationProbability(it, std::nullopt, &force_at_least_one); if (chance == 0 || !force_at_least_one) continue; // Types that are not available can be skipped. const IndustrySpec *is = GetIndustrySpec(it); diff --git a/src/lang/english.txt b/src/lang/english.txt index 9385e9f4cb..566fc9ad01 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -3473,7 +3473,8 @@ STR_GENERATION_LANDSCAPE_GENERATION :{BLACK}Landscap STR_GENERATION_RIVER_GENERATION :{BLACK}River generation STR_GENERATION_CLEARING_TILES :{BLACK}Rough and rocky area generation STR_GENERATION_TOWN_GENERATION :{BLACK}Town generation -STR_GENERATION_INDUSTRY_GENERATION :{BLACK}Industry generation +STR_GENERATION_LAND_INDUSTRY_GENERATION :{BLACK}Land industry generation +STR_GENERATION_WATER_INDUSTRY_GENERATION :{BLACK}Water industry generation STR_GENERATION_OBJECT_GENERATION :{BLACK}Object generation STR_GENERATION_TREE_GENERATION :{BLACK}Tree generation STR_GENERATION_SETTINGUP_GAME :{BLACK}Setting up game diff --git a/src/map.cpp b/src/map.cpp index efb5168cbf..a6dc2af46b 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -23,6 +23,8 @@ /* static */ uint Map::size; ///< The number of tiles on the map /* static */ uint Map::tile_mask; ///< _map_size - 1 (to mask the mapsize) +/* static */ uint Map::initial_land_count; ///< Initial number of land tiles on the map. + /* static */ std::unique_ptr Tile::base_tiles; ///< Base tiles of the map /* static */ std::unique_ptr Tile::extended_tiles; ///< Extended tiles of the map @@ -58,6 +60,20 @@ AllocateWaterRegions(); } +/* static */ void Map::CountLandTiles() +{ + /* Count number of tiles that are land. */ + Map::initial_land_count = 0; + for (const auto tile : Map::Iterate()) { + Map::initial_land_count += IsWaterTile(tile) ? 0 : 1; + } + + /* Compensate for default values being set for (or users are most familiar with) at least + * very low sea level. Dividing by 12 adds roughly 8%. */ + Map::initial_land_count += Map::initial_land_count / 12; + Map::initial_land_count = std::min(Map::initial_land_count, Map::size); +} + #ifdef _DEBUG TileIndex TileAdd(TileIndex tile, TileIndexDiff offset) diff --git a/src/map_func.h b/src/map_func.h index e1929c2f91..1854de91b3 100644 --- a/src/map_func.h +++ b/src/map_func.h @@ -239,8 +239,11 @@ private: static uint size; ///< The number of tiles on the map static uint tile_mask; ///< _map_size - 1 (to mask the mapsize) + static uint initial_land_count; ///< Initial number of land tiles on the map. + public: static void Allocate(uint size_x, uint size_y); + static void CountLandTiles(); /** * Logarithm of the map size along the X side. @@ -307,6 +310,16 @@ public: return Map::SizeY() - 1; } + /** + * Scales the given value by the number of water tiles. + * @param n the value to scale + * @return the scaled size + */ + static inline uint ScaleByLandProportion(uint n) + { + /* Use 64-bit arithmetic to avoid overflow. */ + return static_cast(static_cast(n) * Map::initial_land_count / Map::size); + } /** * 'Wraps' the given "tile" so it is within the map. diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index b3c95ba053..4ba9143c84 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -55,6 +55,7 @@ #include "road_cmd.h" #include "terraform_cmd.h" #include "tunnelbridge_cmd.h" +#include "map_func.h" #include "timer/timer.h" #include "timer/timer_game_calendar.h" #include "timer/timer_game_economy.h" @@ -2420,9 +2421,9 @@ bool GenerateTowns(TownLayout layout, std::optional number) if (number.has_value()) { total = number.value(); } else if (_settings_game.difficulty.number_towns == static_cast(CUSTOM_TOWN_NUMBER_DIFFICULTY)) { - total = GetDefaultTownsForMapSize(); + total = Map::ScaleByLandProportion(GetDefaultTownsForMapSize()); } else { - total = GetDefaultTownsForMapSize() + (Random() & 7); + total = Map::ScaleByLandProportion(GetDefaultTownsForMapSize() + (Random() & 7)); } total = std::min(TownPool::MAX_SIZE, total);