From 82fa5e716474f17bbbdfdfc3b071561582b456bb Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Thu, 29 Sep 2022 09:14:42 +0100 Subject: [PATCH 1/2] Change: Scale towns/industries by amount of land tiles. This stops a large map with lots of water being crammed with towns and industries even when set to Very Low. --- src/genworld.cpp | 2 ++ src/industry_cmd.cpp | 3 ++- src/map.cpp | 11 +++++++++++ src/map_func.h | 13 +++++++++++++ src/town_cmd.cpp | 3 ++- 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/genworld.cpp b/src/genworld.cpp index 4898567549..6374892d83 100644 --- a/src/genworld.cpp +++ b/src/genworld.cpp @@ -134,11 +134,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/industry_cmd.cpp b/src/industry_cmd.cpp index b29e40a824..f4a16558ec 100644 --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -43,6 +43,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" @@ -2356,7 +2357,7 @@ static uint GetNumberOfIndustries() if (difficulty == ID_CUSTOM) return std::min(IndustryPool::MAX_SIZE, _settings_game.game_creation.custom_industry_number); - return std::min(IndustryPool::MAX_SIZE, Map::ScaleBySize(numof_industry_table[difficulty])); + return std::min(IndustryPool::MAX_SIZE, Map::ScaleByLandProportion(Map::ScaleBySize(numof_industry_table[difficulty]))); } /** diff --git a/src/map.cpp b/src/map.cpp index 67fbd4da1e..7fdf42caec 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -24,6 +24,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 @@ -59,6 +61,15 @@ 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; + } +} + #ifdef _DEBUG TileIndex TileAdd(TileIndex tile, TileIndexDiff offset) diff --git a/src/map_func.h b/src/map_func.h index 370099b43f..3a917c7486 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 c3278a3b93..ece9d155e8 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -53,6 +53,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" @@ -2424,7 +2425,7 @@ bool GenerateTowns(TownLayout layout) { uint current_number = 0; uint difficulty = (_game_mode != GM_EDITOR) ? _settings_game.difficulty.number_towns : 0; - uint total = (difficulty == (uint)CUSTOM_TOWN_NUMBER_DIFFICULTY) ? _settings_game.game_creation.custom_town_number : Map::ScaleBySize(_num_initial_towns[difficulty] + (Random() & 7)); + uint total = (difficulty == (uint)CUSTOM_TOWN_NUMBER_DIFFICULTY) ? _settings_game.game_creation.custom_town_number : Map::ScaleByLandProportion(Map::ScaleBySize(_num_initial_towns[difficulty] + (Random() & 7))); total = std::min(TownPool::MAX_SIZE, total); uint32_t townnameparts; TownNames town_names; From 9f9a074d275d5b54c8c2f234579a6c95294f12f0 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Sat, 15 Feb 2025 18:19:14 +0000 Subject: [PATCH 2/2] Change: Scale land industries separately from water industries. --- src/genworld.h | 3 +- src/genworld_gui.cpp | 5 +- src/industry_cmd.cpp | 111 ++++++++++++++++++++++++++++--------------- src/lang/english.txt | 3 +- 4 files changed, 80 insertions(+), 42 deletions(-) diff --git a/src/genworld.h b/src/genworld.h index 97bbdcfef1..830a36892b 100644 --- a/src/genworld.h +++ b/src/genworld.h @@ -75,7 +75,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 ec082d5a08..79003be713 100644 --- a/src/genworld_gui.cpp +++ b/src/genworld_gui.cpp @@ -1348,7 +1348,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, @@ -1458,7 +1459,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 f4a16558ec..81d938d7c3 100644 --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -2289,9 +2289,11 @@ static Industry *CreateNewIndustry(TileIndex tile, IndustryType type, IndustryAv * @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) || @@ -2357,7 +2359,7 @@ static uint GetNumberOfIndustries() if (difficulty == ID_CUSTOM) return std::min(IndustryPool::MAX_SIZE, _settings_game.game_creation.custom_industry_number); - return std::min(IndustryPool::MAX_SIZE, Map::ScaleByLandProportion(Map::ScaleBySize(numof_industry_table[difficulty]))); + return std::min(IndustryPool::MAX_SIZE, Map::ScaleBySize(numof_industry_table[difficulty])); } /** @@ -2383,11 +2385,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(); @@ -2441,6 +2443,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. @@ -2449,46 +2476,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(); } @@ -3092,7 +3127,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 aa446b8f0e..1bce43775d 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -3451,7 +3451,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