1
0
mirror of https://github.com/OpenTTD/OpenTTD synced 2026-01-28 14:44:28 +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;
}
}