diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e6dd49ffb8..5f8d9eb9df 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -91,6 +91,7 @@ add_files( bridge_gui.cpp bridge_map.cpp bridge_map.h + bridge_type.h build_vehicle_gui.cpp cachecheck.cpp cargo_type.h diff --git a/src/bridge.h b/src/bridge.h index c9e7200698..366d792a6b 100644 --- a/src/bridge.h +++ b/src/bridge.h @@ -13,29 +13,16 @@ #include "gfx_type.h" #include "tile_cmd.h" #include "timer/timer_game_calendar.h" - -/** - * This enum is related to the definition of bridge pieces, - * which is used to determine the proper sprite table to use - * while drawing a given bridge part. - */ -enum BridgePieces : uint8_t { - BRIDGE_PIECE_NORTH = 0, - BRIDGE_PIECE_SOUTH, - BRIDGE_PIECE_INNER_NORTH, - BRIDGE_PIECE_INNER_SOUTH, - BRIDGE_PIECE_MIDDLE_ODD, - BRIDGE_PIECE_MIDDLE_EVEN, - BRIDGE_PIECE_HEAD, - NUM_BRIDGE_PIECES, -}; - -DECLARE_INCREMENT_DECREMENT_OPERATORS(BridgePieces) +#include "bridge_type.h" static const uint MAX_BRIDGES = 13; ///< Maximal number of available bridge specs. constexpr uint SPRITES_PER_BRIDGE_PIECE = 32; ///< Number of sprites there are per bridge piece. -typedef uint BridgeType; ///< Bridge spec number. +enum class BridgeSpecCtrlFlag : uint8_t{ + CustomPillarFlags, + InvalidPillarFlags, +}; +using BridgeSpecCtrlFlags = EnumBitSet; /** * Struct containing information about a single bridge type @@ -52,6 +39,8 @@ struct BridgeSpec { StringID transport_name[2]; ///< description of the bridge, when built for road or rail std::vector> sprite_table; ///< table of sprites for drawing the bridge uint8_t flags; ///< bit 0 set: disable drawing of far pillars. + BridgeSpecCtrlFlags ctrl_flags{}; ///< control flags + std::array, NUM_BRIDGE_PIECES - 1> pillar_flags{}; ///< bridge pillar flags, 6 for each axis. }; extern BridgeSpec _bridge[MAX_BRIDGES]; @@ -70,11 +59,13 @@ inline const BridgeSpec *GetBridgeSpec(BridgeType i) return &_bridge[i]; } -void DrawBridgeMiddle(const TileInfo *ti); +void DrawBridgeMiddle(const TileInfo *ti, BridgePillarFlags blocked_pillars); CommandCost CheckBridgeAvailability(BridgeType bridge_type, uint bridge_len, DoCommandFlags flags = {}); int CalcBridgeLenCostFactor(int x); +BridgePillarFlags GetBridgeTilePillarFlags(TileIndex tile, TileIndex rampnorth, TileIndex rampsouth, BridgeType type, TransportType transport_type); + void ResetBridges(); #endif /* BRIDGE_H */ diff --git a/src/bridge_gui.cpp b/src/bridge_gui.cpp index f77337014e..5bf4601767 100644 --- a/src/bridge_gui.cpp +++ b/src/bridge_gui.cpp @@ -383,9 +383,9 @@ void ShowBuildBridgeWindow(TileIndex start, TileIndex end, TransportType transpo CommandCost ret = Command::Do(CommandFlagsToDCFlags(GetCommandFlags()) | DoCommandFlag::QueryCost, end, start, transport_type, 0, road_rail_type); GUIBridgeList bl; - if (ret.Failed()) { - errmsg = ret.GetErrorMessage(); - } else { + if (ret.Failed()) errmsg = ret.GetErrorMessage(); + bool query_per_bridge_type = errmsg == STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION; + if (ret.Succeeded() || query_per_bridge_type) { /* check which bridges can be built */ const uint tot_bridgedata_len = CalcBridgeLenCostFactor(bridge_len + 2); @@ -415,11 +415,13 @@ void ShowBuildBridgeWindow(TileIndex start, TileIndex end, TransportType transpo } bool any_available = false; + StringID type_errmsg = INVALID_STRING_ID; CommandCost type_check; /* loop for all bridgetypes */ for (BridgeType brd_type = 0; brd_type != MAX_BRIDGES; brd_type++) { type_check = CheckBridgeAvailability(brd_type, bridge_len); if (type_check.Succeeded()) { + if (query_per_bridge_type && Command::Do(CommandFlagsToDCFlags(GetCommandFlags()) | DoCommandFlag::QueryCost, end, start, transport_type, brd_type, road_rail_type).Failed()) continue; /* bridge is accepted, add to list */ BuildBridgeData &item = bl.emplace_back(); item.index = brd_type; @@ -428,13 +430,12 @@ void ShowBuildBridgeWindow(TileIndex start, TileIndex end, TransportType transpo * bridge itself (not computed with DoCommandFlag::QueryCost) */ item.cost = ret.GetCost() + (((int64_t)tot_bridgedata_len * _price[PR_BUILD_BRIDGE] * item.spec->price) >> 8) + infra_cost; any_available = true; + } else if (type_check.GetErrorMessage() != INVALID_STRING_ID && !query_per_bridge_type) { + type_errmsg = type_check.GetErrorMessage(); } } /* give error cause if no bridges available here*/ - if (!any_available) - { - errmsg = type_check.GetErrorMessage(); - } + if (!any_available && type_errmsg != INVALID_STRING_ID) errmsg = type_errmsg; } if (!bl.empty()) { diff --git a/src/bridge_type.h b/src/bridge_type.h new file mode 100644 index 0000000000..4ccdb19173 --- /dev/null +++ b/src/bridge_type.h @@ -0,0 +1,52 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file bridge_type.h Header file for bridge types. */ + +#ifndef BRIDGE_TYPE_H +#define BRIDGE_TYPE_H + +#include "core/enum_type.hpp" + +using BridgeType = uint; ///< Bridge spec number. + +/** + * This enum is related to the definition of bridge pieces, + * which is used to determine the proper sprite table to use + * while drawing a given bridge part. + */ +enum BridgePieces : uint8_t { + BRIDGE_PIECE_NORTH = 0, + BRIDGE_PIECE_SOUTH, + BRIDGE_PIECE_INNER_NORTH, + BRIDGE_PIECE_INNER_SOUTH, + BRIDGE_PIECE_MIDDLE_ODD, + BRIDGE_PIECE_MIDDLE_EVEN, + BRIDGE_PIECE_HEAD, + NUM_BRIDGE_PIECES, +}; + +DECLARE_INCREMENT_DECREMENT_OPERATORS(BridgePieces) + +enum class BridgePillarFlag : uint8_t { + CornerW = 0, + CornerS = 1, + CornerE = 2, + CornerN = 3, + EdgeNE = 4, + EdgeSE = 5, + EdgeSW = 6, + EdgeNW = 7, +}; +using BridgePillarFlags = EnumBitSet; + +static constexpr BridgePillarFlags BRIDGEPILLARFLAGS_ALL{ + BridgePillarFlag::CornerW, BridgePillarFlag::CornerS, BridgePillarFlag::CornerE, BridgePillarFlag::CornerN, + BridgePillarFlag::EdgeNE, BridgePillarFlag::EdgeSE, BridgePillarFlag::EdgeSW, BridgePillarFlag::EdgeNW, +}; + +#endif /* BRIDGE_TYPE_H */ diff --git a/src/clear_cmd.cpp b/src/clear_cmd.cpp index cb817bf0a3..0019d8bcdb 100644 --- a/src/clear_cmd.cpp +++ b/src/clear_cmd.cpp @@ -152,7 +152,7 @@ static void DrawTile_Clear(TileInfo *ti) break; } - DrawBridgeMiddle(ti); + DrawBridgeMiddle(ti, {}); } static int GetSlopePixelZ_Clear(TileIndex tile, uint x, uint y, bool) @@ -390,6 +390,11 @@ static CommandCost TerraformTile_Clear(TileIndex tile, DoCommandFlags flags, int return Command::Do(flags, tile); } +static CommandCost CheckBuildAbove_Clear(TileIndex, DoCommandFlags, Axis, int) +{ + return CommandCost(); +} + extern const TileTypeProcs _tile_type_clear_procs = { DrawTile_Clear, ///< draw_tile_proc GetSlopePixelZ_Clear, ///< get_slope_z_proc @@ -405,4 +410,5 @@ extern const TileTypeProcs _tile_type_clear_procs = { nullptr, ///< vehicle_enter_tile_proc GetFoundation_Clear, ///< get_foundation_proc TerraformTile_Clear, ///< terraform_tile_proc + CheckBuildAbove_Clear, // check_build_above_proc }; diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp index 19a9976124..9cdec5c2c4 100644 --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -3205,6 +3205,7 @@ extern const TileTypeProcs _tile_type_industry_procs = { nullptr, // vehicle_enter_tile_proc GetFoundation_Industry, // get_foundation_proc TerraformTile_Industry, // terraform_tile_proc + nullptr, // check_build_above_proc }; bool IndustryCompare::operator() (const IndustryListEntry &lhs, const IndustryListEntry &rhs) const diff --git a/src/lang/english.txt b/src/lang/english.txt index 41531939f3..7b8ada50e5 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -5244,6 +5244,7 @@ STR_ERROR_START_AND_END_MUST_BE_IN :{WHITE}Start an STR_ERROR_ENDS_OF_BRIDGE_MUST_BOTH :{WHITE}... ends of bridge must both be on land STR_ERROR_BRIDGE_TOO_LONG :{WHITE}... bridge too long STR_ERROR_BRIDGE_THROUGH_MAP_BORDER :{WHITE}Bridge would end out of the map +STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION :{WHITE}Bridge is too low for station # Tunnel related errors STR_ERROR_CAN_T_BUILD_TUNNEL_HERE :{WHITE}Can't build tunnel here... diff --git a/src/newgrf/newgrf_act0_bridges.cpp b/src/newgrf/newgrf_act0_bridges.cpp index 949da3937d..48d41f8812 100644 --- a/src/newgrf/newgrf_act0_bridges.cpp +++ b/src/newgrf/newgrf_act0_bridges.cpp @@ -93,6 +93,7 @@ static ChangeInfoResult BridgeChangeInfo(uint first, uint last, int prop, ByteRe MapSpriteMappingRecolour(&bridge->sprite_table[tableid][sprite]); } } + if (!bridge->ctrl_flags.Test(BridgeSpecCtrlFlag::CustomPillarFlags)) bridge->ctrl_flags.Set(BridgeSpecCtrlFlag::InvalidPillarFlags); break; } @@ -120,6 +121,20 @@ static ChangeInfoResult BridgeChangeInfo(uint first, uint last, int prop, ByteRe bridge->price = buf.ReadWord(); break; + case 0x14: + buf.ReadWord(); + buf.ReadWord(); + break; + + case 0x15: // Pillars + for (uint j = 0; j != 6; ++j) { + bridge->pillar_flags[j][AXIS_X] = BridgePillarFlags{buf.ReadByte()}; + bridge->pillar_flags[j][AXIS_Y] = BridgePillarFlags{buf.ReadByte()}; + } + bridge->ctrl_flags.Reset(BridgeSpecCtrlFlag::InvalidPillarFlags); + bridge->ctrl_flags.Set(BridgeSpecCtrlFlag::CustomPillarFlags); + break; + default: ret = CIR_UNKNOWN; break; diff --git a/src/newgrf/newgrf_act0_roadstops.cpp b/src/newgrf/newgrf_act0_roadstops.cpp index 0fc897b565..d13d74b379 100644 --- a/src/newgrf/newgrf_act0_roadstops.cpp +++ b/src/newgrf/newgrf_act0_roadstops.cpp @@ -49,6 +49,13 @@ static ChangeInfoResult IgnoreRoadStopProperty(uint prop, ByteReader &buf) buf.ReadDWord(); break; + case 0x13: + case 0x14: + buf.ReadWord(); + buf.ReadWord(); + buf.ReadWord(); + break; + case 0x16: // Badge list SkipBadgeList(buf); break; @@ -134,6 +141,18 @@ static ChangeInfoResult RoadStopChangeInfo(uint first, uint last, int prop, Byte rs->flags = static_cast(buf.ReadDWord()); // Future-proofing, size this as 4 bytes, but we only need two byte's worth of flags at present break; + case 0x13: // Bridge height. + for (uint j = 0; j != 6; ++j) { + rs->tilespecs[j].height = buf.ReadByte(); + } + break; + + case 0x14: // Disallow pillars. + for (uint j = 0; j != 6; ++j) { + rs->tilespecs[j].disallowed_pillars = BridgePillarFlags{buf.ReadByte()}; + } + break; + case 0x15: // Cost multipliers rs->build_cost_multiplier = buf.ReadByte(); rs->clear_cost_multiplier = buf.ReadByte(); diff --git a/src/newgrf/newgrf_act0_stations.cpp b/src/newgrf/newgrf_act0_stations.cpp index d5336665ea..a06d4c50b1 100644 --- a/src/newgrf/newgrf_act0_stations.cpp +++ b/src/newgrf/newgrf_act0_stations.cpp @@ -184,12 +184,12 @@ static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteR case 0x11: { // Pylon placement uint8_t pylons = buf.ReadByte(); - if (statspec->tileflags.size() < 8) statspec->tileflags.resize(8); + if (statspec->tilespecs.size() < 8) statspec->tilespecs.resize(8); for (int j = 0; j < 8; ++j) { if (HasBit(pylons, j)) { - statspec->tileflags[j].Set(StationSpec::TileFlag::Pylons); + statspec->tilespecs[j].flags.Set(StationSpec::TileFlag::Pylons); } else { - statspec->tileflags[j].Reset(StationSpec::TileFlag::Pylons); + statspec->tilespecs[j].flags.Reset(StationSpec::TileFlag::Pylons); } } break; @@ -209,12 +209,12 @@ static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteR case 0x14: { // Overhead wire placement uint8_t wires = buf.ReadByte(); - if (statspec->tileflags.size() < 8) statspec->tileflags.resize(8); + if (statspec->tilespecs.size() < 8) statspec->tilespecs.resize(8); for (int j = 0; j < 8; ++j) { if (HasBit(wires, j)) { - statspec->tileflags[j].Set(StationSpec::TileFlag::NoWires); + statspec->tilespecs[j].flags.Set(StationSpec::TileFlag::NoWires); } else { - statspec->tileflags[j].Reset(StationSpec::TileFlag::NoWires); + statspec->tilespecs[j].flags.Reset(StationSpec::TileFlag::NoWires); } } break; @@ -222,12 +222,12 @@ static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteR case 0x15: { // Blocked tiles uint8_t blocked = buf.ReadByte(); - if (statspec->tileflags.size() < 8) statspec->tileflags.resize(8); + if (statspec->tilespecs.size() < 8) statspec->tilespecs.resize(8); for (int j = 0; j < 8; ++j) { if (HasBit(blocked, j)) { - statspec->tileflags[j].Set(StationSpec::TileFlag::Blocked); + statspec->tilespecs[j].flags.Set(StationSpec::TileFlag::Blocked); } else { - statspec->tileflags[j].Reset(StationSpec::TileFlag::Blocked); + statspec->tilespecs[j].flags.Reset(StationSpec::TileFlag::Blocked); } } break; @@ -268,11 +268,11 @@ static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteR break; } - case 0x1B: // Minimum bridge height (not implemented) - buf.ReadWord(); - buf.ReadWord(); - buf.ReadWord(); - buf.ReadWord(); + case 0x1B: // Minimum bridge height (old variable) + if (statspec->tilespecs.size() < 8) statspec->tilespecs.resize(8); + for (int j = 0; j < 8; ++j) { + statspec->tilespecs[j].height = buf.ReadByte(); + } break; case 0x1C: // Station Name @@ -285,8 +285,10 @@ static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteR case 0x1E: { // Extended tile flags (replaces prop 11, 14 and 15) uint16_t tiles = buf.ReadExtendedByte(); - auto flags = reinterpret_cast(buf.ReadBytes(tiles)); - statspec->tileflags.assign(flags, flags + tiles); + if (statspec->tilespecs.size() < tiles) statspec->tilespecs.resize(tiles); + for (uint j = 0; j != tiles; ++j) { + statspec->tilespecs[j].flags = StationSpec::TileFlags{buf.ReadByte()}; + } break; } @@ -294,6 +296,24 @@ static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteR statspec->badges = ReadBadgeList(buf, GSF_STATIONS); break; + case 0x20: { // Minimum bridge height (extended) + uint16_t tiles = buf.ReadExtendedByte(); + if (statspec->tilespecs.size() < tiles) statspec->tilespecs.resize(tiles); + for (int j = 0; j != tiles; ++j) { + statspec->tilespecs[j].height = buf.ReadByte(); + } + break; + } + + case 0x21: { // Disallowed bridge pillars + uint16_t tiles = buf.ReadExtendedByte(); + if (statspec->tilespecs.size() < tiles) statspec->tilespecs.resize(tiles); + for (int j = 0; j != tiles; ++j) { + statspec->tilespecs[j].disallowed_pillars = BridgePillarFlags{buf.ReadByte()}; + } + break; + } + default: ret = CIR_UNKNOWN; break; diff --git a/src/newgrf_roadstop.h b/src/newgrf_roadstop.h index 690c9adadb..3c4908ed8b 100644 --- a/src/newgrf_roadstop.h +++ b/src/newgrf_roadstop.h @@ -12,6 +12,7 @@ #ifndef NEWGRF_ROADSTATION_H #define NEWGRF_ROADSTATION_H +#include "bridge_type.h" #include "newgrf_animation_type.h" #include "newgrf_spritegroup.h" #include "newgrf_badge_type.h" @@ -138,12 +139,17 @@ struct RoadStopSpec : NewGRFSpecBase { AnimationInfo animation; - uint8_t bridge_height[6]; ///< Minimum height for a bridge above, 0 for none - uint8_t bridge_disallowed_pillars[6]; ///< Disallowed pillar flags for a bridge above + struct TileSpec { + uint8_t height = 0; ///< Minimum height for a bridge above, 0 for none + BridgePillarFlags disallowed_pillars = BRIDGEPILLARFLAGS_ALL; ///< Disallowed pillar flags for a bridge above + }; + std::array tilespecs{}; ///< Per tile information. uint8_t build_cost_multiplier = 16; ///< Build cost multiplier per tile. uint8_t clear_cost_multiplier = 16; ///< Clear cost multiplier per tile. + uint8_t height; ///< The height of this structure, in heightlevels; max MAX_TILE_HEIGHT. + std::vector badges; /** diff --git a/src/newgrf_station.h b/src/newgrf_station.h index 1b74f90e80..d99e0a8faf 100644 --- a/src/newgrf_station.h +++ b/src/newgrf_station.h @@ -10,6 +10,7 @@ #ifndef NEWGRF_STATION_H #define NEWGRF_STATION_H +#include "bridge_type.h" #include "core/enum_type.hpp" #include "newgrf_animation_type.h" #include "newgrf_badge_type.h" @@ -168,7 +169,13 @@ struct StationSpec : NewGRFSpecBase { Blocked = 2, ///< Tile is blocked to vehicles. }; using TileFlags = EnumBitSet; - std::vector tileflags; ///< List of tile flags. + + struct TileSpec { + TileFlags flags{}; ///< Tile flags. + uint8_t height = 0; ///< Minimum height for a bridge above, 0 for none + BridgePillarFlags disallowed_pillars = BRIDGEPILLARFLAGS_ALL; ///< Disallowed pillar flags for a bridge above + }; + std::vector tilespecs; ///< Per-layout-tile information. AnimationInfo animation; diff --git a/src/object_cmd.cpp b/src/object_cmd.cpp index 15693c90f0..b17b30c9c3 100644 --- a/src/object_cmd.cpp +++ b/src/object_cmd.cpp @@ -483,7 +483,7 @@ static void DrawTile_Object(TileInfo *ti) DrawNewObjectTile(ti, spec); } - DrawBridgeMiddle(ti); + DrawBridgeMiddle(ti, {}); } static int GetSlopePixelZ_Object(TileIndex tile, uint x, uint y, bool) @@ -929,6 +929,16 @@ static CommandCost TerraformTile_Object(TileIndex tile, DoCommandFlags flags, in return Command::Do(flags, tile); } +static CommandCost CheckBuildAbove_Object(TileIndex tile, DoCommandFlags flags, Axis, int height) +{ + const ObjectSpec *spec = ObjectSpec::GetByTile(tile); + if (spec->flags.Test(ObjectFlag::AllowUnderBridge) && GetTileMaxZ(tile) + spec->height <= height) { + return CommandCost(); + } + + return Command::Do(flags, tile); +} + extern const TileTypeProcs _tile_type_object_procs = { DrawTile_Object, // draw_tile_proc GetSlopePixelZ_Object, // get_slope_z_proc @@ -944,4 +954,5 @@ extern const TileTypeProcs _tile_type_object_procs = { nullptr, // vehicle_enter_tile_proc GetFoundation_Object, // get_foundation_proc TerraformTile_Object, // terraform_tile_proc + CheckBuildAbove_Object, // check_build_above_proc }; diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index 0762099ea4..a577161d80 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -2525,7 +2525,7 @@ static void DrawTile_Track(TileInfo *ti) DrawRailTileSeq(ti, dts, TO_BUILDINGS, relocation, 0, _drawtile_track_palette); } - DrawBridgeMiddle(ti); + DrawBridgeMiddle(ti, {}); } void DrawTrainDepotSprite(int x, int y, int dir, RailType railtype) @@ -3070,6 +3070,11 @@ static CommandCost TerraformTile_Track(TileIndex tile, DoCommandFlags flags, int return Command::Do(flags, tile); } +static CommandCost CheckBuildAbove_Track(TileIndex tile, DoCommandFlags flags, Axis, int) +{ + if (IsPlainRail(tile)) return CommandCost(); + return Command::Do(flags, tile); +} extern const TileTypeProcs _tile_type_rail_procs = { DrawTile_Track, // draw_tile_proc @@ -3086,4 +3091,5 @@ extern const TileTypeProcs _tile_type_rail_procs = { VehicleEnter_Track, // vehicle_enter_tile_proc GetFoundation_Track, // get_foundation_proc TerraformTile_Track, // terraform_tile_proc + CheckBuildAbove_Track, // check_build_above_proc }; diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index 49c2e9f69d..5b91b56893 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -1858,7 +1858,7 @@ static void DrawTile_Road(TileInfo *ti) break; } } - DrawBridgeMiddle(ti); + DrawBridgeMiddle(ti, {}); } /** @@ -2616,6 +2616,11 @@ CommandCost CmdConvertRoad(DoCommandFlags flags, TileIndex tile, TileIndex area_ return found_convertible_road ? cost : error; } +static CommandCost CheckBuildAbove_Road(TileIndex tile, DoCommandFlags flags, Axis, int) +{ + if (!IsRoadDepot(tile)) return CommandCost(); + return Command::Do(flags, tile); +} /** Tile callback functions for road tiles */ extern const TileTypeProcs _tile_type_road_procs = { @@ -2633,4 +2638,5 @@ extern const TileTypeProcs _tile_type_road_procs = { VehicleEnter_Road, // vehicle_enter_tile_proc GetFoundation_Road, // get_foundation_proc TerraformTile_Road, // terraform_tile_proc + CheckBuildAbove_Road, // check_build_above_proc }; diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 002e27fef2..91c8490ff2 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -406,6 +406,7 @@ enum SaveLoadVersion : uint16_t { SLV_FACE_STYLES, ///< 355 PR#14319 Addition of face styles, replacing gender and ethnicity. SLV_INDUSTRY_NUM_VALID_HISTORY, ///< 356 PR#14416 Store number of valid history records for industries. SLV_INDUSTRY_ACCEPTED_HISTORY, ///< 357 PR#14321 Add per-industry history of cargo delivered and waiting. + SLV_STATIONS_UNDER_BRIDGES, ///< 358 PR#14477 Allow stations under bridges. SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 1ca967f7d8..998ec299f3 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -77,6 +77,8 @@ #include "safeguards.h" +static StationSpec::TileFlags GetStationTileFlags(StationGfx gfx, const StationSpec *statspec); + /** * Static instance of FlowStat::SharesMap. * Note: This instance is created on task start. @@ -864,6 +866,29 @@ static CommandCost CheckFlatLandAirport(AirportTileTableIterator tile_iter, DoCo return cost; } +static CommandCost IsRailStationBridgeAboveOk(TileIndex tile, const StationSpec *statspec, uint8_t layout, int bridge_height) +{ + if (statspec == nullptr) { + /* Default stations/waypoints */ + int height_above = layout < 4 ? 2 : 5; + if (GetTileMaxZ(tile) + height_above > bridge_height) return CommandCost(STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION); + } else { + int height_above = layout < std::size(statspec->tilespecs) ? statspec->tilespecs[layout].height : 0; + if (height_above == 0) return CommandCost(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); + if (GetTileMaxZ(tile) + height_above > bridge_height) return CommandCost(STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION); + } + + return CommandCost(); +} + +CommandCost IsRailStationBridgeAboveOk(TileIndex tile, const StationSpec *statspec, uint8_t layout) +{ + if (!IsBridgeAbove(tile)) return CommandCost(); + + TileIndex rampsouth = GetSouthernBridgeEnd(tile); + return IsRailStationBridgeAboveOk(tile, statspec, layout, GetBridgeHeight(rampsouth)); +} + /** * Checks if a rail station can be built at the given tile. * @param tile_cur Tile to check. @@ -888,7 +913,7 @@ static CommandCost CheckFlatLandRailStation(TileIndex tile_cur, TileIndex north_ const StationSpec *statspec = StationClass::Get(spec_class)->GetSpec(spec_index); bool slope_cb = statspec != nullptr && statspec->callback_mask.Test(StationCallbackMask::SlopeCheck); - CommandCost ret = CheckBuildableTile(tile_cur, invalid_dirs, allowed_z, false); + CommandCost ret = CheckBuildableTile(tile_cur, invalid_dirs, allowed_z, false, false); if (ret.Failed()) return ret; cost.AddCost(ret.GetCost()); @@ -949,6 +974,33 @@ static CommandCost CheckFlatLandRailStation(TileIndex tile_cur, TileIndex north_ return cost; } +static CommandCost IsRoadStopBridgeAboveOk(TileIndex tile, const RoadStopSpec *spec, bool drive_through, DiagDirection entrance, int bridge_height) +{ + uint layout = drive_through ? (GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET + DiagDirToAxis(entrance)) : entrance; + + if (spec == nullptr) { + if (GetTileMaxZ(tile) + (drive_through ? 1 : 2) > bridge_height) { + return CommandCost(STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION); + } + } else { + int height = spec->tilespecs[layout].height; + if (height == 0) return CommandCost(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); + if (GetTileMaxZ(tile) + height > bridge_height) { + return CommandCost(STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION); + } + } + + return CommandCost(); +} + +static CommandCost IsRoadStopBridgeAboveOk(TileIndex tile, const RoadStopSpec *spec, bool drive_through, DiagDirection entrance) +{ + if (!IsBridgeAbove(tile)) return CommandCost(); + + TileIndex rampsouth = GetSouthernBridgeEnd(tile); + return IsRoadStopBridgeAboveOk(tile, spec, drive_through, entrance, GetBridgeHeight(rampsouth)); +} + /** * Checks if a road stop can be built at the given tile. * @param cur_tile Tile to check. @@ -962,14 +1014,17 @@ static CommandCost CheckFlatLandRailStation(TileIndex tile_cur, TileIndex north_ * @param rt Road type to build, may be INVALID_ROADTYPE if an existing road is required. * @return The cost in case of success, or an error code if it failed. */ -static CommandCost CheckFlatLandRoadStop(TileIndex cur_tile, int &allowed_z, DoCommandFlags flags, DiagDirections invalid_dirs, bool is_drive_through, StationType station_type, Axis axis, StationID *station, RoadType rt) +static CommandCost CheckFlatLandRoadStop(TileIndex cur_tile, int &allowed_z, const RoadStopSpec *spec, DoCommandFlags flags, DiagDirections invalid_dirs, bool is_drive_through, StationType station_type, Axis axis, StationID *station, RoadType rt) { CommandCost cost(EXPENSES_CONSTRUCTION); - CommandCost ret = CheckBuildableTile(cur_tile, invalid_dirs, allowed_z, !is_drive_through); + CommandCost ret = CheckBuildableTile(cur_tile, invalid_dirs, allowed_z, !is_drive_through, false); if (ret.Failed()) return ret; cost.AddCost(ret.GetCost()); + ret = IsRoadStopBridgeAboveOk(cur_tile, spec, is_drive_through, DiagDirection{FindFirstBit(invalid_dirs.base())}); + if (ret.Failed()) return ret; + /* If station is set, then we have special handling to allow building on top of already existing stations. * Station points to StationID::Invalid() if we can build on any station. * Or it points to a station if we're only allowed to build on exactly that station. */ @@ -1307,8 +1362,8 @@ static CommandCost CalculateRailStationCost(TileArea tile_area, DoCommandFlags f static StationSpec::TileFlags GetStationTileFlags(StationGfx gfx, const StationSpec *statspec) { /* Default stations do not draw pylons under roofs (gfx >= 4) */ - if (statspec == nullptr || gfx >= statspec->tileflags.size()) return gfx < 4 ? StationSpec::TileFlag::Pylons : StationSpec::TileFlags{}; - return statspec->tileflags[gfx]; + if (statspec == nullptr || gfx >= statspec->tilespecs.size()) return gfx < 4 ? StationSpec::TileFlag::Pylons : StationSpec::TileFlags{}; + return statspec->tilespecs[gfx].flags; } /** @@ -1362,6 +1417,9 @@ CommandCost CmdBuildRailStation(DoCommandFlags flags, TileIndex tile_org, RailTy w_org = numtracks; } + /* Check if the first tile and the last tile are valid */ + if (!IsValidTile(tile_org) || TileAddWrap(tile_org, w_org - 1, h_org - 1) == INVALID_TILE) return CMD_ERROR; + bool reuse = (station_to_join != NEW_STATION); if (!reuse) station_to_join = StationID::Invalid(); bool distant_join = (station_to_join != StationID::Invalid()); @@ -1392,8 +1450,29 @@ CommandCost CmdBuildRailStation(DoCommandFlags flags, TileIndex tile_org, RailTy if (ret.Failed()) return ret; } - /* Check if we can allocate a custom stationspec to this station */ const StationSpec *statspec = StationClass::Get(spec_class)->GetSpec(spec_index); + TileIndexDiff tile_delta = TileOffsByAxis(axis); // offset to go to the next platform tile + TileIndexDiff track_delta = TileOffsByAxis(OtherAxis(axis)); // offset to go to the next track + + std::vector layouts(numtracks * plat_len); + GetStationLayout(layouts.data(), numtracks, plat_len, statspec); + + { + auto it = std::begin(layouts); + TileIndex tile_track = tile_org; + for (uint i = 0; i < numtracks; i++) { + TileIndex tile = tile_track; + for (uint j = 0; j < plat_len; j++) { + uint8_t layout = ((*it++) & ~1) + axis; // Adjust layout piece to match axis. + ret = IsRailStationBridgeAboveOk(tile, statspec, layout); + if (ret.Failed()) return ret; + tile += tile_delta; + } + tile_track += track_delta; + } + } + + /* Check if we can allocate a custom stationspec to this station */ int specindex = AllocateSpecToStation(statspec, st, flags.Test(DoCommandFlag::Execute)); if (specindex == -1) return CommandCost(STR_ERROR_TOO_MANY_STATION_SPECS); @@ -1423,13 +1502,7 @@ CommandCost CmdBuildRailStation(DoCommandFlags flags, TileIndex tile_org, RailTy st->cached_anim_triggers.Set(statspec->animation.triggers); } - TileIndexDiff tile_delta = TileOffsByAxis(axis); // offset to go to the next platform tile - TileIndexDiff track_delta = TileOffsByAxis(OtherAxis(axis)); // offset to go to the next track Track track = AxisToTrack(axis); - - std::vector layouts(numtracks * plat_len); - GetStationLayout(layouts.data(), numtracks, plat_len, statspec); - uint8_t numtracks_orig = numtracks; Company *c = Company::Get(st->owner); @@ -1440,6 +1513,7 @@ CommandCost CmdBuildRailStation(DoCommandFlags flags, TileIndex tile_org, RailTy int w = plat_len; do { uint8_t layout = layouts[layout_idx++]; + if (IsRailStationTile(tile) && HasStationReservation(tile)) { /* Check for trains having a reservation for this tile. */ Train *v = GetTrainForReservation(tile, AxisToTrack(GetRailStationAxis(tile))); @@ -1913,7 +1987,7 @@ static CommandCost FindJoiningRoadStop(StationID existing_stop, StationID statio * @param unit_cost The cost to build one road stop of the current type. * @return The cost in case of success, or an error code if it failed. */ -CommandCost CalculateRoadStopCost(TileArea tile_area, DoCommandFlags flags, bool is_drive_through, StationType station_type, Axis axis, DiagDirection ddir, StationID *est, RoadType rt, Money unit_cost) +CommandCost CalculateRoadStopCost(TileArea tile_area, DoCommandFlags flags, bool is_drive_through, StationType station_type, const RoadStopSpec *roadstopspec, Axis axis, DiagDirection ddir, StationID *est, RoadType rt, Money unit_cost) { DiagDirections invalid_dirs{}; if (is_drive_through) { @@ -1927,7 +2001,7 @@ CommandCost CalculateRoadStopCost(TileArea tile_area, DoCommandFlags flags, bool int allowed_z = -1; CommandCost cost(EXPENSES_CONSTRUCTION); for (TileIndex cur_tile : tile_area) { - CommandCost ret = CheckFlatLandRoadStop(cur_tile, allowed_z, flags, invalid_dirs, is_drive_through, station_type, axis, est, rt); + CommandCost ret = CheckFlatLandRoadStop(cur_tile, allowed_z, roadstopspec, flags, invalid_dirs, is_drive_through, station_type, axis, est, rt); if (ret.Failed()) return ret; bool is_preexisting_roadstop = IsTileType(cur_tile, MP_STATION) && IsAnyRoadStop(cur_tile); @@ -2008,7 +2082,7 @@ CommandCost CmdBuildRoadStop(DoCommandFlags flags, TileIndex tile, uint8_t width unit_cost = _price[is_truck_stop ? PR_BUILD_STATION_TRUCK : PR_BUILD_STATION_BUS]; } StationID est = StationID::Invalid(); - CommandCost cost = CalculateRoadStopCost(roadstop_area, flags, is_drive_through, is_truck_stop ? StationType::Truck : StationType::Bus, axis, ddir, &est, rt, unit_cost); + CommandCost cost = CalculateRoadStopCost(roadstop_area, flags, is_drive_through, is_truck_stop ? StationType::Truck : StationType::Bus, roadstopspec, axis, ddir, &est, rt, unit_cost); if (cost.Failed()) return cost; Station *st = nullptr; @@ -3073,6 +3147,51 @@ bool SplitGroundSpriteForOverlay(const TileInfo *ti, SpriteID *ground, RailTrack return true; } +static BridgePillarFlags GetRailStationBlockedPillars(const StationSpec *statspec, uint8_t layout) +{ + static const BridgePillarFlags default_pillar_flags[] = { + {BridgePillarFlag::EdgeSW, BridgePillarFlag::EdgeNE}, // X empty + {BridgePillarFlag::EdgeNW, BridgePillarFlag::EdgeSE}, // Y empty + {BridgePillarFlag::EdgeSW, BridgePillarFlag::EdgeNE}, // X small + {BridgePillarFlag::EdgeNW, BridgePillarFlag::EdgeSE}, // Y small + {BridgePillarFlag::EdgeSW, BridgePillarFlag::EdgeNE, BridgePillarFlag::EdgeSE, BridgePillarFlag::CornerE, BridgePillarFlag::CornerS}, // X large + {BridgePillarFlag::EdgeNW, BridgePillarFlag::EdgeSE, BridgePillarFlag::EdgeSW, BridgePillarFlag::CornerS, BridgePillarFlag::CornerW}, // Y large + {BridgePillarFlag::EdgeSW, BridgePillarFlag::EdgeNE, BridgePillarFlag::EdgeNW, BridgePillarFlag::CornerN, BridgePillarFlag::CornerW}, // X large + {BridgePillarFlag::EdgeNW, BridgePillarFlag::EdgeSE, BridgePillarFlag::EdgeNE, BridgePillarFlag::CornerN, BridgePillarFlag::CornerE}, // Y large + }; + + if (statspec == nullptr) { + /* Default stations/waypoints */ + if (layout < 8) return default_pillar_flags[layout]; + return {}; + } + if (layout < std::size(statspec->tilespecs) && statspec->tilespecs[layout].disallowed_pillars != BRIDGEPILLARFLAGS_ALL) { + /* Pllar flags set by NewGRF */ + return statspec->tilespecs[layout].disallowed_pillars; + } + if (GetStationTileFlags(layout, statspec).Test(StationSpec::TileFlag::Blocked)) { + /* Blocked station tile. */ + return {}; + } + + /* Non-blocked station tile. */ + return default_pillar_flags[layout % 2]; +} + +static BridgePillarFlags GetRoadStopBlockedPillars(const RoadStopSpec *spec, bool drive_through, uint8_t layout) +{ + static constexpr BridgePillarFlags default_pillar_flags_x{BridgePillarFlag::EdgeSW, BridgePillarFlag::EdgeNE}; + static constexpr BridgePillarFlags default_pillar_flags_y{BridgePillarFlag::EdgeNW, BridgePillarFlag::EdgeSE}; + + if (spec != nullptr && spec->tilespecs[layout].disallowed_pillars != BRIDGEPILLARFLAGS_ALL) { + return spec->tilespecs[layout].disallowed_pillars; + } + if (drive_through) { + return HasBit(layout, 0) ? default_pillar_flags_y : default_pillar_flags_x; + } + return {BridgePillarFlag::EdgeNE + (DiagDirection)layout}; +} + static void DrawTile_Station(TileInfo *ti) { const NewGRFSpriteLayout *layout = nullptr; @@ -3086,6 +3205,7 @@ static void DrawTile_Station(TileInfo *ti) BaseStation *st = nullptr; const StationSpec *statspec = nullptr; uint tile_layout = 0; + BridgePillarFlags blocked_pillars = {}; if (HasStationRail(ti->tile)) { rti = GetRailTypeInfo(GetRailType(ti->tile)); @@ -3114,6 +3234,7 @@ static void DrawTile_Station(TileInfo *ti) } } } + blocked_pillars = GetRailStationBlockedPillars(statspec, GetStationGfx(ti->tile)); } else { total_offset = 0; } @@ -3358,6 +3479,7 @@ draw_default_foundation: uint sprite_offset = GetDriveThroughStopAxis(ti->tile) == AXIS_X ? 1 : 0; DrawRoadOverlays(ti, PAL_NONE, road_rti, tram_rti, sprite_offset, sprite_offset); } + blocked_pillars = GetRoadStopBlockedPillars(stopspec, true, view); } else { /* Non-drivethrough road stops are only valid for roads. */ assert(road_rt != INVALID_ROADTYPE && tram_rt == INVALID_ROADTYPE); @@ -3366,6 +3488,7 @@ draw_default_foundation: SpriteID ground = GetCustomRoadSprite(road_rti, ti->tile, ROTSG_ROADSTOP); DrawGroundSprite(ground + view, PAL_NONE); } + blocked_pillars = GetRoadStopBlockedPillars(stopspec, false, view); } if (stopspec == nullptr || !stopspec->flags.Test(RoadStopSpecFlag::NoCatenary)) { @@ -3380,6 +3503,7 @@ draw_default_foundation: } DrawRailTileSeq(ti, t, TO_BUILDINGS, total_offset, relocation, palette); + DrawBridgeMiddle(ti, blocked_pillars); } void StationPickerDrawSprite(int x, int y, StationType st, RailType railtype, RoadType roadtype, int image) @@ -5162,6 +5286,40 @@ uint FlowStatMap::GetFlowFromVia(StationID from, StationID via) const return i->second.GetShare(via); } +static CommandCost CheckBuildAbove_Station(TileIndex tile, DoCommandFlags flags, Axis, int height) +{ + switch (GetStationType(tile)) { + case StationType::Rail: + case StationType::RailWaypoint: { + CommandCost ret = IsRailStationBridgeAboveOk(tile, GetStationSpec(tile), GetStationGfx(tile), height + 1); + if (ret.Failed()) { + if (ret.GetErrorMessage() != INVALID_STRING_ID) return ret; + break; + } + return CommandCost(); + } + + case StationType::Bus: + case StationType::Truck: + case StationType::RoadWaypoint: { + CommandCost ret = IsRoadStopBridgeAboveOk(tile, GetRoadStopSpec(tile), IsDriveThroughStopTile(tile), + IsDriveThroughStopTile(tile) ? AxisToDiagDir(GetDriveThroughStopAxis(tile)) : GetBayRoadStopDir(tile), height + 1); + if (ret.Failed()) { + if (ret.GetErrorMessage() != INVALID_STRING_ID) return ret; + break; + } + return CommandCost(); + } + + case StationType::Buoy: + return CommandCost(); + + default: break; + } + + return Command::Do(flags, tile); +} + extern const TileTypeProcs _tile_type_station_procs = { DrawTile_Station, // draw_tile_proc GetSlopePixelZ_Station, // get_slope_z_proc @@ -5177,4 +5335,5 @@ extern const TileTypeProcs _tile_type_station_procs = { VehicleEnter_Station, // vehicle_enter_tile_proc GetFoundation_Station, // get_foundation_proc TerraformTile_Station, // terraform_tile_proc + CheckBuildAbove_Station, // check_build_above_proc }; diff --git a/src/table/bridge_land.h b/src/table/bridge_land.h index 7bf13bb106..6e0e4206d5 100644 --- a/src/table/bridge_land.h +++ b/src/table/bridge_land.h @@ -745,8 +745,42 @@ static const std::span> _bridge_sprite_table[ * @param nrl description of the rail bridge in query tool * @param nrd description of the road bridge in query tool */ -#define MBR(y, mnl, mxl, p, mxs, spr, plt, dsc, nrl, nrd) \ - {TimerGameCalendar::Year{y}, mnl, mxl, p, mxs, spr, plt, dsc, { nrl, nrd }, {}, 0} +#define MBR(y, mnl, mxl, p, mxs, spr, plt, dsc, nrl, nrd, pillars) \ + {TimerGameCalendar::Year{y}, mnl, mxl, p, mxs, spr, plt, dsc, { nrl, nrd }, {}, 0, {}, pillars} + +static constexpr BridgePillarFlags BRIDGEPILLARFLAGS_ALL_CORNERS = { + BridgePillarFlag::CornerW, BridgePillarFlag::CornerS, BridgePillarFlag::CornerE, BridgePillarFlag::CornerN +}; + +/** Pillar flags for bridges which have pillars on the all corners on each piece. */ +static const std::array, NUM_BRIDGE_PIECES - 1> ALL_PILLARS = {{ + {{BRIDGEPILLARFLAGS_ALL_CORNERS, BRIDGEPILLARFLAGS_ALL_CORNERS}}, + {{BRIDGEPILLARFLAGS_ALL_CORNERS, BRIDGEPILLARFLAGS_ALL_CORNERS}}, + {{BRIDGEPILLARFLAGS_ALL_CORNERS, BRIDGEPILLARFLAGS_ALL_CORNERS}}, + {{BRIDGEPILLARFLAGS_ALL_CORNERS, BRIDGEPILLARFLAGS_ALL_CORNERS}}, + {{BRIDGEPILLARFLAGS_ALL_CORNERS, BRIDGEPILLARFLAGS_ALL_CORNERS}}, + {{BRIDGEPILLARFLAGS_ALL_CORNERS, BRIDGEPILLARFLAGS_ALL_CORNERS}}, +}}; + +/** Pillar flags for suspension style bridges. */ +static const std::array, NUM_BRIDGE_PIECES - 1> SUSPENSION_PILLARS = {{ + {{{BridgePillarFlag::CornerW, BridgePillarFlag::CornerS}, {BridgePillarFlag::CornerS, BridgePillarFlag::CornerE}}}, + {{{BridgePillarFlag::CornerE, BridgePillarFlag::CornerN}, {BridgePillarFlag::CornerW, BridgePillarFlag::CornerN}}}, + {{{BridgePillarFlag::CornerE, BridgePillarFlag::CornerN}, {BridgePillarFlag::CornerW, BridgePillarFlag::CornerN}}}, + {{{BridgePillarFlag::CornerW, BridgePillarFlag::CornerS}, {BridgePillarFlag::CornerS, BridgePillarFlag::CornerE}}}, + {{BRIDGEPILLARFLAGS_ALL_CORNERS, BRIDGEPILLARFLAGS_ALL_CORNERS}}, + {{{}, {}}}, +}}; + +/** Pillar flags for cantilever style bridges. */ +static const std::array, NUM_BRIDGE_PIECES - 1> CANTILEVER_PILLARS = {{ + {{{}, {}}}, + {{{BridgePillarFlag::CornerE, BridgePillarFlag::CornerN}, {BridgePillarFlag::CornerW, BridgePillarFlag::CornerN}}}, + {{{BridgePillarFlag::CornerE, BridgePillarFlag::CornerN}, {BridgePillarFlag::CornerW, BridgePillarFlag::CornerN}}}, + {{{BridgePillarFlag::CornerE, BridgePillarFlag::CornerN}, {BridgePillarFlag::CornerW, BridgePillarFlag::CornerN}}}, + {{{BridgePillarFlag::CornerE, BridgePillarFlag::CornerN}, {BridgePillarFlag::CornerW, BridgePillarFlag::CornerN}}}, + {{{BridgePillarFlag::CornerE, BridgePillarFlag::CornerN}, {BridgePillarFlag::CornerW, BridgePillarFlag::CornerN}}}, +}}; const BridgeSpec _orig_bridge[] = { /* @@ -760,43 +794,43 @@ const BridgeSpec _orig_bridge[] = { string with description name on rail name on road | | | | */ MBR( 0, 0, 0xFFFF, 80, 32, 0xA24, PAL_NONE, - STR_BRIDGE_NAME_WOODEN, STR_LAI_BRIDGE_DESCRIPTION_RAIL_WOODEN, STR_LAI_BRIDGE_DESCRIPTION_ROAD_WOODEN), + STR_BRIDGE_NAME_WOODEN, STR_LAI_BRIDGE_DESCRIPTION_RAIL_WOODEN, STR_LAI_BRIDGE_DESCRIPTION_ROAD_WOODEN, ALL_PILLARS), MBR( 0, 0, 2, 112, 48, 0xA26, PALETTE_TO_STRUCT_RED, - STR_BRIDGE_NAME_CONCRETE, STR_LAI_BRIDGE_DESCRIPTION_RAIL_CONCRETE, STR_LAI_BRIDGE_DESCRIPTION_ROAD_CONCRETE), + STR_BRIDGE_NAME_CONCRETE, STR_LAI_BRIDGE_DESCRIPTION_RAIL_CONCRETE, STR_LAI_BRIDGE_DESCRIPTION_ROAD_CONCRETE, ALL_PILLARS), MBR(1930, 0, 5, 144, 64, 0xA25, PAL_NONE, - STR_BRIDGE_NAME_GIRDER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_GIRDER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_GIRDER_STEEL), + STR_BRIDGE_NAME_GIRDER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_GIRDER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_GIRDER_STEEL, ALL_PILLARS), MBR( 0, 2, 10, 168, 80, 0xA22, PALETTE_TO_STRUCT_CONCRETE, - STR_BRIDGE_NAME_SUSPENSION_CONCRETE, STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_CONCRETE, STR_LAI_BRIDGE_DESCRIPTION_ROAD_SUSPENSION_CONCRETE), + STR_BRIDGE_NAME_SUSPENSION_CONCRETE, STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_CONCRETE, STR_LAI_BRIDGE_DESCRIPTION_ROAD_SUSPENSION_CONCRETE, SUSPENSION_PILLARS), MBR(1930, 3, 0xFFFF, 185, 96, 0xA22, PAL_NONE, - STR_BRIDGE_NAME_SUSPENSION_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_SUSPENSION_STEEL), + STR_BRIDGE_NAME_SUSPENSION_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_SUSPENSION_STEEL, SUSPENSION_PILLARS), MBR(1930, 3, 0xFFFF, 192, 112, 0xA22, PALETTE_TO_STRUCT_YELLOW, - STR_BRIDGE_NAME_SUSPENSION_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_SUSPENSION_STEEL), + STR_BRIDGE_NAME_SUSPENSION_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_SUSPENSION_STEEL, SUSPENSION_PILLARS), MBR(1930, 3, 7, 224, 160, 0xA23, PAL_NONE, - STR_BRIDGE_NAME_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_CANTILEVER_STEEL), + STR_BRIDGE_NAME_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_CANTILEVER_STEEL, CANTILEVER_PILLARS), MBR(1930, 3, 8, 232, 208, 0xA23, PALETTE_TO_STRUCT_BROWN, - STR_BRIDGE_NAME_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_CANTILEVER_STEEL), + STR_BRIDGE_NAME_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_CANTILEVER_STEEL, CANTILEVER_PILLARS), MBR(1930, 3, 9, 248, 240, 0xA23, PALETTE_TO_STRUCT_RED, - STR_BRIDGE_NAME_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_CANTILEVER_STEEL), + STR_BRIDGE_NAME_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_CANTILEVER_STEEL, CANTILEVER_PILLARS), MBR(1930, 0, 2, 240, 256, 0xA27, PAL_NONE, - STR_BRIDGE_NAME_GIRDER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_GIRDER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_GIRDER_STEEL), + STR_BRIDGE_NAME_GIRDER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_GIRDER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_GIRDER_STEEL, ALL_PILLARS), MBR(1995, 2, 0xFFFF, 255, 320, 0xA28, PAL_NONE, - STR_BRIDGE_NAME_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_TUBULAR_STEEL), + STR_BRIDGE_NAME_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_TUBULAR_STEEL, CANTILEVER_PILLARS), MBR(2005, 2, 0xFFFF, 380, 512, 0xA28, PALETTE_TO_STRUCT_YELLOW, - STR_BRIDGE_NAME_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_TUBULAR_STEEL), + STR_BRIDGE_NAME_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_TUBULAR_STEEL, CANTILEVER_PILLARS), MBR(2010, 2, 0xFFFF, 510, 608, 0xA28, PALETTE_TO_STRUCT_CONCRETE, - STR_BRIDGE_TUBULAR_SILICON, STR_LAI_BRIDGE_DESCRIPTION_RAIL_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_TUBULAR_STEEL) + STR_BRIDGE_TUBULAR_SILICON, STR_LAI_BRIDGE_DESCRIPTION_RAIL_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_TUBULAR_STEEL, CANTILEVER_PILLARS), }; #undef MBR diff --git a/src/tile_cmd.h b/src/tile_cmd.h index fb60cec092..73db58e679 100644 --- a/src/tile_cmd.h +++ b/src/tile_cmd.h @@ -135,6 +135,8 @@ typedef Foundation GetFoundationProc(TileIndex tile, Slope tileh); */ typedef CommandCost TerraformTileProc(TileIndex tile, DoCommandFlags flags, int z_new, Slope tileh_new); +using CheckBuildAboveProc = CommandCost(TileIndex tile, DoCommandFlags flags, Axis axis, int height); + /** * Set of callback functions for performing tile operations of a given tile type. * @see TileType @@ -154,6 +156,7 @@ struct TileTypeProcs { VehicleEnterTileProc *vehicle_enter_tile_proc; ///< Called when a vehicle enters a tile GetFoundationProc *get_foundation_proc; TerraformTileProc *terraform_tile_proc; ///< Called when a terraforming operation is about to take place + CheckBuildAboveProc *check_build_above_proc; }; extern const TileTypeProcs * const _tile_type_procs[16]; diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index cf74aeb35f..f1161ca07d 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -4130,6 +4130,7 @@ extern const TileTypeProcs _tile_type_town_procs = { nullptr, // vehicle_enter_tile_proc GetFoundation_Town, // get_foundation_proc TerraformTile_Town, // terraform_tile_proc + nullptr, // check_build_above_proc }; std::span GetTownDrawTileData() diff --git a/src/tree_cmd.cpp b/src/tree_cmd.cpp index ad7203dc00..49901b8796 100644 --- a/src/tree_cmd.cpp +++ b/src/tree_cmd.cpp @@ -1031,4 +1031,5 @@ extern const TileTypeProcs _tile_type_trees_procs = { nullptr, // vehicle_enter_tile_proc GetFoundation_Trees, // get_foundation_proc TerraformTile_Trees, // terraform_tile_proc + nullptr, // check_build_above_proc }; diff --git a/src/tunnelbridge_cmd.cpp b/src/tunnelbridge_cmd.cpp index 3184393fdd..f93f89b011 100644 --- a/src/tunnelbridge_cmd.cpp +++ b/src/tunnelbridge_cmd.cpp @@ -12,7 +12,6 @@ */ #include "stdafx.h" -#include "newgrf_object.h" #include "viewport_func.h" #include "command_func.h" #include "town.h" @@ -20,7 +19,6 @@ #include "ship.h" #include "roadveh.h" #include "pathfinder/yapf/yapf_cache.h" -#include "pathfinder/water_regions.h" #include "newgrf_sound.h" #include "autoslope.h" #include "tunnelbridge_map.h" @@ -39,7 +37,6 @@ #include "object_base.h" #include "water.h" #include "company_gui.h" -#include "station_func.h" #include "tunnelbridge_cmd.h" #include "landscape_cmd.h" #include "terraform_cmd.h" @@ -278,6 +275,16 @@ static Money TunnelBridgeClearCost(TileIndex tile, Price base_price) return base_cost; } +static CommandCost CheckBuildAbove(TileIndex tile, DoCommandFlags flags, Axis axis, int height) +{ + if (_tile_type_procs[GetTileType(tile)]->check_build_above_proc != nullptr) { + return _tile_type_procs[GetTileType(tile)]->check_build_above_proc(tile, flags, axis, height); + } + + /* A tile without a handler must be cleared. */ + return Command::Do(flags, tile); +} + /** * Build a Bridge * @param flags type of operation @@ -434,6 +441,14 @@ CommandCost CmdBuildBridge(DoCommandFlags flags, TileIndex tile_end, TileIndex t /* If bridge belonged to bankrupt company, it has a new owner now */ is_new_owner = (owner == OWNER_NONE); if (is_new_owner) owner = company; + + /* Check if the new bridge is compatible with tiles underneath. */ + TileIndexDiff delta = (direction == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); + for (TileIndex tile = tile_start + delta; tile != tile_end; tile += delta) { + CommandCost ret = CheckBuildAbove(tile, flags, direction, z_start); + if (ret.Failed()) return ret; + cost.AddCost(ret.GetCost()); + } } else { /* Build a new bridge. */ @@ -484,43 +499,9 @@ CommandCost CmdBuildBridge(DoCommandFlags flags, TileIndex tile_end, TileIndex t return CommandCost(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); } - switch (GetTileType(tile)) { - case MP_WATER: - if (!IsWater(tile) && !IsCoast(tile)) goto not_valid_below; - break; - - case MP_RAILWAY: - if (!IsPlainRail(tile)) goto not_valid_below; - break; - - case MP_ROAD: - if (IsRoadDepot(tile)) goto not_valid_below; - break; - - case MP_TUNNELBRIDGE: - if (IsTunnel(tile)) break; - if (direction == DiagDirToAxis(GetTunnelBridgeDirection(tile))) goto not_valid_below; - if (z_start < GetBridgeHeight(tile)) goto not_valid_below; - break; - - case MP_OBJECT: { - const ObjectSpec *spec = ObjectSpec::GetByTile(tile); - if (!spec->flags.Test(ObjectFlag::AllowUnderBridge)) goto not_valid_below; - if (GetTileMaxZ(tile) + spec->height > z_start) goto not_valid_below; - break; - } - - case MP_CLEAR: - break; - - default: - not_valid_below:; - /* try and clear the middle landscape */ - ret = Command::Do(flags, tile); - if (ret.Failed()) return ret; - cost.AddCost(ret.GetCost()); - break; - } + ret = CheckBuildAbove(tile, flags, direction, z_start); + if (ret.Failed()) return ret; + cost.AddCost(ret.GetCost()); if (flags.Test(DoCommandFlag::Execute)) { /* We do this here because when replacing a bridge with another @@ -1445,7 +1426,7 @@ static void DrawTile_TunnelBridge(TileInfo *ti) AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, *ti, rear_sep[tunnelbridge_direction]); AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, *ti, front_sep[tunnelbridge_direction]); - DrawBridgeMiddle(ti); + DrawBridgeMiddle(ti, {}); } else { // IsBridge(ti->tile) DrawFoundation(ti, GetBridgeFoundation(ti->tileh, DiagDirToAxis(tunnelbridge_direction))); @@ -1536,7 +1517,7 @@ static void DrawTile_TunnelBridge(TileInfo *ti) } } - DrawBridgeMiddle(ti); + DrawBridgeMiddle(ti, {}); } } @@ -1574,11 +1555,26 @@ static BridgePieces CalcBridgePiece(uint north, uint south) } } +BridgePillarFlags GetBridgeTilePillarFlags(TileIndex tile, TileIndex rampnorth, TileIndex rampsouth, BridgeType type, TransportType transport_type) +{ + if (transport_type == TRANSPORT_WATER) return BRIDGEPILLARFLAGS_ALL_CORNERS; + + const BridgeSpec *spec = GetBridgeSpec(type); + if (!spec->ctrl_flags.Test(BridgeSpecCtrlFlag::InvalidPillarFlags)) { + BridgePieces piece = CalcBridgePiece(GetTunnelBridgeLength(tile, rampnorth) + 1, GetTunnelBridgeLength(tile, rampsouth) + 1); + Axis axis = TileX(rampnorth) == TileX(rampsouth) ? AXIS_Y : AXIS_X; + + return spec->pillar_flags[piece][axis == AXIS_Y ? 1 : 0]; + } + + return BRIDGEPILLARFLAGS_ALL_CORNERS; +} + /** * Draw the middle bits of a bridge. * @param ti Tile information of the tile to draw it on. */ -void DrawBridgeMiddle(const TileInfo *ti) +void DrawBridgeMiddle(const TileInfo *ti, BridgePillarFlags blocked_pillars) { /* Sectional view of bridge bounding boxes: * @@ -1602,6 +1598,7 @@ void DrawBridgeMiddle(const TileInfo *ti) TileIndex rampsouth = GetSouthernBridgeEnd(ti->tile); TransportType transport_type = GetTunnelBridgeTransportType(rampsouth); Axis axis = GetBridgeAxis(ti->tile); + BridgePillarFlags pillars; uint base_offset = GetBridgeMiddleAxisBaseOffset(axis); std::span psid; @@ -1611,9 +1608,11 @@ void DrawBridgeMiddle(const TileInfo *ti) drawfarpillar = !HasBit(GetBridgeSpec(bridge_type)->flags, 0); base_offset += GetBridgeSpriteTableBaseOffset(transport_type, rampsouth); psid = GetBridgeSpriteTable(bridge_type, CalcBridgePiece(GetTunnelBridgeLength(ti->tile, rampnorth) + 1, GetTunnelBridgeLength(ti->tile, rampsouth) + 1)); + pillars = GetBridgeTilePillarFlags(ti->tile, rampnorth, rampsouth, bridge_type, transport_type); } else { drawfarpillar = true; psid = _aqueduct_sprite_table_middle; + pillars = BRIDGEPILLARFLAGS_ALL_CORNERS; } psid = psid.subspan(base_offset, 3); @@ -1682,6 +1681,7 @@ void DrawBridgeMiddle(const TileInfo *ti) /* Do not draw anything more if bridges are invisible */ if (IsInvisibilitySet(TO_BRIDGES)) return; + if (blocked_pillars.Any(pillars)) return; DrawBridgePillars(psid[2], ti, axis, drawfarpillar, x, y, z); } @@ -2084,6 +2084,17 @@ static CommandCost TerraformTile_TunnelBridge(TileIndex tile, DoCommandFlags fla return Command::Do(flags, tile); } +static CommandCost CheckBuildAbove_TunnelBridge(TileIndex tile, DoCommandFlags flags, Axis axis, int height) +{ + if (IsTunnel(tile)) return CommandCost(); + + if (axis != DiagDirToAxis(GetTunnelBridgeDirection(tile)) && height >= GetBridgeHeight(tile)) { + return CommandCost(); + } + + return Command::Do(flags, tile); +} + extern const TileTypeProcs _tile_type_tunnelbridge_procs = { DrawTile_TunnelBridge, // draw_tile_proc GetSlopePixelZ_TunnelBridge, // get_slope_z_proc @@ -2099,4 +2110,5 @@ extern const TileTypeProcs _tile_type_tunnelbridge_procs = { VehicleEnter_TunnelBridge, // vehicle_enter_tile_proc GetFoundation_TunnelBridge, // get_foundation_proc TerraformTile_TunnelBridge, // terraform_tile_proc + CheckBuildAbove_TunnelBridge, // check_build_above_proc }; diff --git a/src/void_cmd.cpp b/src/void_cmd.cpp index 5334824997..2665f4d2bf 100644 --- a/src/void_cmd.cpp +++ b/src/void_cmd.cpp @@ -87,4 +87,5 @@ extern const TileTypeProcs _tile_type_void_procs = { nullptr, // vehicle_enter_tile_proc GetFoundation_Void, // get_foundation_proc TerraformTile_Void, // terraform_tile_proc + nullptr, // check_build_above_proc }; diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp index 756a1d1d38..d4b09693ee 100644 --- a/src/water_cmd.cpp +++ b/src/water_cmd.cpp @@ -926,12 +926,12 @@ static void DrawTile_Water(TileInfo *ti) switch (GetWaterTileType(ti->tile)) { case WATER_TILE_CLEAR: DrawWaterClassGround(ti); - DrawBridgeMiddle(ti); + DrawBridgeMiddle(ti, {}); break; case WATER_TILE_COAST: { DrawShoreTile(ti->tileh); - DrawBridgeMiddle(ti); + DrawBridgeMiddle(ti, {}); break; } @@ -1410,6 +1410,11 @@ static CommandCost TerraformTile_Water(TileIndex tile, DoCommandFlags flags, int return Command::Do(flags, tile); } +static CommandCost CheckBuildAbove_Water(TileIndex tile, DoCommandFlags flags, Axis, int) +{ + if (IsWater(tile) || IsCoast(tile)) return CommandCost(); + return Command::Do(flags, tile); +} extern const TileTypeProcs _tile_type_water_procs = { DrawTile_Water, // draw_tile_proc @@ -1426,4 +1431,5 @@ extern const TileTypeProcs _tile_type_water_procs = { VehicleEnter_Water, // vehicle_enter_tile_proc GetFoundation_Water, // get_foundation_proc TerraformTile_Water, // terraform_tile_proc + CheckBuildAbove_Water, // check_build_above_proc }; diff --git a/src/waypoint_cmd.cpp b/src/waypoint_cmd.cpp index 8814cfc0a7..121757b479 100644 --- a/src/waypoint_cmd.cpp +++ b/src/waypoint_cmd.cpp @@ -174,15 +174,15 @@ static CommandCost IsValidTileForWaypoint(TileIndex tile, Axis axis, StationID * return CommandCost(STR_ERROR_FLAT_LAND_REQUIRED); } - if (IsBridgeAbove(tile)) return CommandCost(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); - return CommandCost(); } extern void GetStationLayout(uint8_t *layout, uint numtracks, uint plat_len, const StationSpec *statspec); extern CommandCost FindJoiningWaypoint(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Waypoint **wp, bool is_road); extern CommandCost CanExpandRailStation(const BaseStation *st, TileArea &new_ta); -extern CommandCost CalculateRoadStopCost(TileArea tile_area, DoCommandFlags flags, bool is_drive_through, StationType station_type, Axis axis, DiagDirection ddir, StationID *est, RoadType rt, Money unit_cost); +extern CommandCost CalculateRoadStopCost(TileArea tile_area, DoCommandFlags flags, bool is_drive_through, StationType station_type, const RoadStopSpec *roadstopspec, Axis axis, DiagDirection ddir, StationID *est, RoadType rt, Money unit_cost); +extern CommandCost IsRailStationBridgeAboveOk(TileIndex tile, const StationSpec *statspec, uint8_t layout); + extern CommandCost RemoveRoadWaypointStop(TileIndex tile, DoCommandFlags flags, int replacement_spec_index); /** @@ -231,12 +231,22 @@ CommandCost CmdBuildRailWaypoint(DoCommandFlags flags, TileIndex start_tile, Axi /* Make sure the area below consists of clear tiles. (OR tiles belonging to a certain rail station) */ StationID est = StationID::Invalid(); + const StationSpec *spec = StationClass::Get(spec_class)->GetSpec(spec_index); + std::vector layout(count); + if (spec != nullptr) { + /* For NewGRF waypoints we like to have their style. */ + GetStationLayout(layout.data(), count, 1, spec); + } + /* Check whether the tiles we're building on are valid rail or not. */ TileIndexDiff offset = TileOffsByAxis(OtherAxis(axis)); for (int i = 0; i < count; i++) { TileIndex tile = start_tile + i * offset; CommandCost ret = IsValidTileForWaypoint(tile, axis, &est); if (ret.Failed()) return ret; + + ret = IsRailStationBridgeAboveOk(tile, spec, layout[i]); + if (ret.Failed()) return ret; } Waypoint *wp = nullptr; @@ -285,12 +295,6 @@ CommandCost CmdBuildRailWaypoint(DoCommandFlags flags, TileIndex start_tile, Axi wp->UpdateVirtCoord(); - const StationSpec *spec = StationClass::Get(spec_class)->GetSpec(spec_index); - std::vector layout(count); - if (spec != nullptr) { - /* For NewGRF waypoints we like to have their style. */ - GetStationLayout(layout.data(), count, 1, spec); - } uint8_t map_spec_index = AllocateSpecToStation(spec, wp, true); Company *c = Company::Get(wp->owner); @@ -364,7 +368,7 @@ CommandCost CmdBuildRoadWaypoint(DoCommandFlags flags, TileIndex start_tile, Axi unit_cost = _price[PR_BUILD_STATION_TRUCK]; } StationID est = StationID::Invalid(); - CommandCost cost = CalculateRoadStopCost(roadstop_area, flags, true, StationType::RoadWaypoint, axis, AxisToDiagDir(axis), &est, INVALID_ROADTYPE, unit_cost); + CommandCost cost = CalculateRoadStopCost(roadstop_area, flags, true, StationType::RoadWaypoint, roadstopspec, axis, AxisToDiagDir(axis), &est, INVALID_ROADTYPE, unit_cost); if (cost.Failed()) return cost; Waypoint *wp = nullptr;