From 3b4cbd33235d6706baccec0073ef0a5c817d2002 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Tue, 15 Jun 2021 18:52:42 +0200 Subject: [PATCH] Improve tree placement Prior to this change, trees tended to either cover the entire map like an ancient forest, or alternatively you turn off their growth which breaks industry. Furthermore there are these ugly random tree clumps at the beginning of the game which look like squares on the map someone placed there. This change adds a new tree placing setting which removes the ugly random clumps and only slightly modifies the initial placement. The actual growth causes trees to bunch up in higher levels as usual but on the lower 4 levels their growth works differently. The number of trees per tile is limited and the trees spread out over a wider area instead of only to the neighboring tile. That spreads them out more and makes for a nicer look. This also allows cacti to spread, since they can now use that same algorithm and avoid bunching up, but spread as they should. --- src/lang/english.txt | 1 + src/table/settings.ini | 4 +- src/tree_cmd.cpp | 126 ++++++++++++++++++++++++++++++++++------- 3 files changed, 107 insertions(+), 24 deletions(-) diff --git a/src/lang/english.txt b/src/lang/english.txt index f8aa4723db..eba78574a4 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1543,6 +1543,7 @@ STR_CONFIG_SETTING_TREE_PLACER_HELPTEXT :Choose the dist STR_CONFIG_SETTING_TREE_PLACER_NONE :None STR_CONFIG_SETTING_TREE_PLACER_ORIGINAL :Original STR_CONFIG_SETTING_TREE_PLACER_IMPROVED :Improved +STR_CONFIG_SETTING_TREE_PLACER_PERFECT :Perfect STR_CONFIG_SETTING_ROAD_SIDE :Road vehicles: {STRING2} STR_CONFIG_SETTING_ROAD_SIDE_HELPTEXT :Choose the driving side STR_CONFIG_SETTING_HEIGHTMAP_ROTATION :Heightmap rotation: {STRING2} diff --git a/src/table/settings.ini b/src/table/settings.ini index 5f2e9741d2..f1c3aec765 100644 --- a/src/table/settings.ini +++ b/src/table/settings.ini @@ -3803,9 +3803,9 @@ var = game_creation.tree_placer type = SLE_UINT8 from = SLV_30 guiflags = SGF_MULTISTRING | SGF_NEWGAME_ONLY | SGF_SCENEDIT_TOO -def = 2 +def = 3 min = 0 -max = 2 +max = 3 str = STR_CONFIG_SETTING_TREE_PLACER strhelp = STR_CONFIG_SETTING_TREE_PLACER_HELPTEXT strval = STR_CONFIG_SETTING_TREE_PLACER_NONE diff --git a/src/tree_cmd.cpp b/src/tree_cmd.cpp index 74cfdc1aea..7ae01c5dac 100644 --- a/src/tree_cmd.cpp +++ b/src/tree_cmd.cpp @@ -39,6 +39,7 @@ enum TreePlacer { TP_NONE, ///< No tree placer algorithm TP_ORIGINAL, ///< The original algorithm TP_IMPROVED, ///< A 'improved' algorithm + TP_PERFECT, ///< A 'best' algorithm }; /** Where to place trees while in-game? */ @@ -305,6 +306,13 @@ static void PlaceTreeAtSameHeight(TileIndex tile, int height) } } +int MaxTreeCount(const TileIndex tile) +{ + const auto tile_z = GetTileZ(tile); + + return (tile_z == 1) ? 1 : tile_z + (_settings_game.game_creation.landscape != LT_TROPIC ? 0 : 1); +} + /** * Place some trees randomly * @@ -324,14 +332,19 @@ void PlaceTreesRandomly() if (CanPlantTreesOnTile(tile, true)) { PlaceTree(tile, r); - if (_settings_game.game_creation.tree_placer != TP_IMPROVED) continue; + if (_settings_game.game_creation.tree_placer != TP_IMPROVED && + _settings_game.game_creation.tree_placer != TP_PERFECT) continue; /* Place a number of trees based on the tile height. * This gives a cool effect of multiple trees close together. * It is almost real life ;) */ ht = GetTileZ(tile); /* The higher we get, the more trees we plant */ - j = GetTileZ(tile) * 2; + if (_settings_game.game_creation.tree_placer == TP_PERFECT) { + j = MaxTreeCount(tile); + } else { + j = GetTileZ(tile) * 2; + } /* Above snowline more trees! */ if (_settings_game.game_creation.landscape == LT_ARCTIC && ht > GetSnowLine()) j *= 3; while (j--) { @@ -436,7 +449,8 @@ void GenerateTrees() switch (_settings_game.game_creation.tree_placer) { case TP_ORIGINAL: i = _settings_game.game_creation.landscape == LT_ARCTIC ? 15 : 6; break; - case TP_IMPROVED: i = _settings_game.game_creation.landscape == LT_ARCTIC ? 4 : 2; break; + case TP_IMPROVED: + case TP_PERFECT: i = _settings_game.game_creation.landscape == LT_ARCTIC ? 4 : 2; break; default: NOT_REACHED(); } @@ -444,10 +458,16 @@ void GenerateTrees() if (_settings_game.game_creation.landscape == LT_TROPIC) total += ScaleByMapSize(DEFAULT_RAINFOREST_TREE_STEPS); total *= i; uint num_groups = (_settings_game.game_creation.landscape != LT_TOYLAND) ? ScaleByMapSize(GB(Random(), 0, 5) + 25) : 0; - total += num_groups * DEFAULT_TREE_STEPS; + + if (_settings_game.game_creation.tree_placer != TP_PERFECT) { + total += num_groups * DEFAULT_TREE_STEPS; + } + SetGeneratingWorldProgress(GWP_TREE, total); - if (num_groups != 0) PlaceTreeGroups(num_groups); + if (_settings_game.game_creation.tree_placer != TP_PERFECT) { + if (num_groups != 0) PlaceTreeGroups(num_groups); + } for (; i != 0; i--) { PlaceTreesRandomly(); @@ -479,11 +499,24 @@ CommandCost CmdPlantTree(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 TileArea ta(tile, p2); TILE_AREA_LOOP(tile, ta) { switch (GetTileType(tile)) { - case MP_TREES: + case MP_TREES: { + bool grow_existing_tree_instead = false; + /* no more space for trees? */ - if (GetTreeCount(tile) == 4) { - msg = STR_ERROR_TREE_ALREADY_HERE; - continue; + if (_settings_game.game_creation.tree_placer == TP_PERFECT) { + if (GetTreeCount(tile) >= 4 || ((int)GetTreeCount(tile) >= MaxTreeCount(tile))) { + if (GetTreeGrowth(tile) < 3) { + grow_existing_tree_instead = true; + } else { + msg = STR_ERROR_TREE_ALREADY_HERE; + continue; + } + } + } else { + if (GetTreeCount(tile) == 4) { + msg = STR_ERROR_TREE_ALREADY_HERE; + continue; + } } /* Test tree limit. */ @@ -493,23 +526,28 @@ CommandCost CmdPlantTree(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 } if (flags & DC_EXEC) { - AddTreeCount(tile, 1); + if (grow_existing_tree_instead) { + SetTreeGrowth(tile, 3); + } else { + AddTreeCount(tile, 1); + } MarkTileDirtyByTile(tile, VMDF_NOT_MAP_MODE); if (c != nullptr) c->tree_limit -= 1 << 16; } /* 2x as expensive to add more trees to an existing tile */ cost.AddCost(_price[PR_BUILD_TREES] * 2); break; + } case MP_WATER: - if (!IsCoast(tile) || IsSlopeWithOneCornerRaised(GetTileSlope(tile))) { + if (!CanPlantTreesOnTile(tile, false) || !IsCoast(tile) || IsSlopeWithOneCornerRaised(GetTileSlope(tile))) { msg = STR_ERROR_CAN_T_BUILD_ON_WATER; continue; } FALLTHROUGH; case MP_CLEAR: { - if (IsBridgeAbove(tile)) { + if (!CanPlantTreesOnTile(tile, false) || IsBridgeAbove(tile)) { msg = STR_ERROR_SITE_UNSUITABLE; continue; } @@ -853,29 +891,73 @@ static void TileLoop_Trees(TileIndex tile) AddTreeGrowth(tile, 1); break; - case 1: // add a tree - if (GetTreeCount(tile) < 4 && CanPlantExtraTrees(tile)) { + case 1: { // add a tree + if (_settings_game.game_creation.tree_placer == TP_PERFECT) { + if ((GetTreeCount(tile) < 4) && (GetTreeType(tile) != TREE_CACTUS) && ((int)GetTreeCount(tile) < MaxTreeCount(tile))) { + AddTreeCount(tile, 1); + SetTreeGrowth(tile, 0); + break; + } + } else if (GetTreeCount(tile) < 4 && CanPlantExtraTrees(tile)) { AddTreeCount(tile, 1); SetTreeGrowth(tile, 0); break; } - FALLTHROUGH; + } + FALLTHROUGH; case 2: { // add a neighbouring tree if (!CanPlantExtraTrees(tile)) break; - TreeType treetype = GetTreeType(tile); + if (_settings_game.game_creation.tree_placer == TP_PERFECT && + ((_settings_game.game_creation.landscape != LT_TROPIC && GetTileZ(tile) < 3) || (GetTreeType(tile) == TREE_CACTUS))) { + // On lower levels we spread more randomly to not bunch up. + const uint32 r = Random(); + int x = 0; + int y = 0; + TileIndex cur_tile = INVALID_TILE; + // Give cacti more chance to spread since they are sparse to begin with. + const int max_tries = (GetTreeType(tile) == TREE_CACTUS) ? 8 : 4; - tile += TileOffsByDir((Direction)(Random() & 7)); + // Try multiple times to spread + for (int i = 0; i < max_tries; ++i) { + x = GB(r, 0, 5) - 16; + y = GB(r, 8, 5) - 16; + cur_tile = TileAddWrap(tile, x, y); - /* Cacti don't spread */ - if (!CanPlantTreesOnTile(tile, false)) return; + if ((cur_tile != INVALID_TILE) && + (abs(x) + abs(y) <= 16) && + (CanPlantTreesOnTile(cur_tile, true)) && + (Delta(GetTileZ(cur_tile), GetTileZ(tile)) <= 2)) { + break; + } + } - /* Don't plant trees, if ground was freshly cleared */ - if (IsTileType(tile, MP_CLEAR) && GetClearGround(tile) == CLEAR_GRASS && GetClearDensity(tile) != 3) return; + if (cur_tile == INVALID_TILE) return; - PlantTreesOnTile(tile, treetype, 0, 0); + // Keep in range of the existing tree + if (abs(x) + abs(y) > 16) return; + // Clear tile, no farm-tiles or rocks + if (!CanPlantTreesOnTile(cur_tile, true)) return; + + // Not too much height difference + if (Delta(GetTileZ(cur_tile), GetTileZ(tile)) > 2) return; + + // Place one tree and quit + PlantTreesOnTile(cur_tile, GetTreeType(tile), 0, 0); + } else { + const TreeType tree_type = GetTreeType(tile); + + tile += TileOffsByDir((Direction)(Random() & 7)); + + 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, tree_type, 0, 0); + } break; }