From e10200efa456bc7691e13ca9b72dd02ee11034c1 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Wed, 10 Sep 2025 18:41:56 +0100 Subject: [PATCH] Change: Allow bridges over locks. (#14595) The bridge must be at least 2 levels higher than the lock. --- src/lang/english.txt | 1 + src/saveload/saveload.h | 1 + src/table/water_land.h | 55 +++++++++++++++++++++++------------------ src/water_cmd.cpp | 31 ++++++++++++++++++++--- src/water_map.h | 6 +++-- 5 files changed, 65 insertions(+), 29 deletions(-) diff --git a/src/lang/english.txt b/src/lang/english.txt index e6c6a3d291..dcd008c392 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -5268,6 +5268,7 @@ STR_ERROR_BRIDGE_TOO_LOW_FOR_DOCK :{WHITE}Bridge i STR_ERROR_BRIDGE_TOO_LOW_FOR_BUOY :{WHITE}Bridge is too low for buoy STR_ERROR_BRIDGE_TOO_LOW_FOR_RAIL_WAYPOINT :{WHITE}Bridge is too low for rail waypoint STR_ERROR_BRIDGE_TOO_LOW_FOR_ROAD_WAYPOINT :{WHITE}Bridge is too low for road waypoint +STR_ERROR_BRIDGE_TOO_LOW_FOR_LOCK :{WHITE}Bridge is too low for lock # Tunnel related errors STR_ERROR_CAN_T_BUILD_TUNNEL_HERE :{WHITE}Can't build tunnel here... diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 2dfb49dc32..85cf3cf7ca 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -410,6 +410,7 @@ enum SaveLoadVersion : uint16_t { SLV_STATIONS_UNDER_BRIDGES, ///< 359 PR#14477 Allow stations under bridges. SLV_DOCKS_UNDER_BRIDGES, ///< 360 PR#14594 Allow docks under bridges. + SLV_LOCKS_UNDER_BRIDGES, ///< 361 PR#14595 Allow locks under bridges. SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/table/water_land.h b/src/table/water_land.h index 9bb843b3a1..2c32b972c6 100644 --- a/src/table/water_land.h +++ b/src/table/water_land.h @@ -55,64 +55,71 @@ static const DrawTileSpriteSpan _shipdepot_display_data[][DEPOT_PART_END] = { }, }; +static constexpr uint8_t LOCK_HEIGHT_LOWER_REAR = 6; ///< Sub-tile height of rear wall of lower part. +static constexpr uint8_t LOCK_HEIGHT_LOWER_FRONT = 10; ///< Sub-tile height of front wall of lower part. +static constexpr uint8_t LOCK_HEIGHT_MIDDLE_REAR = 6; ///< Sub-tile height of rear wall of middle part. +static constexpr uint8_t LOCK_HEIGHT_MIDDLE_FRONT = 10; ///< Sub-tile height of front wall of middle part. +static constexpr uint8_t LOCK_HEIGHT_UPPER_REAR = 6; ///< Sub-tile height of rear wall of upper part. +static constexpr uint8_t LOCK_HEIGHT_UPPER_FRONT = 6; ///< Sub-tile height of front wall of upper part. + static const DrawTileSeqStruct _lock_display_middle_ne_seq[] = { - TILE_SEQ_LINE( 0, 0, 0, 0x10, 1, 0x14, 0 + 1) - TILE_SEQ_LINE( 0, 0xF, 0, 0x10, 1, 0x14, 4 + 1) + TILE_SEQ_LINE(0, 0, 0, TILE_SIZE, 1, LOCK_HEIGHT_MIDDLE_REAR, 0 + 1) + TILE_SEQ_LINE(0, 15, 0, TILE_SIZE, 1, LOCK_HEIGHT_MIDDLE_FRONT, 4 + 1) }; static const DrawTileSeqStruct _lock_display_middle_se_seq[] = { - TILE_SEQ_LINE( 0, 0, 0, 1, 0x10, 0x14, 0) - TILE_SEQ_LINE( 0xF, 0, 0, 1, 0x10, 0x14, 4) + TILE_SEQ_LINE( 0, 0, 0, 1, TILE_SIZE, LOCK_HEIGHT_MIDDLE_REAR, 0) + TILE_SEQ_LINE(15, 0, 0, 1, TILE_SIZE, LOCK_HEIGHT_MIDDLE_FRONT, 4) }; static const DrawTileSeqStruct _lock_display_middle_sw_seq[] = { - TILE_SEQ_LINE( 0, 0, 0, 0x10, 1, 0x14, 0 + 2) - TILE_SEQ_LINE( 0, 0xF, 0, 0x10, 1, 0x14, 4 + 2) + TILE_SEQ_LINE(0, 0, 0, TILE_SIZE, 1, LOCK_HEIGHT_MIDDLE_REAR, 0 + 2) + TILE_SEQ_LINE(0, 15, 0, TILE_SIZE, 1, LOCK_HEIGHT_MIDDLE_FRONT, 4 + 2) }; static const DrawTileSeqStruct _lock_display_middle_nw_seq[] = { - TILE_SEQ_LINE( 0, 0, 0, 1, 0x10, 0x14, 0 + 3) - TILE_SEQ_LINE( 0xF, 0, 0, 1, 0x10, 0x14, 4 + 3) + TILE_SEQ_LINE( 0, 0, 0, 1, TILE_SIZE, LOCK_HEIGHT_MIDDLE_REAR, 0 + 3) + TILE_SEQ_LINE(15, 0, 0, 1, TILE_SIZE, LOCK_HEIGHT_MIDDLE_FRONT, 4 + 3) }; static const DrawTileSeqStruct _lock_display_lower_ne_seq[] = { - TILE_SEQ_LINE( 0, 0, 0, 0x10, 1, 0x14, 8 + 1) - TILE_SEQ_LINE( 0, 0xF, 0, 0x10, 1, 0x14, 12 + 1) + TILE_SEQ_LINE(0, 0, 0, TILE_SIZE, 1, LOCK_HEIGHT_LOWER_REAR, 8 + 1) + TILE_SEQ_LINE(0, 15, 0, TILE_SIZE, 1, LOCK_HEIGHT_LOWER_FRONT, 12 + 1) }; static const DrawTileSeqStruct _lock_display_lower_se_seq[] = { - TILE_SEQ_LINE( 0, 0, 0, 0x1, 0x10, 0x14, 8) - TILE_SEQ_LINE( 0xF, 0, 0, 0x1, 0x10, 0x14, 12) + TILE_SEQ_LINE( 0, 0, 0, 1, TILE_SIZE, LOCK_HEIGHT_LOWER_REAR, 8) + TILE_SEQ_LINE(15, 0, 0, 1, TILE_SIZE, LOCK_HEIGHT_LOWER_FRONT, 12) }; static const DrawTileSeqStruct _lock_display_lower_sw_seq[] = { - TILE_SEQ_LINE( 0, 0, 0, 0x10, 1, 0x14, 8 + 2) - TILE_SEQ_LINE( 0, 0xF, 0, 0x10, 1, 0x14, 12 + 2) + TILE_SEQ_LINE(0, 0, 0, TILE_SIZE, 1, LOCK_HEIGHT_LOWER_REAR, 8 + 2) + TILE_SEQ_LINE(0, 15, 0, TILE_SIZE, 1, LOCK_HEIGHT_LOWER_FRONT, 12 + 2) }; static const DrawTileSeqStruct _lock_display_lower_nw_seq[] = { - TILE_SEQ_LINE( 0, 0, 0, 1, 0x10, 0x14, 8 + 3) - TILE_SEQ_LINE( 0xF, 0, 0, 1, 0x10, 0x14, 12 + 3) + TILE_SEQ_LINE( 0, 0, 0, 1, TILE_SIZE, LOCK_HEIGHT_LOWER_REAR, 8 + 3) + TILE_SEQ_LINE(15, 0, 0, 1, TILE_SIZE, LOCK_HEIGHT_LOWER_FRONT, 12 + 3) }; static const DrawTileSeqStruct _lock_display_upper_ne_seq[] = { - TILE_SEQ_LINE( 0, 0, 0, 0x10, 1, 0x14, 16 + 1) - TILE_SEQ_LINE( 0, 0xF, 0, 0x10, 1, 0x14, 20 + 1) + TILE_SEQ_LINE(0, 0, 0, TILE_SIZE, 1, LOCK_HEIGHT_UPPER_REAR, 16 + 1) + TILE_SEQ_LINE(0, 15, 0, TILE_SIZE, 1, LOCK_HEIGHT_UPPER_FRONT, 20 + 1) }; static const DrawTileSeqStruct _lock_display_upper_se_seq[] = { - TILE_SEQ_LINE( 0, 0, 0, 0x1, 0x10, 0x14, 16) - TILE_SEQ_LINE( 0xF, 0, 0, 0x1, 0x10, 0x14, 20) + TILE_SEQ_LINE( 0, 0, 0, 1, TILE_SIZE, LOCK_HEIGHT_UPPER_REAR, 16) + TILE_SEQ_LINE(15, 0, 0, 1, TILE_SIZE, LOCK_HEIGHT_UPPER_FRONT, 20) }; static const DrawTileSeqStruct _lock_display_upper_sw_seq[] = { - TILE_SEQ_LINE( 0, 0, 0, 0x10, 1, 0x14, 16 + 2) - TILE_SEQ_LINE( 0, 0xF, 0, 0x10, 1, 0x14, 20 + 2) + TILE_SEQ_LINE(0, 0, 0, TILE_SIZE, 1, LOCK_HEIGHT_UPPER_REAR, 16 + 2) + TILE_SEQ_LINE(0, 15, 0, TILE_SIZE, 1, LOCK_HEIGHT_UPPER_FRONT, 20 + 2) }; static const DrawTileSeqStruct _lock_display_upper_nw_seq[] = { - TILE_SEQ_LINE( 0, 0, 0, 1, 0x10, 0x14, 16 + 3) - TILE_SEQ_LINE( 0xF, 0, 0, 1, 0x10, 0x14, 20 + 3) + TILE_SEQ_LINE( 0, 0, 0, 1, TILE_SIZE, LOCK_HEIGHT_UPPER_REAR, 16 + 3) + TILE_SEQ_LINE(15, 0, 0, 1, TILE_SIZE, LOCK_HEIGHT_UPPER_FRONT, 20 + 3) }; static const DrawTileSpriteSpan _lock_display_data[][DIAGDIR_END] = { diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp index 47cdcf9b08..39a06374e1 100644 --- a/src/water_cmd.cpp +++ b/src/water_cmd.cpp @@ -301,6 +301,21 @@ static CommandCost RemoveShipDepot(TileIndex tile, DoCommandFlags flags) return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_SHIP]); } +/** + * Get the minimal height required for a bridge above a lock part. + * @param lock_part the lock part. + * @return the minimal bridge height. + */ +static uint8_t GetLockPartMinimalBridgeHeight(LockPart lock_part) +{ + static constexpr uint8_t MINIMAL_BRIDGE_HEIGHT[LOCK_PART_END] = { + 2, // LOCK_PART_MIDDLE + 3, // LOCK_PART_LOWER + 2, // LOCK_PART_UPPER + }; + return MINIMAL_BRIDGE_HEIGHT[to_underlying(lock_part)]; +} + /** * Builds a lock. * @param tile Central tile of the lock. @@ -348,8 +363,11 @@ static CommandCost DoBuildLock(TileIndex tile, DiagDirection dir, DoCommandFlags } WaterClass wc_upper = IsWaterTile(tile + delta) ? GetWaterClass(tile + delta) : WATER_CLASS_CANAL; - if (IsBridgeAbove(tile) || IsBridgeAbove(tile - delta) || IsBridgeAbove(tile + delta)) { - return CommandCost(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); + for (LockPart lock_part = LOCK_PART_MIDDLE; TileIndex t : {tile, tile - delta, tile + delta}) { + if (IsBridgeAbove(t) && GetBridgeHeight(GetSouthernBridgeEnd(t)) < GetTileMaxZ(t) + GetLockPartMinimalBridgeHeight(lock_part)) { + return CommandCost(STR_ERROR_BRIDGE_TOO_LOW_FOR_LOCK); + } + ++lock_part; } if (flags.Test(DoCommandFlag::Execute)) { @@ -939,6 +957,9 @@ static void DrawTile_Water(TileInfo *ti) case WATER_TILE_LOCK: DrawWaterLock(ti); + DrawBridgeMiddle(ti, DiagDirToAxis(GetLockDirection(ti->tile)) == AXIS_X + ? BridgePillarFlags{BridgePillarFlag::EdgeNE, BridgePillarFlag::EdgeSW} + : BridgePillarFlags{BridgePillarFlag::EdgeNW, BridgePillarFlag::EdgeSE}); break; case WATER_TILE_DEPOT: @@ -1412,9 +1433,13 @@ static CommandCost TerraformTile_Water(TileIndex tile, DoCommandFlags flags, int return Command::Do(flags, tile); } -static CommandCost CheckBuildAbove_Water(TileIndex tile, DoCommandFlags flags, Axis, int) +static CommandCost CheckBuildAbove_Water(TileIndex tile, DoCommandFlags flags, Axis, int height) { if (IsWater(tile) || IsCoast(tile)) return CommandCost(); + if (IsLock(tile)) { + if (GetTileMaxZ(tile) + GetLockPartMinimalBridgeHeight(GetLockPart(tile)) <= height) return CommandCost(); + return CommandCost(STR_ERROR_BRIDGE_TOO_LOW_FOR_LOCK); + } return Command::Do(flags, tile); } diff --git a/src/water_map.h b/src/water_map.h index b3a7a335f3..2c424d44b1 100644 --- a/src/water_map.h +++ b/src/water_map.h @@ -66,7 +66,9 @@ enum LockPart : uint8_t { LOCK_PART_MIDDLE = 0, ///< Middle part of a lock. LOCK_PART_LOWER = 1, ///< Lower part of a lock. LOCK_PART_UPPER = 2, ///< Upper part of a lock. + LOCK_PART_END, }; +DECLARE_INCREMENT_DECREMENT_OPERATORS(LockPart); bool IsPossibleDockingTile(Tile t); @@ -323,10 +325,10 @@ inline DiagDirection GetLockDirection(Tile t) * @return The part. * @pre IsTileType(t, MP_WATER) && IsLock(t) */ -inline uint8_t GetLockPart(Tile t) +inline LockPart GetLockPart(Tile t) { assert(IsLock(t)); - return GB(t.m5(), WBL_LOCK_PART_BEGIN, WBL_LOCK_PART_COUNT); + return static_cast(GB(t.m5(), WBL_LOCK_PART_BEGIN, WBL_LOCK_PART_COUNT)); } /**