@@ -6125,6 +6125,7 @@ static const Action5Type _action5_types[] = {
/* 0x16 */ { A5BLOCK_ALLOW_OFFSET, SPR_AIRPORT_PREVIEW_BASE, 1, SPR_AIRPORT_PREVIEW_COUNT, "Airport preview graphics" },
/* 0x17 */ { A5BLOCK_ALLOW_OFFSET, SPR_RAILTYPE_TUNNEL_BASE, 1, RAILTYPE_TUNNEL_BASE_COUNT, "Railtype tunnel base" },
/* 0x18 */ { A5BLOCK_ALLOW_OFFSET, SPR_PALETTE_BASE, 1, PALETTE_SPRITE_COUNT, "Palette" },
/* 0x19 */ { A5BLOCK_FIXED, SPR_FLAT_WATER_DEPTH_BASE, 1, FLAT_WATER_DEPTH_SPRITE_COUNT, "Water tiles with depth" },
};

/* Action 0x05 */
@@ -6134,7 +6135,12 @@ static void GraphicsNew(ByteReader *buf)
*
* B graphics-type What set of graphics the sprites define.
* E num-sprites How many sprites are in this set?
* V other data Graphics type specific data. Currently unused. */
* V other data Graphics type specific data.
* type & 0x80: (high bit set in type)
* E Offset of sprite to begin replacing at.
* type==0x19:
* B*16 Water depth sprite offset map.
*/

uint8 type = buf->ReadByte();
uint16 num = buf->ReadExtendedByte();
@@ -6200,6 +6206,26 @@ static void GraphicsNew(ByteReader *buf)
if (offset <= depot_no_track_offset && offset + num > depot_no_track_offset) _loaded_newgrf_features.tram = TRAMWAY_REPLACE_DEPOT_NO_TRACK;
}

if (type == 0x19) {
/* Read table of depth-sprite maping */
if (!buf->HasData(FLAT_WATER_DEPTH_SPRITE_COUNT)) {
grfmsg(1, "GraphicsNew: %s (type 0x%02X) requires a %d byte table following the sprite count for depth sprite map. Skipping.", action5_type->name, type, WATER_DEPTH_MAX);
}
SpriteID water_tiles[FLAT_WATER_DEPTH_SPRITE_COUNT];
for (int i = 0; i < lengthof(water_tiles); i++) {
byte b = buf->ReadByte();
if (b == 0xFF) {
water_tiles[i] = SPR_FLAT_WATER_TILE;
} else if (b < num) {
water_tiles[i] = SPR_FLAT_WATER_DEPTH_BASE + b;
} else {
grfmsg(1, "GraphicsNew: %s (type 0x%02X) depth mapping table index %d has an invalid sprite offset (%d). Using default sprite.", action5_type->name, type, i, b);
water_tiles[i] = SPR_FLAT_WATER_TILE;
}
}
SetWaterDepthSprites(water_tiles);
}

for (; num > 0; num--) {
_cur.nfo_line++;
LoadNextSprite(replace == 0 ? _cur.spriteid++ : replace++, _cur.file_index, _cur.nfo_line, _cur.grf_container_ver);
@@ -8614,6 +8640,9 @@ void ResetNewGRFData()
/* Reset canal sprite groups and flags */
memset(_water_feature, 0, sizeof(_water_feature));

/* Reset the water depth sprite mapping. */
ClearWaterDepthSprites();

/* Reset the snowline table. */
ClearSnowLine();

@@ -100,6 +100,9 @@ struct CanalResolverObject : public ResolverObject {

/* Random data for river or canal tiles, otherwise zero */
case 0x83: return IsTileType(this->tile, MP_WATER) ? GetWaterTileRandomBits(this->tile) : 0;

/* Water depth range 0 to 15 */
case 0x84: return IsTileType(this->tile, MP_WATER) ? GetWaterDepth(this->tile) : 0;
}

DEBUG(grf, 1, "Unhandled canal variable 0x%02X", variable);
@@ -497,6 +497,8 @@ CommandCost GetErrorMessageFromLocationCallbackResult(uint16 cb_res, const GRFFi
case 0x406: res = CommandCost(STR_ERROR_CAN_T_BUILD_ON_SEA); break;
case 0x407: res = CommandCost(STR_ERROR_CAN_T_BUILD_ON_CANAL); break;
case 0x408: res = CommandCost(STR_ERROR_CAN_T_BUILD_ON_RIVER); break;
case 0x409: res = CommandCost(STR_ERROR_WATER_TOO_DEEP); break;
case 0x40A: res = CommandCost(STR_ERROR_WATER_TOO_SHALLOW); break;
}
}

@@ -866,6 +866,7 @@ static uint32 VehicleGetVariable(Vehicle *v, const VehicleScopeResolver *object,
Ship *s = Ship::From(v);
switch (variable - 0x80) {
case 0x62: return s->state;
case 0x66: return s->tile_depth;
}
break;
}
@@ -390,6 +390,8 @@ static uint32 GetCountAndDistanceOfClosestInstance(byte param_setID, byte layout
case 0xAA: return this->industry->counter;
case 0xAB: return GB(this->industry->counter, 8, 8);
case 0xAC: return this->industry->was_cargo_delivered;
case 0xAD: return this->industry->water_depth_min;
case 0xAE: return this->industry->water_depth_max;

case 0xB0: return Clamp(this->industry->construction_date - DAYS_TILL_ORIGINAL_BASE_YEAR, 0, 65535); // Date when built since 1920 (in days)
case 0xB3: return this->industry->construction_type; // Construction type
@@ -540,6 +542,7 @@ CommandCost CheckIfCallBackAllowsCreation(TileIndex tile, IndustryType type, siz
ind.random = initial_random_bits;
ind.founder = founder;
ind.psa = nullptr;
GetIndustryLayoutWaterDepthMinMax(indspec->layouts[layout], tile, ind.water_depth_min, ind.water_depth_max);

IndustriesResolverObject object(tile, &ind, type, seed, CBID_INDUSTRY_LOCATION, 0, creation_type);
uint16 result = object.ResolveCallback();
@@ -297,7 +297,7 @@ class CYapfCostShipT

/* Ocean/canal speed penalty. */
const ShipVehicleInfo *svi = ShipVehInfo(Yapf().GetVehicle()->engine_type);
byte speed_frac = (GetEffectiveWaterClass(n.GetTile()) == WATER_CLASS_SEA) ? svi->ocean_speed_frac : svi->canal_speed_frac;
byte speed_frac = (GetEffectiveWaterDepth(n.GetTile()) >= WATER_DEPTH_DEEP) ? svi->ocean_speed_frac : svi->canal_speed_frac;
if (speed_frac > 0) c += YAPF_TILE_LENGTH * (1 + tf->m_tiles_skipped) * speed_frac / (256 - speed_frac);

/* apply it */
@@ -3100,6 +3100,28 @@ bool AfterLoadGame()
for (Industry *ind : Industry::Iterate()) if (ind->neutral_station != nullptr) ind->neutral_station->industry = ind;
}

if (IsSavegameVersionBefore(SLV_WATER_DEPTH)) {
/* Set difficulty to "original" */
_settings_game.difficulty.water_clearing_cost_exponent = 0;
/* Make sure water tiles have an appropriate depth */
for (TileIndex t = 0; t < map_size; t++) {
if (IsTileType(t, MP_WATER)) SetWaterDepth(t, 0);
}
extern void ErodeAllWaterTiles(); // landscape.cpp
ErodeAllWaterTiles();
/* Synthesize water depth min/max on all industries */
for (Industry *ind : Industry::Iterate()) {
const IndustrySpec *spec = GetIndustrySpec(ind->type);
ind->water_depth_min = ind->water_depth_max = 0;
if (spec == nullptr || !(spec->behaviour & INDUSTRYBEH_BUILT_ONWATER)) continue;
/* This industry type builds on water, try to find nearby water tiles for synthetic depth */
TileIndex tile = INVALID_TILE;
if (CircularTileSearch(&tile, 5, [](TileIndex t, void *) { return IsWaterTile(t); }, nullptr)) {
ind->water_depth_min = ind->water_depth_max = GetWaterDepth(tile);
}
Comment on lines +3117 to +3121

This comment has been minimized.

@SamuXarick

SamuXarick Dec 30, 2020
Contributor

IsWaterTile requires valid tiles, but you pass INVALID_TILE to it, and it asserts.

I think something like this might work:

			/* This industry type builds on water, try to find nearby water tiles for synthetic depth */
			TileIndex tile = INVALID_TILE;
			TILE_AREA_LOOP(tile_cur, ind->location) {
				tile = tile_cur;
				if (CircularTileSearch(&tile, 5, [](TileIndex t, void*) { return IsWaterTile(t); }, nullptr)) {
					ind->water_depth_min = ind->water_depth_max = GetWaterDepth(tile);
				}
			}
}
}

if (IsSavegameVersionBefore(SLV_TREES_WATER_CLASS)) {
/* Update water class for trees. */
for (TileIndex t = 0; t < map_size; t++) {
@@ -63,6 +63,8 @@ static const SaveLoad _industry_desc[] = {
SLE_CONDVAR(Industry, last_cargo_accepted_at[0], SLE_INT32, SLV_70, SLV_EXTEND_INDUSTRY_CARGO_SLOTS),
SLE_CONDARR(Industry, last_cargo_accepted_at, SLE_INT32, 16, SLV_EXTEND_INDUSTRY_CARGO_SLOTS, SL_MAX_VERSION),
SLE_CONDVAR(Industry, selected_layout, SLE_UINT8, SLV_73, SL_MAX_VERSION),
SLE_CONDVAR(Industry, water_depth_min, SLE_UINT8, SLV_WATER_DEPTH, SL_MAX_VERSION),
SLE_CONDVAR(Industry, water_depth_max, SLE_UINT8, SLV_WATER_DEPTH, SL_MAX_VERSION),

SLEG_CONDARR(_old_ind_persistent_storage.storage, SLE_UINT32, 16, SLV_76, SLV_161),
SLE_CONDREF(Industry, psa, REF_STORAGE, SLV_161, SL_MAX_VERSION),
@@ -304,6 +304,8 @@ enum SaveLoadVersion : uint16 {
SLV_ENDING_YEAR, ///< 218 PR#7747 v1.10 Configurable ending year.
SLV_FIX_TOWN_ACCEPTANCE, ///< 219 PR#8157 Fix Town::cargo_accepted savegame format.

SLV_WATER_DEPTH, ///< 220 PR#7924 Water depth in map array.

SL_MAX_VERSION, ///< Highest possible saveload version
};

@@ -764,6 +764,7 @@ const SaveLoad *GetVehicleDescription(VehicleType vt)
SLE_VAR(Ship, state, SLE_UINT8),
SLE_CONDDEQUE(Ship, path, SLE_UINT8, SLV_SHIP_PATH_CACHE, SL_MAX_VERSION),
SLE_CONDVAR(Ship, rotation, SLE_UINT8, SLV_SHIP_ROTATION, SL_MAX_VERSION),
SLE_CONDVAR(Ship, tile_depth, SLE_UINT8, SLV_WATER_DEPTH, SL_MAX_VERSION),

SLE_CONDNULL(16, SLV_2, SLV_144), // old reserved space

@@ -103,6 +103,7 @@ void SQAITile_Register(Squirrel *engine)
SQAITile.DefSQStaticMethod(engine, &ScriptTile::GetMinHeight, "GetMinHeight", 2, ".i");
SQAITile.DefSQStaticMethod(engine, &ScriptTile::GetMaxHeight, "GetMaxHeight", 2, ".i");
SQAITile.DefSQStaticMethod(engine, &ScriptTile::GetCornerHeight, "GetCornerHeight", 3, ".ii");
SQAITile.DefSQStaticMethod(engine, &ScriptTile::GetWaterDepth, "GetWaterDepth", 2, ".i");
SQAITile.DefSQStaticMethod(engine, &ScriptTile::GetOwner, "GetOwner", 2, ".i");
SQAITile.DefSQStaticMethod(engine, &ScriptTile::HasTransportType, "HasTransportType", 3, ".ii");
SQAITile.DefSQStaticMethod(engine, &ScriptTile::GetCargoAcceptance, "GetCargoAcceptance", 6, ".iiiii");
@@ -19,6 +19,7 @@
*
* API additions:
* \li AIPriorityQueue
* \li AITile::GetWaterDepth
*
* \b 1.10.0
*
@@ -103,6 +103,7 @@ void SQGSTile_Register(Squirrel *engine)
SQGSTile.DefSQStaticMethod(engine, &ScriptTile::GetMinHeight, "GetMinHeight", 2, ".i");
SQGSTile.DefSQStaticMethod(engine, &ScriptTile::GetMaxHeight, "GetMaxHeight", 2, ".i");
SQGSTile.DefSQStaticMethod(engine, &ScriptTile::GetCornerHeight, "GetCornerHeight", 3, ".ii");
SQGSTile.DefSQStaticMethod(engine, &ScriptTile::GetWaterDepth, "GetWaterDepth", 2, ".i");
SQGSTile.DefSQStaticMethod(engine, &ScriptTile::GetOwner, "GetOwner", 2, ".i");
SQGSTile.DefSQStaticMethod(engine, &ScriptTile::HasTransportType, "HasTransportType", 3, ".ii");
SQGSTile.DefSQStaticMethod(engine, &ScriptTile::GetCargoAcceptance, "GetCargoAcceptance", 6, ".iiiii");
@@ -25,6 +25,7 @@
* \li GSStoryPage::MakeTileButtonReference
* \li GSStoryPage::MakeVehicleButtonReference
* \li GSPriorityQueue
* \li GSTile::GetWaterDepth
*
* \b 1.10.0
*
@@ -186,6 +186,14 @@
return (z + ::GetSlopeZInCorner(slope, (::Corner)corner));
}

int32 ScriptTile::GetWaterDepth(TileIndex tile)
{
if (!::IsValidTile(tile)) return -1;
if (!::IsWaterTile(tile)) return 0;

return ::GetWaterDepth(tile);
}

/* static */ ScriptCompany::CompanyID ScriptTile::GetOwner(TileIndex tile)
{
if (!::IsValidTile(tile)) return ScriptCompany::COMPANY_INVALID;
@@ -311,6 +311,14 @@ class ScriptTile : public ScriptObject {
*/
static int32 GetCornerHeight(TileIndex tile, Corner corner);

/**
* Get the water depth of a tile.
* @param tile The tile to check on.
* @pre ScriptMap::IsValidTile(tile).
* @return Water depth, range 0 (shallow) to 15 (deepest).
*/
static int32 GetWaterDepth(TileIndex tile);

/**
* Get the owner of the tile.
* @param tile The tile to get the owner from.
@@ -1622,6 +1622,7 @@ static SettingsContainer &GetSettingsTree()
accounting->Add(new SettingEntry("economy.infrastructure_maintenance"));
accounting->Add(new SettingEntry("difficulty.vehicle_costs"));
accounting->Add(new SettingEntry("difficulty.construction_cost"));
accounting->Add(new SettingEntry("difficulty.water_clearing_cost_exponent"));
}

SettingsPage *vehicles = main->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES));
@@ -1749,6 +1750,7 @@ static SettingsContainer &GetSettingsTree()

environment->Add(new SettingEntry("station.modified_catchment"));
environment->Add(new SettingEntry("construction.extra_tree_placement"));
environment->Add(new SettingEntry("difficulty.water_depth_erosion_speed"));
}

SettingsPage *ai = main->Add(new SettingsPage(STR_CONFIG_SETTING_AI));
@@ -67,6 +67,8 @@ struct DifficultySettings {
bool line_reverse_mode; ///< reversing at stations or not
bool disasters; ///< are disasters enabled
byte town_council_tolerance; ///< minimum required town ratings to be allowed to demolish stuff
byte water_clearing_cost_exponent; ///< how the cost of clearing water tiles grows depending on depth
byte water_depth_erosion_speed; ///< how fast water depth smoothing happens
};

/** Settings relating to viewport/smallmap scrolling. */
@@ -16,19 +16,20 @@
#include "water_map.h"

void GetShipSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type);
WaterClass GetEffectiveWaterClass(TileIndex tile);
WaterDepth GetEffectiveWaterDepth(TileIndex tile);

typedef std::deque<Trackdir> ShipPathCache;

/**
* All ships have this type.
*/
struct Ship FINAL : public SpecializedVehicle<Ship, VEH_SHIP> {
TrackBits state; ///< The "track" the ship is following.
ShipPathCache path; ///< Cached path.
Direction rotation; ///< Visible direction.
int16 rotation_x_pos; ///< NOSAVE: X Position before rotation.
int16 rotation_y_pos; ///< NOSAVE: Y Position before rotation.
TrackBits state; ///< The "track" the ship is following.
ShipPathCache path; ///< Cached path.
Direction rotation; ///< Visible direction.
WaterDepth tile_depth; ///< Cached depth of current tile.
int16 rotation_x_pos; ///< NOSAVE: X Position before rotation.
int16 rotation_y_pos; ///< NOSAVE: Y Position before rotation.

/** We don't want GCC to zero our struct! It already is zeroed and has an index! */
Ship() : SpecializedVehicleBase() {}
@@ -40,22 +40,40 @@
#include "safeguards.h"

/**
* Determine the effective #WaterClass for a ship travelling on a tile.
* Determine the effective depth of a water-like tile for a ship travelling on the tile.
* @param tile Tile of interest
* @return the waterclass to be used by the ship.
* @return A water depth for the tile, may be WATER_DEPTH_MIN if the true depth couldn't be determined.
*/
WaterClass GetEffectiveWaterClass(TileIndex tile)
{
if (HasTileWaterClass(tile)) return GetWaterClass(tile);
if (IsTileType(tile, MP_TUNNELBRIDGE)) {
assert(GetTunnelBridgeTransportType(tile) == TRANSPORT_WATER);
return WATER_CLASS_CANAL;
}
if (IsTileType(tile, MP_RAILWAY)) {
assert(GetRailGroundType(tile) == RAIL_GROUND_WATER);
return WATER_CLASS_SEA;
WaterDepth GetEffectiveWaterDepth(TileIndex tile)
{
switch (GetTileType(tile)) {
case MP_WATER:
case MP_INDUSTRY:
/* Tile types that can be queried directly */
return GetWaterDepth(tile);
case MP_TUNNELBRIDGE:
/* Aqueduct, assume it's always shallow */
assert(GetTunnelBridgeTransportType(tile) == TRANSPORT_WATER);
return WATER_DEPTH_MIN;
case MP_RAILWAY:
/* Halftile with railway on foundation, and water on lower part.
* Counts as shallow as it's next by coast. */
assert(GetRailGroundType(tile) == RAIL_GROUND_WATER);
return WATER_DEPTH_MIN;
case MP_STATION:
case MP_OBJECT:
/* Thing in the water - search for a real water tile nearby and use that */
if (CircularTileSearch(&tile, 5, [](TileIndex tile, void *) { return IsTileType(tile, MP_WATER); }, nullptr)) {
return GetWaterDepth(tile);
} else {
return WATER_DEPTH_MIN;
}
case MP_TREES:
/* This is actually a coast tile */
return WATER_DEPTH_MIN;
default:
NOT_REACHED();
}
NOT_REACHED();
}

static const uint16 _ship_sprites[] = {0x0E5D, 0x0E55, 0x0E65, 0x0E6D};
@@ -203,8 +221,11 @@ void Ship::UpdateCache()
{
const ShipVehicleInfo *svi = ShipVehInfo(this->engine_type);

/* Update current water depth */
this->tile_depth = GetEffectiveWaterDepth(this->tile);

/* Get speed fraction for the current water type. Aqueducts are always canals. */
bool is_ocean = GetEffectiveWaterClass(this->tile) == WATER_CLASS_SEA;
bool is_ocean = this->tile_depth >= WATER_DEPTH_DEEP;
uint raw_speed = GetVehicleProperty(this, PROP_SHIP_SPEED, svi->max_speed);
this->vcache.cached_max_speed = svi->ApplyWaterClassSpeedFrac(raw_speed, is_ocean);

@@ -735,10 +756,10 @@ static void ShipController(Ship *v)
v->tile = gp.new_tile;
v->state = TrackToTrackBits(track);

/* Update ship cache when the water class changes. Aqueducts are always canals. */
WaterClass old_wc = GetEffectiveWaterClass(gp.old_tile);
WaterClass new_wc = GetEffectiveWaterClass(gp.new_tile);
if (old_wc != new_wc) v->UpdateCache();
/* Update ship cache when the water depth changes. */
WaterDepth old_depth = v->tile_depth;
WaterDepth new_depth = GetEffectiveWaterDepth(gp.new_tile);
if (old_depth != new_depth) v->UpdateCache();
}

Direction new_direction = (Direction)b[2];
@@ -317,6 +317,36 @@ strhelp = STR_CONFIG_SETTING_CITY_APPROVAL_HELPTEXT
strval = STR_CITY_APPROVAL_PERMISSIVE
proc = DifficultyNoiseChange
[SDT_VAR]
base = GameSettings
var = difficulty.water_clearing_cost_exponent
type = SLE_UINT8
from = SLV_WATER_DEPTH
guiflags = SGF_MULTISTRING
def = 2
min = 0
max = 2
interval = 1
str = STR_CONFIG_SETTING_WATER_CLEARING_COST
strhelp = STR_CONFIG_SETTING_WATER_CLEARING_COST_HELPTEXT
strval = STR_WATER_CLEARING_COST_ORIGINAL
cat = SC_BASIC
[SDT_VAR]
base = GameSettings
var = difficulty.water_depth_erosion_speed
type = SLE_UINT8
from = SLV_WATER_DEPTH
guiflags = SGF_MULTISTRING
def = 2
min = 0
max = 3
interval = 1
str = STR_CONFIG_SETTING_WATER_DEPTH_EROSION_SPEED
strhelp = STR_CONFIG_SETTING_WATER_DEPTH_EROSION_SPEED_HELPTEXT
strval = STR_WATER_DEPTH_EROSION_SPEED_NONE
cat = SC_ADVANCED
[SDTG_VAR]
name = ""diff_level""
var = _old_diff_level
@@ -301,8 +301,12 @@ static const uint16 EMPTY_BOUNDING_BOX_SPRITE_COUNT = 1;
static const SpriteID SPR_PALETTE_BASE = SPR_EMPTY_BOUNDING_BOX + EMPTY_BOUNDING_BOX_SPRITE_COUNT;
static const uint16 PALETTE_SPRITE_COUNT = 1;

/* Sprites for flat water with depth */
static const SpriteID SPR_FLAT_WATER_DEPTH_BASE = SPR_PALETTE_BASE + PALETTE_SPRITE_COUNT;
static const uint16 FLAT_WATER_DEPTH_SPRITE_COUNT = 16;

/* From where can we start putting NewGRFs? */
static const SpriteID SPR_NEWGRFS_BASE = SPR_PALETTE_BASE + PALETTE_SPRITE_COUNT;
static const SpriteID SPR_NEWGRFS_BASE = SPR_FLAT_WATER_DEPTH_BASE + PALETTE_SPRITE_COUNT;

/* Manager face sprites */
static const SpriteID SPR_GRADIENT = 874; // background gradient behind manager face
@@ -693,8 +693,8 @@ static void HeightMapAdjustWaterLevel(amplitude_t water_percent, height_t h_max_
FOR_ALL_TILES_IN_HEIGHT(h) {
/* Transform height from range h_water_level..h_max into 0..h_max_new range */
*h = (height_t)(((int)h_max_new) * (*h - h_water_level) / (h_max - h_water_level)) + I2H(1);
/* Make sure all values are in the proper range (0..h_max_new) */
if (*h < 0) *h = I2H(0);
/* Make sure all values are in the proper range (-water_depth_max..h_max_new) */
*h = max<height_t>(*h, I2H(-WATER_DEPTH_MAX));
if (*h >= h_max_new) *h = h_max_new - 1;
}

@@ -1006,6 +1006,19 @@ void GenerateTerrainPerlin()
}
}

/* Make deep water */
for (int y = 0; y < _height_map.size_y; y++) {
for (int x = 0; x < _height_map.size_x; x++) {
const TileIndex t = TileXY(x, y);
if (!IsValidTile(t)) continue;
const int h = H2I(_height_map.height(x, y));
if (h < 0 && GetTileSlope(t) == SLOPE_FLAT) {
MakeSea(t);
SetWaterDepth(t, Clamp(-h, WATER_DEPTH_MIN, WATER_DEPTH_MAX));
}
}
}

IncreaseGeneratingWorldProgress(GWP_LANDSCAPE);

FreeHeightMap();
@@ -64,6 +64,11 @@ static const uint8 _flood_from_dirs[] = {
(1 << DIR_W ) | (1 << DIR_SW) | (1 << DIR_NW), // SLOPE_SEN, SLOPE_STEEP_E
};

const WaterDepth CANAL_MAX_WATER_DEPTH = 2; ///< Maximum depth canals can be built over

const int WATER_DEPTH_METRES_PER_UNIT = 20; ///< How many metres of depth one unit represents
const int WATER_DEPTH_METRES_ZERO = 10; ///< Depth in metres for water depth zero

/**
* Marks tile dirty if it is a canal or river tile.
* Called to avoid glitches when flooding tiles next to canal tile.
@@ -122,12 +127,14 @@ CommandCost CmdBuildShipDepot(TileIndex tile, DoCommandFlag flags, uint32 p1, ui
CommandCost cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_DEPOT_SHIP]);

bool add_cost = !IsWaterTile(tile);
WaterDepth depth1 = HasWaterDepth(tile) ? GetWaterDepth(tile) : WATER_DEPTH_MIN;
CommandCost ret = DoCommand(tile, 0, 0, flags | DC_AUTO, CMD_LANDSCAPE_CLEAR);
if (ret.Failed()) return ret;
if (add_cost) {
cost.AddCost(ret);
}
add_cost = !IsWaterTile(tile2);
WaterDepth depth2 = HasWaterDepth(tile2) ? GetWaterDepth(tile2) : WATER_DEPTH_MIN;
ret = DoCommand(tile2, 0, 0, flags | DC_AUTO, CMD_LANDSCAPE_CLEAR);
if (ret.Failed()) return ret;
if (add_cost) {
@@ -147,6 +154,8 @@ CommandCost CmdBuildShipDepot(TileIndex tile, DoCommandFlag flags, uint32 p1, ui

MakeShipDepot(tile, _current_company, depot->index, DEPOT_PART_NORTH, axis, wc1);
MakeShipDepot(tile2, _current_company, depot->index, DEPOT_PART_SOUTH, axis, wc2);
SetWaterDepth(tile, depth1);
SetWaterDepth(tile2, depth2);
CheckForDockingTile(tile);
CheckForDockingTile(tile2);
MarkTileDirtyByTile(tile);
@@ -251,6 +260,16 @@ void MakeWaterKeepingClass(TileIndex tile, Owner o)
default: break;
}

/* Restore the water depth from surrounding tiles */
if (wc != WATER_CLASS_INVALID) {
WaterDepth min_water_depth = WATER_DEPTH_MAX + 1;
for (Direction dir = DIR_BEGIN; dir < DIR_END; dir++) {
const TileIndex dest = tile + TileOffsByDir(dir);
if (IsValidTile(dest) && IsWaterTile(dest)) min_water_depth = min(min_water_depth, GetWaterDepth(dest));
}
if (min_water_depth <= WATER_DEPTH_MAX) SetWaterDepth(tile, min_water_depth);
}

if (wc != WATER_CLASS_INVALID) CheckForDockingTile(tile);
MarkTileDirtyByTile(tile);
}
@@ -467,6 +486,13 @@ CommandCost CmdBuildCanal(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
if (IsTileType(tile, MP_WATER) && (!IsTileOwner(tile, OWNER_WATER) || wc == WATER_CLASS_SEA)) continue;

bool water = IsWaterTile(tile);
WaterDepth depth = water ? GetWaterDepth(tile) : WATER_DEPTH_MIN;
if (depth > CANAL_MAX_WATER_DEPTH) {
/* Too deep to convert to canal, pretend the tile has to be demolished and rebuilt */
water = false;
depth = CANAL_MAX_WATER_DEPTH;
}

ret = DoCommand(tile, 0, 0, flags | DC_FORCE_CLEAR_TILE, CMD_LANDSCAPE_CLEAR);
if (ret.Failed()) return ret;

@@ -491,6 +517,7 @@ CommandCost CmdBuildCanal(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32

default:
MakeCanal(tile, _current_company, Random());
SetWaterDepth(tile, depth);
if (Company::IsValidID(_current_company)) {
Company::Get(_current_company)->infrastructure.water++;
DirtyCompanyInfrastructureWindows(_current_company);
@@ -512,13 +539,24 @@ CommandCost CmdBuildCanal(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
}
}

static int WaterClearCostMultiplier(WaterDepth depth)
{
const int real_depth = max<int>(depth, 1);
switch (_settings_game.difficulty.water_clearing_cost_exponent) {
case 0: return 1;
case 1: return real_depth;
case 2: return real_depth * real_depth;
default: NOT_REACHED();
}
}

static CommandCost ClearTile_Water(TileIndex tile, DoCommandFlag flags)
{
switch (GetWaterTileType(tile)) {
case WATER_TILE_CLEAR: {
if (flags & DC_NO_WATER) return_cmd_error(STR_ERROR_CAN_T_BUILD_ON_WATER);

Money base_cost = IsCanal(tile) ? _price[PR_CLEAR_CANAL] : _price[PR_CLEAR_WATER];
const Money base_cost = IsCanal(tile) ? _price[PR_CLEAR_CANAL] : _price[PR_CLEAR_WATER];
/* Make sure freeform edges are allowed or it's not an edge tile. */
if (!_settings_game.construction.freeform_edges && (!IsInsideMM(TileX(tile), 1, MapMaxX() - 1) ||
!IsInsideMM(TileY(tile), 1, MapMaxY() - 1))) {
@@ -535,6 +573,9 @@ static CommandCost ClearTile_Water(TileIndex tile, DoCommandFlag flags)
if (ret.Failed()) return ret;
}

/* Adjust for deep water clearing cost */
const int cost_multiplier = WaterClearCostMultiplier(GetWaterDepth(tile));

if (flags & DC_EXEC) {
if (IsCanal(tile) && Company::IsValidID(owner)) {
Company::Get(owner)->infrastructure.water--;
@@ -546,7 +587,7 @@ static CommandCost ClearTile_Water(TileIndex tile, DoCommandFlag flags)
if (remove) RemoveDockingTile(tile);
}

return CommandCost(EXPENSES_CONSTRUCTION, base_cost);
return CommandCost(EXPENSES_CONSTRUCTION, base_cost * cost_multiplier);
}

case WATER_TILE_COAST: {
@@ -556,16 +597,19 @@ static CommandCost ClearTile_Water(TileIndex tile, DoCommandFlag flags)
CommandCost ret = EnsureNoVehicleOnGround(tile);
if (ret.Failed()) return ret;

/* Adjust for deep water clearing cost */
const int cost_multiplier = WaterClearCostMultiplier(GetWaterDepth(tile));

if (flags & DC_EXEC) {
bool remove = IsDockingTile(tile);
DoClearSquare(tile);
MarkCanalsAndRiversAroundDirty(tile);
if (remove) RemoveDockingTile(tile);
}
if (IsSlopeWithOneCornerRaised(slope)) {
return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_WATER]);
return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_WATER] * cost_multiplier);
} else {
return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_ROUGH]);
return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_ROUGH] * cost_multiplier);
}
}

@@ -676,6 +720,9 @@ static void DrawWaterSprite(SpriteID base, uint offset, CanalFeature feature, Ti
if (base != SPR_FLAT_WATER_TILE) {
/* Only call offset callback if the sprite is NewGRF-provided. */
offset = GetCanalSpriteOffset(feature, tile, offset);
} else {
/* Use the regular base sprite for the depth */
base = GetWaterBaseSprite(GetWaterDepth(tile));
}
DrawGroundSprite(base + offset, PAL_NONE);
}
@@ -741,7 +788,8 @@ static void DrawWaterEdges(bool canal, uint offset, TileIndex tile)
/** Draw a plain sea water tile with no edges */
static void DrawSeaWater(TileIndex tile)
{
DrawGroundSprite(SPR_FLAT_WATER_TILE, PAL_NONE);
const WaterDepth depth = HasWaterDepth(tile) ? GetWaterDepth(tile) : 0;
DrawGroundSprite(GetWaterBaseSprite(depth), PAL_NONE);
}

/** draw a canal styled water tile with dikes around */
@@ -863,6 +911,9 @@ static void DrawRiverWater(const TileInfo *ti)
}
}

/* If the plain flat tile was selected, use a depth indicating sprite instead. */
if (image == SPR_FLAT_WATER_TILE && offset == 0) image = GetWaterBaseSprite(GetWaterDepth(ti->tile));

DrawGroundSprite(image + offset, PAL_NONE);

/* Draw river edges if available. */
@@ -901,6 +952,13 @@ static void DrawTile_Water(TileInfo *ti)
switch (GetWaterTileType(ti->tile)) {
case WATER_TILE_CLEAR:
DrawWaterClassGround(ti);
#ifdef _DEBUG
if (_cur_dpi->zoom <= ZOOM_LVL_VIEWPORT && !HaveWaterDepthSprites()) {
WaterDepth depth = GetWaterDepth(ti->tile);
SpriteID spr = SPR_ASCII_SPACE_SMALL + (depth > 9 ? depth + 'A' - 10 : depth + '0') - ' ';
DrawGroundSprite(spr, TC_GOLD | (1 << PALETTE_TEXT_RECOLOUR));
}
#endif
DrawBridgeMiddle(ti);
break;

@@ -945,14 +1003,17 @@ static Foundation GetFoundation_Water(TileIndex tile, Slope tileh)
static void GetTileDesc_Water(TileIndex tile, TileDesc *td)
{
switch (GetWaterTileType(tile)) {
case WATER_TILE_CLEAR:
case WATER_TILE_CLEAR: {
switch (GetWaterClass(tile)) {
case WATER_CLASS_SEA: td->str = STR_LAI_WATER_DESCRIPTION_WATER; break;
case WATER_CLASS_CANAL: td->str = STR_LAI_WATER_DESCRIPTION_CANAL; break;
case WATER_CLASS_RIVER: td->str = STR_LAI_WATER_DESCRIPTION_RIVER; break;
default: NOT_REACHED();
}
const WaterDepth depth = GetWaterDepth(tile);
td->dparam[0] = (depth == 0) ? WATER_DEPTH_METRES_ZERO : depth * WATER_DEPTH_METRES_PER_UNIT;
break;
}
case WATER_TILE_COAST: td->str = STR_LAI_WATER_DESCRIPTION_COAST_OR_RIVERBANK; break;
case WATER_TILE_LOCK : td->str = STR_LAI_WATER_DESCRIPTION_LOCK; break;
case WATER_TILE_DEPOT:
@@ -1211,6 +1272,18 @@ void TileLoop_Water(TileIndex tile)
{
if (IsTileType(tile, MP_WATER)) AmbientSoundEffect(tile);

/* Only do depth erosion on rare occasions (at maximum erosion speed).
* 6 bits matched means once every 16384 ticks, or about 221 days between erosion. */
bool do_erosion = IsTileType(tile, MP_WATER) && (TileHash2Bit(TileX(tile), TileY(tile)) << 4 | GB(GetWaterTileRandomBits(tile), 0, 4)) == GB(_tick_counter, 8, 6);
/* Slower erosion is achieved by a chance roll (probability is further decreased by the effect of neighbour tiles changing slowly too) */
if (_settings_game.difficulty.water_depth_erosion_speed == 2) do_erosion = do_erosion && Chance16(1, 3);
if (_settings_game.difficulty.water_depth_erosion_speed == 1) do_erosion = do_erosion && Chance16(1, 30);
if (_settings_game.difficulty.water_depth_erosion_speed == 0) do_erosion = false;

if (do_erosion) {
if (ErodeWaterTileDepth(tile)) MarkTileDirtyByTile(tile);
}

switch (GetFloodingBehaviour(tile)) {
case FLOOD_ACTIVE:
for (Direction dir = DIR_BEGIN; dir < DIR_END; dir++) {
@@ -53,6 +53,11 @@ enum WaterClass {
/** Helper information for extract tool. */
template <> struct EnumPropsT<WaterClass> : MakeEnumPropsT<WaterClass, byte, WATER_CLASS_SEA, WATER_CLASS_INVALID, WATER_CLASS_INVALID, 2> {};

typedef uint8 WaterDepth;
const WaterDepth WATER_DEPTH_MIN = 0; ///< Smallest permitted water depth level
const WaterDepth WATER_DEPTH_DEEP = 1; ///< Smallest depth value that counts as "deep" water (not shallow)
const WaterDepth WATER_DEPTH_MAX = 15; ///< Largest permitted water depth level (4 bits)

/** Sections of the water depot. */
enum DepotPart {
DEPOT_PART_NORTH = 0, ///< Northern part of a depot.
@@ -68,6 +73,7 @@ enum LockPart {
};

bool IsPossibleDockingTile(TileIndex t);
bool ErodeWaterTileDepth(TileIndex tile);

/**
* Get the water tile type at a tile.
@@ -186,6 +192,43 @@ static inline bool IsWaterTile(TileIndex t)
return IsTileType(t, MP_WATER) && IsWater(t);
}

static inline bool HasWaterDepth(TileIndex t)
{
switch (GetTileType(t)) {
case MP_WATER:
case MP_INDUSTRY:
return true;
default:
return false;
}
}

/**
* Get the depth of water on a water tile.
* @param t Tile to query.
* @return Depth of water (range 0 to 15)
* @pre IsTileType(t, MP_WATER) || IsTileType(t, MP_INDUSTRY)
*/
static inline WaterDepth GetWaterDepth(TileIndex t)
{
extern WaterDepth GetIndustryTileWaterDepth(TileIndex tile);
switch (GetTileType(t)) {
case MP_WATER:
return GB(_m[t].m3, 0, 4);
case MP_INDUSTRY:
return GetIndustryTileWaterDepth(t);
default:
NOT_REACHED();
}
}

static inline void SetWaterDepth(TileIndex t, WaterDepth depth)
{
assert(IsTileType(t, MP_WATER));
assert(depth <= WATER_DEPTH_MAX);
SB(_m[t].m3, 0, 4, depth);
}

/**
* Is it a coast tile?
* @param t Water tile to query.
@@ -392,15 +435,16 @@ static inline void MakeShore(TileIndex t)
* @param o The owner of the water
* @param wc The class of water the tile has to be
* @param random_bits Eventual random bits to be set for this tile
* @param depth Depth of water at tile
*/
static inline void MakeWater(TileIndex t, Owner o, WaterClass wc, uint8 random_bits)
static inline void MakeWater(TileIndex t, Owner o, WaterClass wc, uint8 random_bits, WaterDepth depth)
{
SetTileType(t, MP_WATER);
SetTileOwner(t, o);
SetWaterClass(t, wc);
SetDockingTile(t, false);
_m[t].m2 = 0;
_m[t].m3 = 0;
SB(_m[t].m3, 0, 4, depth);
_m[t].m4 = random_bits;
_m[t].m5 = WBL_TYPE_NORMAL << WBL_TYPE_BEGIN;
SB(_me[t].m6, 2, 4, 0);
@@ -413,7 +457,7 @@ static inline void MakeWater(TileIndex t, Owner o, WaterClass wc, uint8 random_b
*/
static inline void MakeSea(TileIndex t)
{
MakeWater(t, OWNER_WATER, WATER_CLASS_SEA, 0);
MakeWater(t, OWNER_WATER, WATER_CLASS_SEA, 0, 0);
}

/**
@@ -423,7 +467,7 @@ static inline void MakeSea(TileIndex t)
*/
static inline void MakeRiver(TileIndex t, uint8 random_bits)
{
MakeWater(t, OWNER_WATER, WATER_CLASS_RIVER, random_bits);
MakeWater(t, OWNER_WATER, WATER_CLASS_RIVER, random_bits, 0);
}

/**
@@ -435,7 +479,7 @@ static inline void MakeRiver(TileIndex t, uint8 random_bits)
static inline void MakeCanal(TileIndex t, Owner o, uint8 random_bits)
{
assert(o != OWNER_WATER);
MakeWater(t, o, WATER_CLASS_CANAL, random_bits);
MakeWater(t, o, WATER_CLASS_CANAL, random_bits, 0);
}

/**