diff --git a/src/core/math_func.hpp b/src/core/math_func.hpp index 67b347f2b4..5641a66682 100644 --- a/src/core/math_func.hpp +++ b/src/core/math_func.hpp @@ -365,4 +365,22 @@ constexpr uint64_t PowerOfTen(int power) uint32_t IntSqrt(uint32_t num); + +/** + * Simple 32 bit to 32 bit hash. + * From MurmurHash3. + * @param h The value to take the hash of. + * @return The hash of the value. + */ +inline uint32_t SimpleHash32(uint32_t h) +{ + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + + return h; +} + #endif /* MATH_FUNC_HPP */ diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index a820c7c4f1..e7005b73b4 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -383,6 +383,7 @@ enum SaveLoadVersion : uint16_t { SLV_GROUP_NUMBERS, ///< 336 PR#12297 Add per-company group numbers. SLV_INCREASE_STATION_TYPE_FIELD_SIZE, ///< 337 PR#12572 Increase size of StationType field in map array SLV_ROAD_WAYPOINTS, ///< 338 PR#12572 Road waypoints + SLV_PROCEDURAL_TREE_GROWTH, ///< 339 PR#11955 Procedural tree growth. SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/table/settings/world_settings.ini b/src/table/settings/world_settings.ini index 6b5d1466e0..9ad46faf71 100644 --- a/src/table/settings/world_settings.ini +++ b/src/table/settings/world_settings.ini @@ -579,4 +579,5 @@ max = 3 str = STR_CONFIG_SETTING_EXTRA_TREE_PLACEMENT strhelp = STR_CONFIG_SETTING_EXTRA_TREE_PLACEMENT_HELPTEXT strval = STR_CONFIG_SETTING_EXTRA_TREE_PLACEMENT_NO_SPREAD +post_cb = [](auto) { MarkWholeScreenDirty(); } cat = SC_BASIC diff --git a/src/tree_cmd.cpp b/src/tree_cmd.cpp index 1cf11de25d..30a6dc4f56 100644 --- a/src/tree_cmd.cpp +++ b/src/tree_cmd.cpp @@ -58,6 +58,10 @@ static const uint16_t DEFAULT_TREE_STEPS = 1000; ///< Default number static const uint16_t DEFAULT_RAINFOREST_TREE_STEPS = 15000; ///< Default number of attempts for placing extra trees at rainforest in tropic. static const uint16_t EDITOR_TREE_DIV = 5; ///< Game editor tree generation divisor factor. +/* Pre-randomized tree growth cycles (for procedural growth). */ +static const uint8_t RANDOM_GROWTH_SPREAD[] = {141, 66, 6, 10, 14, 46, 46, 78, 78, 78, 78, 67, 7, 11, 15, 47, 79, 19, 23, 27, 14, 78, 18, 22, 26, 13, 66, 6, 10, 14, 46, 46, 67, 7, 11, 15, 19, 23, 27, 14, 46, 46, 67, 7, 11, 15, 79, 19, 23, 27, 14, 78, 78, 46, 46, 46, 78, 46, 46, 46, 46, 46, 67, 7, 11, 15, 19, 23, 27, 14, 46, 18, 22, 26, 13, 77, 66, 6, 10, 14, 46, 18, 22, 26, 13, 45, 45, 45, 45, 45, 66, 6, 10, 14, 67, 7, 11, 15, 19, 23, 27, 14, 18, 22, 26, 13, 45, 45, 45, 66, 6, 10, 14, 18, 22, 26, 13, 45, 45, 45, 45, 17, 21, 25, 12, 44, 44, 44, 76, 44, 65, 5, 9, 13, 66, 6, 10, 14, 46, 46, 67, 7, 11, 15, 47, 47, 47, 79, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 47, 47, 19, 23, 27, 14, 67, 7, 11, 15, 47, 79, 47, 47, 79, 47, 47, 19, 23, 27, 14, 46, 46, 67, 7, 11, 15, 47, 47, 47, 47, 79, 47, 47, 47, 47, 47, 19, 23, 27, 14, 78, 46, 46, 46, 46, 46, 78, 46, 46, 46, 78, 46, 67, 7, 11, 15, 79, 79, 47, 47, 79, 47, 79, 47, 47, 47, 79, 79, 79, 79, 47, 47, 47, 19, 23, 27, 14, 46, 46, 46, 18, 22, 26, 13, 45, 66, 6, 10, 14, 46, 18, 22, 26, 13, 45, 45, 45, 17, 21, 25, 12, 16, 20, 24, 141, 77, 45, 45, 45, 77, 45, 77, 45, 17, 21, 25, 12, 76, 44, 44, 44, 44, 16, 20, 24, 141, 45, 17, 21, 25, 12, 44, 76, 76, 44, 44, 44, 44, 44, 44, 76, 44, 44, 65, 5, 9, 13, 45, 77, 17, 21, 25, 12, 44, 44, 44, 44, 44, 44, 44, 44, 76, 16, 20, 24, 141, 17, 21, 25, 12, 44, 44, 76, 16, 20, 24, 141, 45, 17, 21, 25, 12, 44, 16, 20, 24, 141, 66, 6, 10, 14, 46, 46, 67, 7, 11, 15, 47, 47, 47, 47, 79, 47, 47, 47, 47, 47, 47, 47, 79, 47, 47, 19, 23, 27, 14, 46, 46, 46, 67, 7, 11, 15, 47, 79, 47, 47, 79, 47, 47, 47, 79, 47, 19, 23, 27, 14, 46, 46, 46, 18, 22, 26, 13, 45, 45, 17, 21, 25, 12, 65, 5, 9, 13, 45, 45, 45, 45, 45, 17, 21, 25, 12, 44, 65, 5, 9, 13, 77, 45, 45, 45, 45, 77, 45, 45, 45, 77, 45, 77, 17, 21, 25, 12, 44, 44, 76, 16, 20, 24, 141, 45, 45, 45, 45, 45, 45, 77, 45, 77, 66, 6, 10, 14, 46, 46, 46, 67, 7, 11, 15, 79, 79, 79, 79, 79, 47, 19, 23, 27, 14, 46, 78, 18, 22, 26, 13, 45, 45, 45, 45, 45, 45, 66, 6, 10, 14, 46, 78, 78, 46, 46, 46, 46, 46, 67, 7, 11, 15, 79, 47, 47, 47, 47, 47, 47, 79, 47, 47, 19, 23, 27, 14, 46, 46, 67, 7, 11, 15, 47, 47, 79, 47, 47, 47, 47, 19, 23, 27, 14, 78, 46, 67, 7, 11, 15, 19, 23, 27, 14, 46, 67, 7, 11, 15, 47, 19, 23, 27, 14, 46, 78, 46, 67, 7, 11, 15, 79, 79, 47, 47, 47, 47, 47, 47, 47, 79, 19, 23, 27, 14, 46, 18, 22, 26, 13, 17, 21, 25, 12, 44, 44, 44, 44, 16, 20, 24, 141, 66, 6, 10, 14, 46, 46, 46, 78, 46, 46, 18, 22, 26, 13, 45, 45, 45, 45, 45, 45, 45, 66, 6, 10, 14, 67, 7, 11, 15, 79, 47, 19, 23, 27, 14, 46, 46, 46, 78, 18, 22, 26, 13, 17, 21, 25, 12, 44, 44, 16, 20, 24, 141, 45, 45, 66, 6, 10, 14, 46, 18, 22, 26, 13, 17, 21, 25, 12, 44, 44, 44, 76, 44, 44, 65, 5, 9, 13, 17, 21, 25, 12, 44, 44, 76, 44, 16, 20, 24, 141, 77, 45, 66, 6, 10, 14, 67, 7, 11, 15, 19, 23, 27, 14, 46, 67, 7, 11, 15, 79, 47, 47, 79, 79, 79, 47, 79, 47, 47, 47, 19, 23, 27, 14, 46, 46, 46, 46, 46, 78, 46, 67, 7, 11, 15, 47, 79, 79, 79, 19, 23, 27, 14, 46, 78, 46, 67, 7, 11, 15, 47, 47, 19, 23, 27, 14, 46, 67, 7, 11, 15, 19, 23, 27, 14, 46, 46, 67, 7, 11, 15, 79, 47, 47, 47, 47, 19, 23, 27, 14, 46, 46, 18, 22, 26, 13, 45, 66, 6, 10, 14, 46, 46, 78, 46, 18, 22, 26, 13, 17, 21, 25, 12, 44, 44, 44, 76, 44, 16, 20, 24, 141, 45, 66, 6, 10, 14, 46, 46, 78, 46, 46, 46, 46, 46, 46, 18, 22, 26, 13, 66, 6, 10, 14, 67, 7, 11, 15, 19, 23, 27, 14, 78, 67, 7, 11, 15, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 47, 47, 19, 23, 27, 14, 46, 78, 46, 18, 22, 26, 13, 66, 6, 10, 14, 46, 67, 7, 11, 15, 47, 19, 23, 27, 14, 46, 46, 46, 46, 67, 7, 11, 15, 47, 47, 47, 19, 23, 27, 14, 46, 46, 46, 46, 67, 7, 11, 15, 79, 47, 79, 47, 47, 47, 47, 79, 47, 47, 47, 79, 79, 47, 47, 19, 23, 27, 14, 46, 18, 22, 26, 13, 45, 77, 45, 45, 45, 45, 77, 77, 45, 17, 21, 25, 12, 16, 20, 24, 141, 45, 45, 45, 45, 66, 6, 10, 14, 18, 22, 26, 13, 45, 45, 45, 45, 66, 6, 10, 14, 46, 78, 46, 78, 46, 78, 67, 7, 11, 15, 47, 19, 23, 27, 14, 46, 67, 7, 11, 15, 19, 23, 27, 14, 18, 22, 26, 13, 45, 45, 17, 21, 25, 12, 16, 20, 24}; +static const uint8_t RANDOM_GROWTH_NO_SPREAD[] = {44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 16, 20, 24, 0, 4, 8, 12, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 16, 20, 24, 0, 4, 8, 12, 16, 20, 24, 0, 4, 8, 12, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 16, 20, 24, 0, 4, 8, 12, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 16, 20, 24, 0, 4, 8, 12, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 16, 20, 24, 0, 4, 8, 12, 44}; + /** * Tests if a tile can be converted to MP_TREES * This is true for clear ground without farms or rocks. @@ -134,21 +138,44 @@ static void PlantTreesOnTile(TileIndex tile, TreeType treetype, uint count, Tree static TreeType GetRandomTreeType(TileIndex tile, uint seed) { switch (_settings_game.game_creation.landscape) { - case LT_TEMPERATE: - return static_cast(seed * TREE_COUNT_TEMPERATE / 256 + TREE_TEMPERATE); - - case LT_ARCTIC: - return static_cast(seed * TREE_COUNT_SUB_ARCTIC / 256 + TREE_SUB_ARCTIC); - + case LT_TEMPERATE: return TREE_RANDOM_TEMPERATE; + case LT_ARCTIC: return TREE_RANDOM_ARCTIC; case LT_TROPIC: switch (GetTropicZone(tile)) { - case TROPICZONE_NORMAL: return static_cast(seed * TREE_COUNT_SUB_TROPICAL / 256 + TREE_SUB_TROPICAL); - case TROPICZONE_DESERT: return static_cast((seed > 12) ? TREE_INVALID : TREE_CACTUS); - default: return static_cast(seed * TREE_COUNT_RAINFOREST / 256 + TREE_RAINFOREST); + case TROPICZONE_NORMAL: return TREE_RANDOM_TROPIC_NORMAL; + case TROPICZONE_DESERT: return ((seed > 12) ? TREE_INVALID : TREE_CACTUS); + case TROPICZONE_RAINFOREST: return TREE_RANDOM_TROPIC_RAINFOREST; + default: NOT_REACHED(); } + case LT_TOYLAND: return TREE_RANDOM_TOYLAND; + default: NOT_REACHED(); + } +} +/** + * Get a TreeType for the given tile. Random procedural tree types are coverted to + * specific ones based on the tile hash. + * + * @param tile The tile to get a TreeType from + * @param tile_hash The hash of this tile, used as source of randomness + * @return The tree type + */ +static TreeType GetProceduralTreeType(TileIndex tile, uint32_t tile_hash) +{ + TreeType type = GetTreeType(tile); + switch (type) { + case TREE_RANDOM_TEMPERATE: + return static_cast(tile_hash % TREE_COUNT_TEMPERATE + TREE_TEMPERATE); + case TREE_RANDOM_ARCTIC: + return static_cast(tile_hash % TREE_COUNT_SUB_ARCTIC + TREE_SUB_ARCTIC); + case TREE_RANDOM_TROPIC_NORMAL: + return static_cast(tile_hash % TREE_COUNT_SUB_TROPICAL + TREE_SUB_TROPICAL); + case TREE_RANDOM_TROPIC_RAINFOREST: + return static_cast(tile_hash % TREE_COUNT_RAINFOREST + TREE_RAINFOREST); + case TREE_RANDOM_TOYLAND: + return static_cast(tile_hash % TREE_COUNT_TOYLAND + TREE_TOYLAND); default: - return static_cast(seed * TREE_COUNT_TOYLAND / 256 + TREE_TOYLAND); + return type; } } @@ -166,7 +193,7 @@ static void PlaceTree(TileIndex tile, uint32_t r) TreeType tree = GetRandomTreeType(tile, GB(r, 24, 8)); if (tree != TREE_INVALID) { - PlantTreesOnTile(tile, tree, GB(r, 22, 2), static_cast(std::min(GB(r, 16, 3), 6))); + PlantTreesOnTile(tile, tree, 0, TreeGrowthStage::Procedural); MarkTileDirtyByTile(tile); /* Rerandomize ground, if neither snow nor shore */ @@ -515,6 +542,34 @@ struct TreeListEnt : PalSpriteID { uint8_t x, y; }; +/** + * Determines whether new trees can be planted on the tile or around it according to game settings. + * @param tile The tile to check. + * @return True iff new trees can be planted. + */ +static bool CanPlantExtraTrees(TileIndex tile) +{ + if (_settings_game.game_creation.landscape == LT_TROPIC && GetTropicZone(tile) == TROPICZONE_RAINFOREST) { + return (_settings_game.construction.extra_tree_placement == ETP_SPREAD_ALL || _settings_game.construction.extra_tree_placement == ETP_SPREAD_RAINFOREST); + } + return _settings_game.construction.extra_tree_placement == ETP_SPREAD_ALL; +} + +/** + * Picks predefined tree growth state based on input parameters. + * @param tile_hash The hash of the tile. + * @param can_plant_extra Whether new trees can be planted (result of CanPlantExtraTrees call). + * @param counter Time counter. + * @return tree growth state (bits 0..1 - tree count, 2..4 - growth stage). + */ +static uint8_t PickRandomGrowth(uint32_t tile_hash, bool can_plant_extra, uint32_t counter) +{ + if (can_plant_extra) return RANDOM_GROWTH_SPREAD[(tile_hash + counter) % lengthof(RANDOM_GROWTH_SPREAD)]; + uint8_t value = RANDOM_GROWTH_NO_SPREAD[(tile_hash + counter) % lengthof(RANDOM_GROWTH_NO_SPREAD)]; + /* When trees don't spread their count doesn't change so just use a random one. */ + return value | (tile_hash % 4); +} + static void DrawTile_Trees(TileInfo *ti) { switch (GetTreeGround(ti->tile)) { @@ -528,11 +583,14 @@ static void DrawTile_Trees(TileInfo *ti) if (IsInvisibilitySet(TO_TREES)) return; uint tmp = CountBits(ti->tile.base() + ti->x + ti->y); - uint index = GB(tmp, 0, 2) + (GetTreeType(ti->tile) << 2); + uint32_t tile_hash = SimpleHash32(ti->tile.base()); + auto tt = GetProceduralTreeType(ti->tile, tile_hash); + uint index = GB(tmp, 0, 2) + (tt << 2); + uint density = GetTreeDensity(ti->tile); /* different tree styles above one of the grounds */ if ((GetTreeGround(ti->tile) == TREE_GROUND_SNOW_DESERT || GetTreeGround(ti->tile) == TREE_GROUND_ROUGH_SNOW) && - GetTreeDensity(ti->tile) >= 2 && + density >= 2 && IsInsideMM(index, TREE_SUB_ARCTIC << 2, TREE_RAINFOREST << 2)) { index += 164 - (TREE_SUB_ARCTIC << 2); } @@ -549,9 +607,25 @@ static void DrawTile_Trees(TileInfo *ti) /* put the trees to draw in a list */ uint trees = GetTreeCount(ti->tile); + TreeGrowthStage growth = GetTreeGrowth(ti->tile); + if (growth == TreeGrowthStage::Procedural) { + uint64_t counter = 0; + + /* Use a fixed value for the counter (0) when trees aren't growing. */ + if (_settings_game.construction.extra_tree_placement != ETP_NO_GROWTH_NO_SPREAD) { + counter = TimerGameTick::counter >> 12; + + /* For procedural growth counter parity is stored instead of tree count. */ + if (counter % 2 != trees - 1) counter++; + } + + auto proc_value = PickRandomGrowth(tile_hash, CanPlantExtraTrees(ti->tile), counter); + growth = static_cast(GB(proc_value, 2, 3)); + trees = GB(proc_value, 0, 2) + 1; + } for (uint i = 0; i < trees; i++) { - SpriteID sprite = s[0].sprite + (i == trees - 1 ? static_cast(GetTreeGrowth(ti->tile)) : 3); + SpriteID sprite = s[0].sprite + (i == trees - 1 ? static_cast(growth) : 3); PaletteID pal = s[0].pal; te[i].sprite = sprite; @@ -608,7 +682,8 @@ static CommandCost ClearTile_Trees(TileIndex tile, DoCommandFlag flags) } num = GetTreeCount(tile); - if (IsInsideMM(GetTreeType(tile), TREE_RAINFOREST, TREE_CACTUS)) num *= 4; + auto tt = GetProceduralTreeType(tile, SimpleHash32(tile.base())); + if (IsInsideMM(tt, TREE_RAINFOREST, TREE_CACTUS)) num *= 4; if (flags & DC_EXEC) DoClearSquare(tile); @@ -617,7 +692,7 @@ static CommandCost ClearTile_Trees(TileIndex tile, DoCommandFlag flags) static void GetTileDesc_Trees(TileIndex tile, TileDesc *td) { - TreeType tt = GetTreeType(tile); + TreeType tt = GetProceduralTreeType(tile, SimpleHash32(tile.base())); if (IsInsideMM(tt, TREE_RAINFOREST, TREE_CACTUS)) { td->str = STR_LAI_TREE_NAME_RAINFOREST; @@ -686,13 +761,6 @@ static void TileLoopTreesAlps(TileIndex tile) MarkTileDirtyByTile(tile); } -static bool CanPlantExtraTrees(TileIndex tile) -{ - return ((_settings_game.game_creation.landscape == LT_TROPIC && GetTropicZone(tile) == TROPICZONE_RAINFOREST) ? - (_settings_game.construction.extra_tree_placement == ETP_SPREAD_ALL || _settings_game.construction.extra_tree_placement == ETP_SPREAD_RAINFOREST) : - _settings_game.construction.extra_tree_placement == ETP_SPREAD_ALL); -} - static void TileLoop_Trees(TileIndex tile) { if (GetTreeGround(tile) == TREE_GROUND_SHORE) { @@ -708,9 +776,9 @@ static void TileLoop_Trees(TileIndex tile) /* TimerGameTick::counter is incremented by 256 between each call, so ignore lower 8 bits. * Also, we use a simple hash to spread the updates evenly over the map. - * 11 and 9 are just some co-prime numbers for better spread. */ - uint32_t cycle = 11 * TileX(tile) + 9 * TileY(tile) + (TimerGameTick::counter >> 8); + uint32_t tile_hash = SimpleHash32(tile.base()); + uint32_t cycle = tile_hash + (TimerGameTick::counter >> 8); /* Handle growth of grass (under trees/on MP_TREES tiles) at every 8th processings, like it's done for grass on MP_CLEAR tiles. */ if ((cycle & 7) == 7 && GetTreeGround(tile) == TREE_GROUND_GRASS) { @@ -726,86 +794,126 @@ static void TileLoop_Trees(TileIndex tile) static const uint32_t TREE_UPDATE_FREQUENCY = 16; // How many tile updates happen for one tree update if (cycle % TREE_UPDATE_FREQUENCY != TREE_UPDATE_FREQUENCY - 1) return; - switch (GetTreeGrowth(tile)) { - case TreeGrowthStage::Grown: // regular sized tree - if (_settings_game.game_creation.landscape == LT_TROPIC && - GetTreeType(tile) != TREE_CACTUS && - GetTropicZone(tile) == TROPICZONE_DESERT) { - AddTreeGrowth(tile, 1); - } else { - switch (GB(Random(), 0, 3)) { - case 0: // start destructing - AddTreeGrowth(tile, 1); - break; + uint64_t counter = (TimerGameTick::counter >> 12) + 1; + bool can_plant_extra = CanPlantExtraTrees(tile); + uint8_t value = PickRandomGrowth(tile_hash, can_plant_extra, counter); - case 1: // add a tree - if (GetTreeCount(tile) < 4 && CanPlantExtraTrees(tile)) { - AddTreeCount(tile, 1); - SetTreeGrowth(tile, TreeGrowthStage::Growing1); + TreeGrowthStage growth = GetTreeGrowth(tile); + uint count = GetTreeCount(tile); + TreeGrowthStage proc_growth = static_cast(GB(value, 2, 3)); + uint proc_count = GB(value, 0, 2) + 1; + bool spread = false; + bool clear = false; + + /* Check if the growth state matches procedural growth so we can switch to it. */ + if (growth == proc_growth && count == proc_count) { + growth = TreeGrowthStage::Procedural; + SetTreeGrowth(tile, growth); + SetTreeCycle(tile, 0); // Tree cycle overwrites tree count + } + + if (growth == TreeGrowthStage::Procedural) { + /* Store the parity of this cycle so that drawing routine can detect + * whether the tile was alredy looped this cycle asd so chose appropriate state. + */ + SetTreeCycle(tile, counter % 2); + + /* Don't mark dirty if trees didn't change. */ + if (HasBit(value, 5)) return; + + spread = HasBit(value, 6); + clear = HasBit(value, 7); + } else { + /* Custom growth */ + switch (growth) { + case TreeGrowthStage::Grown: // regular sized tree + if (_settings_game.game_creation.landscape == LT_TROPIC && + GetTreeType(tile) != TREE_CACTUS && + GetTropicZone(tile) == TROPICZONE_DESERT) { + AddTreeGrowth(tile, 1); + } else { + switch (GB(Random(), 0, 3)) { + case 0: // start destructing + AddTreeGrowth(tile, 1); + break; + + case 1: // add a tree + spread = can_plant_extra; + if (count < 4 && spread) { + AddTreeCount(tile, 1); + SetTreeGrowth(tile, TreeGrowthStage::Growing1); + spread = false; + } + break; + + case 2: { // add a neighbouring tree + spread = can_plant_extra; break; } - [[fallthrough]]; - case 2: { // add a neighbouring tree - if (!CanPlantExtraTrees(tile)) break; - - TreeType treetype = GetTreeType(tile); - - tile += TileOffsByDir(static_cast(Random() % DIR_END)); - - /* Cacti don't spread */ - if (!CanPlantTreesOnTile(tile, false)) return; - - /* Don't plant trees, if ground was freshly cleared */ - if (IsTileType(tile, MP_CLEAR) && GetClearGround(tile) == CLEAR_GRASS && GetClearDensity(tile) != 3) return; - - PlantTreesOnTile(tile, treetype, 0, TreeGrowthStage::Growing1); - - break; + default: + return; } - - default: - return; } - } - break; + break; - case TreeGrowthStage::Dead: // final stage of tree destruction - if (!CanPlantExtraTrees(tile)) { - /* if trees can't spread just plant a new one to prevent deforestation */ - SetTreeGrowth(tile, TreeGrowthStage::Growing1); - } else if (GetTreeCount(tile) > 1) { - /* more than one tree, delete it */ - AddTreeCount(tile, -1); - SetTreeGrowth(tile, TreeGrowthStage::Grown); - } else { - /* just one tree, change type into MP_CLEAR */ - switch (GetTreeGround(tile)) { - case TREE_GROUND_SHORE: MakeShore(tile); break; - case TREE_GROUND_GRASS: MakeClear(tile, CLEAR_GRASS, GetTreeDensity(tile)); break; - case TREE_GROUND_ROUGH: MakeClear(tile, CLEAR_ROUGH, 3); break; - case TREE_GROUND_ROUGH_SNOW: { - uint density = GetTreeDensity(tile); - MakeClear(tile, CLEAR_ROUGH, 3); - MakeSnow(tile, density); - break; - } - default: // snow or desert - if (_settings_game.game_creation.landscape == LT_TROPIC) { - MakeClear(tile, CLEAR_DESERT, GetTreeDensity(tile)); - } else { - uint density = GetTreeDensity(tile); - MakeClear(tile, CLEAR_GRASS, 3); - MakeSnow(tile, density); - } - break; + case TreeGrowthStage::Dead: // final stage of tree destruction + if (!can_plant_extra) { + /* if trees can't spread just plant a new one to prevent deforestation */ + SetTreeGrowth(tile, TreeGrowthStage::Growing1); + } else if (count > 1) { + /* more than one tree, delete it */ + AddTreeCount(tile, -1); + SetTreeGrowth(tile, TreeGrowthStage::Grown); + } else { + /* no more trees, mark the tile for clearing */ + clear = true; } - } - break; + break; - default: - AddTreeGrowth(tile, 1); - break; + default: + AddTreeGrowth(tile, 1); + break; + } + } + + if (clear) { + /* All trees died, change type into MP_CLEAR */ + switch (GetTreeGround(tile)) { + case TREE_GROUND_SHORE: MakeShore(tile); break; + case TREE_GROUND_GRASS: MakeClear(tile, CLEAR_GRASS, GetTreeDensity(tile)); break; + case TREE_GROUND_ROUGH: MakeClear(tile, CLEAR_ROUGH, 3); break; + case TREE_GROUND_ROUGH_SNOW: { + uint density = GetTreeDensity(tile); + MakeClear(tile, CLEAR_ROUGH, 3); + MakeSnow(tile, density); + break; + } + default: // snow or desert + if (_settings_game.game_creation.landscape == LT_TROPIC) { + MakeClear(tile, CLEAR_DESERT, GetTreeDensity(tile)); + } else { + uint density = GetTreeDensity(tile); + MakeClear(tile, CLEAR_GRASS, 3); + MakeSnow(tile, density); + } + break; + } + } + + if (spread) { + TreeType treetype = GetProceduralTreeType(tile, tile_hash); + + /* We never spread while changing the current tile so it's ok to overwrite and only mark the new tile dirty later. */ + tile += TileOffsByDir((Direction)(Random() % DIR_END)); + + /* Cacti don't spread */ + if (!CanPlantTreesOnTile(tile, false)) return; + + /* Don't plant trees, if ground was freshly cleared */ + if (IsTileType(tile, MP_CLEAR) && GetClearGround(tile) == CLEAR_GRASS && GetClearDensity(tile) != 3) return; + + PlantTreesOnTile(tile, treetype, 0, TreeGrowthStage::Growing1); } MarkTileDirtyByTile(tile); diff --git a/src/tree_map.h b/src/tree_map.h index 25a2e71a0b..5c222a93ae 100644 --- a/src/tree_map.h +++ b/src/tree_map.h @@ -21,6 +21,8 @@ * offsets from the grfs files. These points to the start of * the tree list for a landscape. See the TREE_COUNT_* enumerations * for the amount of different trees for a specific landscape. + * TREE_RANDOM_* are special values used in the map array that signify that + * exact tree type is not stored and should be determined procedurally. */ enum TreeType { TREE_TEMPERATE = 0x00, ///< temperate tree @@ -29,7 +31,14 @@ enum TreeType { TREE_CACTUS = 0x1B, ///< a cactus for the 'desert part' on a sub-tropical map TREE_SUB_TROPICAL = 0x1C, ///< tree on a sub-tropical map, non-rainforest, non-desert TREE_TOYLAND = 0x20, ///< tree on a toyland map + TREE_RANDOM_TEMPERATE = 0xFA, ///< procedural TREE_TEMPERATE + TREE_RANDOM_BEGIN = TREE_RANDOM_TEMPERATE, + TREE_RANDOM_ARCTIC = 0xFB, ///< procedural TREE_SUB_ARCTIC + TREE_RANDOM_TROPIC_NORMAL = 0xFC, ///< procedural tropic tree + TREE_RANDOM_TROPIC_RAINFOREST = 0xFD, ///< procedural TREE_RAINFOREST + TREE_RANDOM_TOYLAND = 0xFE, ///< procedural TREE_TOYLAND TREE_INVALID = 0xFF, ///< An invalid tree + TREE_RANDOM_END = TREE_INVALID, }; /* Counts the number of tree types for each landscape. @@ -70,6 +79,7 @@ enum class TreeGrowthStage : uint { Dying1 = 4, ///< First stage of dying Dying2 = 5, ///< Second stage of dying Dead = 6, ///< Dead tree + Procedural = 7, ///< Magic value that signifies that tree growth stage is determined procedurally, not stored in the map array. }; /** @@ -183,6 +193,20 @@ inline void AddTreeCount(Tile t, int c) t.m5() += c << 6; } +/** + * Returns the tree growth stage. + * Sets the tree cycle parity (for procedural growth). Uses the same bits as tree counter. + * + * @param t The tile to set the value on + * @param c The value to set + * @pre Tile must be of type MP_TREES + */ +static inline void SetTreeCycle(Tile t, int c) +{ + assert(IsTileType(t, MP_TREES)); + SB(t.m5(), 6, 2, c); +} + /** * Returns the tree growth stage. *