diff --git a/src/genworld.cpp b/src/genworld.cpp index 870e9dc082..95fddd60f0 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 a3079d471d..d59cff228a 100644 --- a/src/genworld_gui.cpp +++ b/src/genworld_gui.cpp @@ -1327,7 +1327,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, @@ -1435,7 +1436,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 8f6892ef4b..8348212177 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" @@ -2284,9 +2285,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) || @@ -2378,11 +2381,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(); @@ -2436,6 +2439,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. @@ -2444,46 +2472,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(); } @@ -3083,7 +3119,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 20ffc52218..eec57a788b 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -3454,7 +3454,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..cf994b694b 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,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 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 5f07725944..8e0e67a20d 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" @@ -2384,7 +2385,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;