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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user