1
0
mirror of https://github.com/OpenTTD/OpenTTD synced 2025-12-10 06:52:05 +01:00

Feature: Rivers can end in wetlands if unable to reach sea (#14784)

This commit is contained in:
Tyler Trahan
2025-11-18 07:38:55 -05:00
committed by GitHub
parent a8b000f982
commit 509cbedf0b
4 changed files with 127 additions and 46 deletions

View File

@@ -29,6 +29,7 @@
#include "animated_tile_func.h"
#include "core/random_func.hpp"
#include "object_base.h"
#include "tree_cmd.h"
#include "company_func.h"
#include "company_gui.h"
#include "saveload/saveload.h"
@@ -1045,24 +1046,117 @@ static bool FindSpring(TileIndex tile)
}
/**
* Make a connected lake; fill all tiles in the circular tile search that are connected.
* @param tile The tile to consider for lake making.
* Is this a valid tile for the water feature at the end of a river?
* @param tile The tile to check.
* @param height The height of the rest of the water feature, which must match.
* @return True iff this is a valid tile to be part of the river terminus.
*/
static bool IsValidRiverTerminusTile(TileIndex tile, uint height)
{
if (!IsValidTile(tile) || TileHeight(tile) != height || !IsTileFlat(tile)) return false;
if (_settings_game.game_creation.landscape == LandscapeType::Tropic && GetTropicZone(tile) == TROPICZONE_DESERT) return false;
return true;
}
/**
* Make a lake centred on the given tile, of a random diameter.
* @param lake_centre The middle tile of the lake.
* @param height_lake The height of the lake.
*/
static void MakeLake(TileIndex tile, uint height_lake)
static void MakeLake(TileIndex lake_centre, uint height_lake)
{
if (!IsValidTile(tile) || TileHeight(tile) != height_lake || !IsTileFlat(tile)) return;
if (_settings_game.game_creation.landscape == LandscapeType::Tropic && GetTropicZone(tile) == TROPICZONE_DESERT) return;
MakeRiverAndModifyDesertZoneAround(lake_centre);
uint diameter = RandomRange(8) + 3;
for (DiagDirection d = DIAGDIR_BEGIN; d < DIAGDIR_END; d++) {
TileIndex t = tile + TileOffsByDiagDir(d);
if (IsWaterTile(t)) {
MakeRiverAndModifyDesertZoneAround(tile);
return;
/* Run the loop twice, so artefacts from going circular in one direction get (mostly) hidden. */
for (uint loops = 0; loops < 2; ++loops) {
for (TileIndex tile : SpiralTileSequence(lake_centre, diameter)) {
if (!IsValidRiverTerminusTile(tile, height_lake)) continue;
for (DiagDirection d = DIAGDIR_BEGIN; d < DIAGDIR_END; d++) {
TileIndex t = tile + TileOffsByDiagDir(d);
if (IsWaterTile(t)) {
MakeRiverAndModifyDesertZoneAround(tile);
return;
}
}
}
}
}
/**
* Make wetlands around the given tile.
* @param centre The starting tile.
* @param height The height of the wetlands.
* @param river_length The length of the river.
*/
static void MakeWetlands(TileIndex centre, uint height, uint river_length)
{
MakeRiverAndModifyDesertZoneAround(centre);
uint diameter = std::max((river_length), 16u);
/* Some wetlands have trees planted among the water tiles. */
bool has_trees = Chance16(1, 2);
/* Create the main wetland area. */
for (TileIndex tile : SpiralTileSequence(centre, diameter)) {
if (!IsValidRiverTerminusTile(tile, height)) continue;
/* Don't make a perfect square, but a circle with a noisy border. */
uint radius = diameter / 2;
if ((DistanceSquare(tile, centre) > radius * radius) && Chance16(3, 4)) continue;
if (Chance16(1, 3)) {
/* This tile is water. */
MakeRiverAndModifyDesertZoneAround(tile);
} else if (IsTileType(tile, MP_CLEAR)) {
/* This tile is ground, which we always make rough. */
SetClearGroundDensity(tile, CLEAR_ROUGH, 3);
/* Maybe place trees? */
if (has_trees && _settings_game.game_creation.tree_placer != TP_NONE) {
PlaceTree(tile, Random(), true);
}
}
}
}
/**
* Try to end a river at a tile which is not the sea.
* @param tile The tile to try ending the river at.
* @param begin The starting tile of the river.
* @return Whether we succesfully ended the river on the given tile.
*/
static bool TryMakeRiverTerminus(TileIndex tile, TileIndex begin)
{
if (!IsValidTile(tile)) return false;
/* We don't want to end the river at the entry of the valley. */
if (tile == begin) return false;
/* We don't want the river to end in the desert. */
if (_settings_game.game_creation.landscape == LandscapeType::Tropic && GetTropicZone(tile) == TROPICZONE_DESERT) return false;
/* Only end on flat slopes. */
int height_lake;
if (!IsTileFlat(tile, &height_lake)) return false;
/* Only build at the height of the river. */
int height_begin = TileHeight(begin);
if (height_lake != height_begin) return false;
/* Checks successful, time to build.
* Chance of water feature is split evenly between a lake, a wetland with trees, and a wetland with grass. */
if (Chance16(1, 3)) {
MakeLake(tile, height_lake);
} else {
MakeWetlands(tile, height_lake, DistanceManhattan(tile, begin));
}
/* This is the new end of the river. */
return true;
}
/**
* Widen a river by expanding into adjacent tiles via circular tile search.
* @param tile The tile to try expanding the river into.
@@ -1284,31 +1378,12 @@ static std::tuple<bool, bool> FlowRiver(TileIndex spring, TileIndex begin, uint
} else if (queue.size() > 32) {
/* Maybe we can make a lake. Find the Nth of the considered tiles. */
TileIndex lake_centre = queue[RandomRange(static_cast<uint32_t>(queue.size()))];
int height_lake;
if (IsValidTile(lake_centre) &&
/* We don't want the lake at the entry of the valley. */
lake_centre != begin &&
/* We don't want lakes in the desert. */
(_settings_game.game_creation.landscape != LandscapeType::Tropic || GetTropicZone(lake_centre) != TROPICZONE_DESERT) &&
/* A river, or lake, can only be built on flat slopes. */
IsTileFlat(lake_centre, &height_lake) &&
/* We want the lake to be built at the height of the river. */
height_lake == height_begin &&
/* We only want a lake if the river is long enough. */
DistanceManhattan(spring, lake_centre) > min_river_length) {
end = lake_centre;
MakeRiverAndModifyDesertZoneAround(lake_centre);
uint diameter = RandomRange(8) + 3;
/* Run the loop twice, so artefacts from going circular in one direction get (mostly) hidden. */
for (uint loops = 0; loops < 2; ++loops) {
for (auto tile : SpiralTileSequence(lake_centre, diameter)) {
MakeLake(tile, height_lake);
}
if (DistanceManhattan(spring, lake_centre) > min_river_length) {
if (TryMakeRiverTerminus(lake_centre, begin)) {
/* If successful, this becomes the new end of the river. */
end = lake_centre;
found = true;
}
found = true;
}
}

View File

@@ -99,6 +99,17 @@ enum RightClickClose : uint8_t {
RCC_YES_EXCEPT_STICKY,
};
/**
* List of tree placer algorithm.
*
* This enumeration defines all possible tree placer algorithm in the game.
*/
enum TreePlacer : uint8_t {
TP_NONE, ///< No tree placer algorithm
TP_ORIGINAL, ///< The original algorithm
TP_IMPROVED, ///< A 'improved' algorithm
};
/** Possible values for "place_houses" setting. */
enum PlaceHouses : uint8_t {
PH_FORBIDDEN = 0,

View File

@@ -33,17 +33,6 @@
#include "safeguards.h"
/**
* List of tree placer algorithm.
*
* This enumeration defines all possible tree placer algorithm in the game.
*/
enum TreePlacer : uint8_t {
TP_NONE, ///< No tree placer algorithm
TP_ORIGINAL, ///< The original algorithm
TP_IMPROVED, ///< A 'improved' algorithm
};
/** Where to place trees while in-game? */
enum ExtraTreePlacement : uint8_t {
ETP_NO_SPREAD, ///< Grow trees on tiles that have them but don't spread to new ones
@@ -167,8 +156,9 @@ static TreeType GetRandomTreeType(TileIndex tile, uint seed)
*
* @param tile The tile to make a tree-tile from
* @param r The randomness value from a Random() value
* @param keep_density Whether to keep the existing ground density of the tile.
*/
static void PlaceTree(TileIndex tile, uint32_t r)
void PlaceTree(TileIndex tile, uint32_t r, bool keep_density)
{
TreeType tree = GetRandomTreeType(tile, GB(r, 24, 8));
@@ -176,6 +166,9 @@ static void PlaceTree(TileIndex tile, uint32_t r)
PlantTreesOnTile(tile, tree, GB(r, 22, 2), static_cast<TreeGrowthStage>(std::min<uint8_t>(GB(r, 16, 3), 6)));
MarkTileDirtyByTile(tile);
/* Maybe keep the existing ground density.*/
if (keep_density) return;
/* Rerandomize ground, if neither snow nor shore */
TreeGround ground = GetTreeGround(tile);
if (ground != TREE_GROUND_SNOW_DESERT && ground != TREE_GROUND_ROUGH_SNOW && ground != TREE_GROUND_SHORE) {

View File

@@ -12,6 +12,8 @@
#include "command_type.h"
void PlaceTree(TileIndex tile, uint32_t r, bool keep_density = false);
CommandCost CmdPlantTree(DoCommandFlags flags, TileIndex tile, TileIndex start_tile, uint8_t tree_to_plant, bool diagonal);
DEF_CMD_TRAIT(CMD_PLANT_TREE, CmdPlantTree, CommandFlag::Auto, CommandType::LandscapeConstruction)