diff --git a/src/bridge_map.h b/src/bridge_map.h index 74c6974db2..7d8af3a319 100644 --- a/src/bridge_map.h +++ b/src/bridge_map.h @@ -180,4 +180,72 @@ static inline void MakeAqueductBridgeRamp(TileIndex t, Owner o, DiagDirection d) MakeBridgeRamp(t, o, 0, d, TRANSPORT_WATER, 0); } +/** + * Checks if this road bridge head is a custom bridge head + * @param t The tile to analyze + * @pre IsBridgeTile(t) && GetTunnelBridgeTransportType(t) == TRANSPORT_ROAD + * @return true if it is a custom bridge head + */ +static inline bool IsRoadCustomBridgeHead(TileIndex t) +{ + assert(IsBridgeTile(t) && (TransportType)GB(_m[t].m5, 2, 2) == TRANSPORT_ROAD); + return GB(_m[t].m2, 0, 8) != 0; +} + +/** + * Checks if this tile is a road bridge head with a custom bridge head + * @param t The tile to analyze + * @return true if it is a road bridge head with a custom bridge head + */ +static inline bool IsRoadCustomBridgeHeadTile(TileIndex t) +{ + return IsBridgeTile(t) && (TransportType)GB(_m[t].m5, 2, 2) == TRANSPORT_ROAD && IsRoadCustomBridgeHead(t); +} + +/** + * Returns the road bits for a (possibly custom) road bridge head + * @param t The tile to analyze + * @param rt Road type. + * @pre IsBridgeTile(t) && GetTunnelBridgeTransportType(t) == TRANSPORT_ROAD + * @return road bits for the bridge head + */ +static inline RoadBits GetCustomBridgeHeadRoadBits(TileIndex t, RoadType rt) +{ + assert(IsBridgeTile(t)); + if (!HasTileRoadType(t, rt)) return (RoadBits) 0; + RoadBits bits = (GB(_m[t].m5, 0, 1) ? ROAD_Y : ROAD_X) ^ (RoadBits) GB(_m[t].m2, rt == ROADTYPE_TRAM ? 4 : 0, 4); + return bits; +} + +/** + * Returns the road bits for a (possibly custom) road bridge head, for all road types + * @param t The tile to analyze + * @pre IsBridgeTile(t) && GetTunnelBridgeTransportType(t) == TRANSPORT_ROAD + * @return road bits for the bridge head + */ +static inline RoadBits GetCustomBridgeHeadAllRoadBits(TileIndex t) +{ + return GetCustomBridgeHeadRoadBits(t, ROADTYPE_ROAD) | GetCustomBridgeHeadRoadBits(t, ROADTYPE_TRAM); +} + +/** + * Sets the road bits for a (possibly custom) road bridge head + * @param t The tile to modify + * @param rt Road type. + * @param bits The road bits. + * @pre IsBridgeTile(t) && GetTunnelBridgeTransportType(t) == TRANSPORT_ROAD + * @pre HasTileRoadType() must be set correctly before calling this + */ +static inline void SetCustomBridgeHeadRoadBits(TileIndex t, RoadType rt, RoadBits bits) +{ + assert(IsBridgeTile(t)); + if (HasTileRoadType(t, rt)) { + assert(bits != ROAD_NONE); + SB(_m[t].m2, rt == ROADTYPE_TRAM ? 4 : 0, 4, bits ^ (GB(_m[t].m5, 0, 1) ? ROAD_Y : ROAD_X)); + } else { + assert(bits == ROAD_NONE); + SB(_m[t].m2, rt == ROADTYPE_TRAM ? 4 : 0, 4, 0); + } +} + #endif /* BRIDGE_MAP_H */ diff --git a/src/company_gui.cpp b/src/company_gui.cpp index 0be6679583..424ee85df6 100644 --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -2474,6 +2474,15 @@ void DirtyCompanyInfrastructureWindows(CompanyID company) SetWindowDirty(WC_COMPANY_INFRASTRUCTURE, company); } +/** + * Redraw all windows with all company infrastructure counts. + */ +void DirtyAllCompanyInfrastructureWindows() +{ + SetWindowClassesDirty(WC_COMPANY); + SetWindowClassesDirty(WC_COMPANY_INFRASTRUCTURE); +} + struct BuyCompanyWindow : Window { BuyCompanyWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc) { diff --git a/src/company_gui.h b/src/company_gui.h index f4964f3dc7..ed6326dfa4 100644 --- a/src/company_gui.h +++ b/src/company_gui.h @@ -25,5 +25,6 @@ void ShowCompany(CompanyID company); void InvalidateCompanyWindows(const Company *c); void DeleteCompanyWindows(CompanyID company); void DirtyCompanyInfrastructureWindows(CompanyID company); +void DirtyAllCompanyInfrastructureWindows(); #endif /* COMPANY_GUI_H */ diff --git a/src/landscape.cpp b/src/landscape.cpp index 185e84a80b..e71e69b659 100644 --- a/src/landscape.cpp +++ b/src/landscape.cpp @@ -30,6 +30,7 @@ #include "core/random_func.hpp" #include "object_base.h" #include "company_func.h" +#include "tunnelbridge_map.h" #include "pathfinder/npf/aystar.h" #include "saveload/saveload.h" #include @@ -351,6 +352,8 @@ Slope GetFoundationSlope(TileIndex tile, int *z) bool HasFoundationNW(TileIndex tile, Slope slope_here, uint z_here) { + if (IsRoadCustomBridgeHeadTile(tile) && GetTunnelBridgeDirection(tile) == DIAGDIR_NW) return false; + int z; int z_W_here = z_here; @@ -368,6 +371,8 @@ bool HasFoundationNW(TileIndex tile, Slope slope_here, uint z_here) bool HasFoundationNE(TileIndex tile, Slope slope_here, uint z_here) { + if (IsRoadCustomBridgeHeadTile(tile) && GetTunnelBridgeDirection(tile) == DIAGDIR_NE) return false; + int z; int z_E_here = z_here; diff --git a/src/pathfinder/follow_track.hpp b/src/pathfinder/follow_track.hpp index 9f19b029c0..b1c7bfaec4 100644 --- a/src/pathfinder/follow_track.hpp +++ b/src/pathfinder/follow_track.hpp @@ -101,8 +101,9 @@ struct CFollowTrackT { assert(IsTram()); // this function shouldn't be called in other cases - if (IsNormalRoadTile(tile)) { - RoadBits rb = GetRoadBits(tile, ROADTYPE_TRAM); + const bool is_bridge = IsRoadCustomBridgeHeadTile(tile); + if (is_bridge || IsNormalRoadTile(tile)) { + RoadBits rb = is_bridge ? GetCustomBridgeHeadRoadBits(tile, ROADTYPE_TRAM) : GetRoadBits(tile, ROADTYPE_TRAM); switch (rb) { case ROAD_NW: return DIAGDIR_NW; case ROAD_SW: return DIAGDIR_SW; @@ -215,7 +216,7 @@ protected: m_tiles_skipped = GetTunnelBridgeLength(m_new_tile, m_old_tile); return; } - assert(ReverseDiagDir(enterdir) == m_exitdir); + if (!IsRoadCustomBridgeHeadTile(m_old_tile)) assert(ReverseDiagDir(enterdir) == m_exitdir); } /* normal or station tile, do one step */ @@ -364,7 +365,12 @@ protected: } } } else { // IsBridge(m_new_tile) - if (!m_is_bridge) { + if (!m_is_bridge && IsRoadTT() && IsRoadCustomBridgeHeadTile(m_new_tile)) { + if (!(DiagDirToRoadBits(ReverseDiagDir(m_exitdir)) & GetCustomBridgeHeadRoadBits(m_new_tile, IsTram() ? ROADTYPE_TRAM : ROADTYPE_ROAD))) { + m_err = EC_NO_WAY; + return false; + } + } else if (!m_is_bridge) { DiagDirection ramp_enderdir = GetTunnelBridgeDirection(m_new_tile); if (ramp_enderdir != m_exitdir) { m_err = EC_NO_WAY; diff --git a/src/pathfinder/yapf/yapf_road.cpp b/src/pathfinder/yapf/yapf_road.cpp index 380b641da7..fa100700d1 100644 --- a/src/pathfinder/yapf/yapf_road.cpp +++ b/src/pathfinder/yapf/yapf_road.cpp @@ -104,7 +104,10 @@ public: */ inline bool PfCalcCost(Node &n, const TrackFollower *tf) { - int segment_cost = 0; + /* this is to handle the case where the starting tile is a junction custom bridge head, + * and we have advanced across the bridge in the initial step */ + int segment_cost = tf->m_tiles_skipped * YAPF_TILE_LENGTH; + uint tiles = 0; /* start at n.m_key.m_tile / n.m_key.m_td and walk to the end of segment */ TileIndex tile = n.m_key.m_tile; @@ -127,6 +130,11 @@ public: TrackFollower F(Yapf().GetVehicle()); if (!F.Follow(tile, trackdir)) break; + /* if we skipped some tunnel tiles, add their cost */ + /* with custom bridge heads, this cost must be added before checking if the segment has ended */ + segment_cost += F.m_tiles_skipped * YAPF_TILE_LENGTH; + tiles += F.m_tiles_skipped + 1; + /* if there are more trackdirs available & reachable, we are at the end of segment */ if (KillFirstBit(F.m_new_td_bits) != TRACKDIR_BIT_NONE) break; @@ -135,10 +143,6 @@ public: /* stop if RV is on simple loop with no junctions */ if (F.m_new_tile == n.m_key.m_tile && new_td == n.m_key.m_td) return false; - /* if we skipped some tunnel tiles, add their cost */ - segment_cost += F.m_tiles_skipped * YAPF_TILE_LENGTH; - tiles += F.m_tiles_skipped + 1; - /* add hilly terrain penalty */ segment_cost += Yapf().SlopeCost(tile, F.m_new_tile, trackdir); diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index 6482a14b19..f6ba3381f7 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -53,7 +53,7 @@ bool RoadVehiclesAreBuilt() } /** Invalid RoadBits on slopes. */ -static const RoadBits _invalid_tileh_slopes_road[2][15] = { +extern const RoadBits _invalid_tileh_slopes_road[2][15] = { /* The inverse of the mixable RoadBits on a leveled slope */ { ROAD_NONE, // SLOPE_FLAT @@ -213,36 +213,75 @@ static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits piec if (ret.Failed()) return ret; if (!IsTileType(tile, MP_ROAD)) { + const bool custom_bridge_head = IsBridgeTile(tile) && + HasBridgeFlatRamp(GetTileSlope(tile), DiagDirToAxis(GetTunnelBridgeDirection(tile))) && + (_settings_game.construction.road_custom_bridge_heads || IsRoadCustomBridgeHead(tile)); + /* If it's the last roadtype, just clear the whole tile */ - if (rts == RoadTypeToRoadTypes(rt)) return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); + if (!custom_bridge_head && rts == RoadTypeToRoadTypes(rt)) return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); CommandCost cost(EXPENSES_CONSTRUCTION); if (IsTileType(tile, MP_TUNNELBRIDGE)) { - /* Removing any roadbit in the bridge axis removes the roadtype (that's the behaviour remove-long-roads needs) */ - if ((AxisToRoadBits(DiagDirToAxis(GetTunnelBridgeDirection(tile))) & pieces) == ROAD_NONE) return_cmd_error(rt == ROADTYPE_TRAM ? STR_ERROR_THERE_IS_NO_TRAMWAY : STR_ERROR_THERE_IS_NO_ROAD); + const RoadBits entrance_piece = DiagDirToRoadBits(GetTunnelBridgeDirection(tile)); + const RoadBits axial_pieces = AxisToRoadBits(DiagDirToAxis(GetTunnelBridgeDirection(tile))); + const RoadBits existing = IsBridge(tile) ? GetCustomBridgeHeadRoadBits(tile, rt) : axial_pieces; + const RoadType other_rt = (rt == ROADTYPE_ROAD) ? ROADTYPE_TRAM : ROADTYPE_ROAD; - TileIndex other_end = GetOtherTunnelBridgeEnd(tile); - /* Pay for *every* tile of the bridge or tunnel */ - uint len = GetTunnelBridgeLength(other_end, tile) + 2; - cost.AddCost(len * _price[PR_CLEAR_ROAD]); + /* handle case where we would otherwise leave a single bridge entrance piece */ + if ((existing & ~pieces) == entrance_piece) { + pieces |= entrance_piece; + } + + /* Removing any roadbit in the bridge axis removes the roadtype (that's the behaviour remove-long-roads needs) */ + if ((existing & pieces) == ROAD_NONE) return_cmd_error(rt == ROADTYPE_TRAM ? STR_ERROR_THERE_IS_NO_TRAMWAY : STR_ERROR_THERE_IS_NO_ROAD); + + if (!custom_bridge_head) pieces |= axial_pieces; + + const TileIndex other_end = GetOtherTunnelBridgeEnd(tile); + const uint middle_len = GetTunnelBridgeLength(other_end, tile); + uint pieces_count = 0; + + const RoadBits other_end_existing = IsBridge(other_end) ? GetCustomBridgeHeadRoadBits(other_end, rt) : axial_pieces; + RoadBits other_end_pieces = ROAD_NONE; + if (pieces & entrance_piece) { + other_end_pieces |= MirrorRoadBits(entrance_piece); + /* if removing the other end entrance would only leave one piece, remove that too */ + if (CountBits(other_end_existing & ~other_end_pieces) == 1) { + other_end_pieces |= other_end_existing; + } + pieces_count += middle_len * 2; + if ((GetCustomBridgeHeadRoadBits(tile, other_rt) & entrance_piece) == ROAD_NONE) { + /* can't leave no entrance pieces for any road type */ + return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); + } + } + pieces_count += CountBits(pieces & existing); + pieces_count += CountBits(other_end_pieces & other_end_existing); + + cost.AddCost(pieces_count * _price[PR_CLEAR_ROAD]); if (flags & DC_EXEC) { - Company *c = Company::GetIfValid(GetRoadOwner(tile, rt)); - if (c != NULL) { - /* A full diagonal road tile has two road bits. */ - c->infrastructure.road[rt] -= len * 2 * TUNNELBRIDGE_TRACKBIT_FACTOR; - DirtyCompanyInfrastructureWindows(c->index); + SubtractRoadTunnelBridgeInfrastructure(tile, other_end); + + const RoadBits bits = existing & ~pieces; + const RoadBits other_bits = other_end_existing & ~other_end_pieces; + + if (bits == ROAD_NONE) SetRoadTypes(tile, GetRoadTypes(tile) & ~RoadTypeToRoadTypes(rt)); + if (other_bits == ROAD_NONE) SetRoadTypes(other_end, GetRoadTypes(other_end) & ~RoadTypeToRoadTypes(rt)); + + if (IsBridge(tile)) { + SetCustomBridgeHeadRoadBits(tile, rt, bits); + SetCustomBridgeHeadRoadBits(other_end, rt, other_bits); } - SetRoadTypes(other_end, GetRoadTypes(other_end) & ~RoadTypeToRoadTypes(rt)); - SetRoadTypes(tile, GetRoadTypes(tile) & ~RoadTypeToRoadTypes(rt)); + if (bits == ROAD_NONE && other_bits == ROAD_NONE) { + /* If the owner of the bridge sells all its road, also move the ownership + * to the owner of the other roadtype, unless the bridge owner is a town. */ - /* If the owner of the bridge sells all its road, also move the ownership - * to the owner of the other roadtype, unless the bridge owner is a town. */ - RoadType other_rt = (rt == ROADTYPE_ROAD) ? ROADTYPE_TRAM : ROADTYPE_ROAD; - Owner other_owner = GetRoadOwner(tile, other_rt); - if (!IsTileOwner(tile, other_owner) && !IsTileOwner(tile, OWNER_TOWN)) { - SetTileOwner(tile, other_owner); - SetTileOwner(other_end, other_owner); + Owner other_owner = GetRoadOwner(tile, other_rt); + if (!IsTileOwner(tile, other_owner) && !IsTileOwner(tile, OWNER_TOWN)) { + SetTileOwner(tile, other_owner); + SetTileOwner(other_end, other_owner); + } } /* Mark tiles dirty that have been repaved */ @@ -252,6 +291,9 @@ static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits piec MarkTileDirtyByTile(tile); MarkTileDirtyByTile(other_end); } + + AddRoadTunnelBridgeInfrastructure(tile, other_end); + DirtyAllCompanyInfrastructureWindows(); } } else { assert(IsDriveThroughStopTile(tile)); @@ -670,12 +712,108 @@ CommandCost CmdBuildRoad(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 case MP_TUNNELBRIDGE: { if (GetTunnelBridgeTransportType(tile) != TRANSPORT_ROAD) goto do_clear; - /* Only allow building the outern roadbit, so building long roads stops at existing bridges */ - if (MirrorRoadBits(DiagDirToRoadBits(GetTunnelBridgeDirection(tile))) != pieces) goto do_clear; - if (HasTileRoadType(tile, rt)) return_cmd_error(STR_ERROR_ALREADY_BUILT); - /* Don't allow adding roadtype to the bridge/tunnel when vehicles are already driving on it */ - CommandCost ret = TunnelBridgeIsFree(tile, GetOtherTunnelBridgeEnd(tile)); - if (ret.Failed()) return ret; + + const TileIndex other_end = GetOtherTunnelBridgeEnd(tile); + + if (IsBridge(tile)) { + const DiagDirection entrance_dir = GetTunnelBridgeDirection(tile); + const RoadBits entrance_piece = DiagDirToRoadBits(entrance_dir); + const RoadBits axial_pieces = AxisToRoadBits(DiagDirToAxis(entrance_dir)); + existing = GetCustomBridgeHeadRoadBits(tile, rt); + + if (!(_settings_game.construction.road_custom_bridge_heads && HasBridgeFlatRamp(tileh, DiagDirToAxis(entrance_dir)))) { + /* Ordinary bridge heads only */ + /* Only allow building the outer roadbit, so building long roads stops at existing bridges */ + if (MirrorRoadBits(entrance_piece) != pieces) goto do_clear; + pieces = axial_pieces; + } + if ((existing & pieces) == pieces) return_cmd_error(STR_ERROR_ALREADY_BUILT); + if ((pieces & ~axial_pieces) && !_settings_game.construction.build_on_slopes) { + return_cmd_error(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION); + } + if ((_invalid_tileh_slopes_road[0][tileh] & (pieces & ~entrance_piece)) != ROAD_NONE) { + return_cmd_error(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION); + } + + /* Don't allow adding roadtype to the bridge/tunnel when vehicles are already driving on it */ + CommandCost ret = TunnelBridgeIsFree(tile, other_end); + if (ret.Failed()) return ret; + + /* check if this end is already owned by someone else */ + const Owner owner = GetRoadOwner(tile, rt); + if (owner != OWNER_NONE) { + CommandCost ret = CheckOwnership(owner, tile); + if (ret.Failed()) return ret; + } + + if ((existing | pieces) == entrance_piece) { + /* + * Don't allow the custom bridge head bits to be only the entrance piece + * as this makes road vehicles go haywire + */ + pieces = axial_pieces; + } + + RoadBits added_pieces = (existing | pieces) & ~existing; + uint added_pieces_count = CountBits(added_pieces); + RoadBits other_end_added_pieces = ROAD_NONE; + RoadBits other_end_existing = ROAD_NONE; + + if (added_pieces & entrance_piece) { + /* adding road to whole bridge */ + + /* check if other end is already owned by someone else */ + const Owner other_end_owner = GetRoadOwner(other_end, rt); + if (other_end_owner != OWNER_NONE) { + CommandCost ret = CheckOwnership(other_end_owner, other_end); + if (ret.Failed()) return ret; + } + + other_end_added_pieces = MirrorRoadBits(entrance_piece); + added_pieces_count += 1 + (GetTunnelBridgeLength(tile, other_end) * 2); + + other_end_existing = GetCustomBridgeHeadRoadBits(other_end, rt); + assert((other_end_added_pieces & other_end_existing) == ROAD_NONE); + + if (other_end_existing == ROAD_NONE) { + /* + * Don't allow the other end custom bridge head bits to be only the entrance piece + * as this makes road vehicles go haywire + */ + other_end_added_pieces = axial_pieces; + added_pieces_count++; + } + } + + cost.AddCost(added_pieces_count * _price[PR_BUILD_ROAD]); + + if (flags & DC_EXEC) { + SubtractRoadTunnelBridgeInfrastructure(tile, other_end); + + SetRoadTypes(tile, GetRoadTypes(tile) | RoadTypeToRoadTypes(rt)); + SetRoadOwner(tile, rt, company); + SetCustomBridgeHeadRoadBits(tile, rt, existing | pieces); + if (other_end_added_pieces) { + SetRoadTypes(other_end, GetRoadTypes(other_end) | RoadTypeToRoadTypes(rt)); + SetRoadOwner(other_end, rt, company); + SetCustomBridgeHeadRoadBits(other_end, rt, other_end_existing | other_end_added_pieces); + } + + MarkBridgeDirty(tile); + + AddRoadTunnelBridgeInfrastructure(tile, other_end); + DirtyAllCompanyInfrastructureWindows(); + } + + return cost; + } else { // IsTunnel(tile) + /* Only allow building the outer roadbit, so building long roads stops at existing bridges */ + if (MirrorRoadBits(DiagDirToRoadBits(GetTunnelBridgeDirection(tile))) != pieces) goto do_clear; + if (HasTileRoadType(tile, rt)) return_cmd_error(STR_ERROR_ALREADY_BUILT); + /* Don't allow adding roadtype to the bridge/tunnel when vehicles are already driving on it */ + CommandCost ret = TunnelBridgeIsFree(tile, other_end); + if (ret.Failed()) return ret; + } break; } @@ -762,7 +900,7 @@ do_clear:; /* Mark tiles dirty that have been repaved */ if (IsBridge(tile)) { - MarkBridgeDirty(tile); + NOT_REACHED(); } else { MarkTileDirtyByTile(other_end); MarkTileDirtyByTile(tile); @@ -864,8 +1002,6 @@ CommandCost CmdBuildLongRoad(TileIndex start_tile, DoCommandFlag flags, uint32 p CommandCost cost(EXPENSES_CONSTRUCTION); CommandCost last_error = CMD_ERROR; TileIndex tile = start_tile; - bool had_bridge = false; - bool had_tunnel = false; bool had_success = false; bool is_ai = HasBit(p2, 6); @@ -896,27 +1032,21 @@ CommandCost CmdBuildLongRoad(TileIndex start_tile, DoCommandFlag flags, uint32 p } } else { had_success = true; - /* Only pay for the upgrade on one side of the bridges and tunnels */ - if (IsTileType(tile, MP_TUNNELBRIDGE)) { - if (IsBridge(tile)) { - if (!had_bridge || GetTunnelBridgeDirection(tile) == dir) { - cost.AddCost(ret); - } - had_bridge = true; - } else { // IsTunnel(tile) - if (!had_tunnel || GetTunnelBridgeDirection(tile) == dir) { - cost.AddCost(ret); - } - had_tunnel = true; - } - } else { - cost.AddCost(ret); - } + cost.AddCost(ret); + } + /* Do not run into or across bridges/tunnels */ + if (IsTileType(tile, MP_TUNNELBRIDGE)) { + if (GetTunnelBridgeDirection(tile) == dir) break; } if (tile == end_tile) break; tile += TileOffsByDiagDir(dir); + + /* Do not run onto a bridge/tunnel tile from below/above */ + if (IsTileType(tile, MP_TUNNELBRIDGE)) { + if (GetTunnelBridgeDirection(tile) == ReverseDiagDir(dir)) break; + } } return had_success ? cost : last_error; @@ -1239,16 +1369,17 @@ static void DrawRoadDetail(SpriteID img, const TileInfo *ti, int dx, int dy, int * Draw ground sprite and road pieces * @param ti TileInfo */ -static void DrawRoadBits(TileInfo *ti) +void DrawRoadBits(TileInfo *ti) { - RoadBits road = GetRoadBits(ti->tile, ROADTYPE_ROAD); - RoadBits tram = GetRoadBits(ti->tile, ROADTYPE_TRAM); + const bool is_bridge = IsTileType(ti->tile, MP_TUNNELBRIDGE); + RoadBits road = is_bridge ? GetCustomBridgeHeadRoadBits(ti->tile, ROADTYPE_ROAD) : GetRoadBits(ti->tile, ROADTYPE_ROAD); + RoadBits tram = is_bridge ? GetCustomBridgeHeadRoadBits(ti->tile, ROADTYPE_TRAM) : GetRoadBits(ti->tile, ROADTYPE_TRAM); SpriteID image = 0; PaletteID pal = PAL_NONE; if (ti->tileh != SLOPE_FLAT) { - DrawFoundation(ti, GetRoadFoundation(ti->tileh, road | tram)); + DrawFoundation(ti, is_bridge ? FOUNDATION_LEVELED : GetRoadFoundation(ti->tileh, road | tram)); /* DrawFoundation() modifies ti. * Default sloped sprites.. */ @@ -1257,7 +1388,7 @@ static void DrawRoadBits(TileInfo *ti) if (image == 0) image = _road_tile_sprites_1[road != ROAD_NONE ? road : tram]; - Roadside roadside = GetRoadside(ti->tile); + Roadside roadside = is_bridge ? ROADSIDE_PAVED : GetRoadside(ti->tile); if (DrawRoadAsSnowDesert(ti->tile, roadside)) { image += 19; @@ -1285,14 +1416,14 @@ static void DrawRoadBits(TileInfo *ti) DrawGroundSprite(image, pal); } - if (road != ROAD_NONE) { + if (!is_bridge && road != ROAD_NONE) { DisallowedRoadDirections drd = GetDisallowedRoadDirections(ti->tile); if (drd != DRD_NONE) { DrawGroundSpriteAt(SPR_ONEWAY_BASE + drd - 1 + ((road == ROAD_X) ? 0 : 3), PAL_NONE, 8, 8, GetPartialPixelZ(8, 8, ti->tileh)); } } - if (HasRoadWorks(ti->tile)) { + if (!is_bridge && HasRoadWorks(ti->tile)) { /* Road works */ DrawGroundSprite((road | tram) & ROAD_X ? SPR_EXCAVATION_X : SPR_EXCAVATION_Y, PAL_NONE); return; @@ -1587,7 +1718,7 @@ static bool ClickTile_Road(TileIndex tile) } /* Converts RoadBits to TrackBits */ -static const TrackBits _road_trackbits[16] = { +extern const TrackBits _road_trackbits[16] = { TRACK_BIT_NONE, // ROAD_NONE TRACK_BIT_NONE, // ROAD_NW TRACK_BIT_NONE, // ROAD_SW diff --git a/src/road_map.cpp b/src/road_map.cpp index 4984117bab..7d6a541fa8 100644 --- a/src/road_map.cpp +++ b/src/road_map.cpp @@ -52,6 +52,7 @@ RoadBits GetAnyRoadBits(TileIndex tile, RoadType rt, bool straight_tunnel_bridge case MP_TUNNELBRIDGE: if (GetTunnelBridgeTransportType(tile) != TRANSPORT_ROAD) return ROAD_NONE; + if (IsRoadCustomBridgeHeadTile(tile)) return GetCustomBridgeHeadRoadBits(tile, rt); return straight_tunnel_bridge_entrance ? AxisToRoadBits(DiagDirToAxis(GetTunnelBridgeDirection(tile))) : DiagDirToRoadBits(ReverseDiagDir(GetTunnelBridgeDirection(tile))); diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 9b541a766b..1072c72d41 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -1143,6 +1143,8 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) * by the previous vehicle in the chain when it gets to the right place. */ if (v->IsInDepot()) return true; + bool no_advance_tile = false; + if (v->state == RVSB_WORMHOLE) { /* Vehicle is entering a depot or is on a bridge or in a tunnel */ GetNewVehiclePosResult gp = GetNewVehiclePos(v); @@ -1156,19 +1158,24 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) } if (IsTileType(gp.new_tile, MP_TUNNELBRIDGE) && HasBit(VehicleEnterTile(v, gp.new_tile, gp.x, gp.y), VETS_ENTERED_WORMHOLE)) { - /* Vehicle has just entered a bridge or tunnel */ + if (IsRoadCustomBridgeHeadTile(gp.new_tile)) { + v->frame = 15; + no_advance_tile = true; + } else { + /* Vehicle has just entered a bridge or tunnel */ + v->x_pos = gp.x; + v->y_pos = gp.y; + v->UpdatePosition(); + v->UpdateInclination(true, true); + return true; + } + } else { v->x_pos = gp.x; v->y_pos = gp.y; v->UpdatePosition(); - v->UpdateInclination(true, true); + if ((v->vehstatus & VS_HIDDEN) == 0) v->Vehicle::UpdateViewport(true); return true; } - - v->x_pos = gp.x; - v->y_pos = gp.y; - v->UpdatePosition(); - if ((v->vehstatus & VS_HIDDEN) == 0) v->Vehicle::UpdateViewport(true); - return true; } /* Get move position data for next frame. @@ -1179,12 +1186,16 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) (_settings_game.vehicle.road_side << RVS_DRIVE_SIDE)) ^ v->overtaking][v->frame + 1]; if (rd.x & RDE_NEXT_TILE) { - TileIndex tile = v->tile + TileOffsByDiagDir((DiagDirection)(rd.x & 3)); + TileIndex tile = v->tile; + if (!no_advance_tile) tile += TileOffsByDiagDir((DiagDirection)(rd.x & 3)); Trackdir dir; if (v->IsFrontEngine()) { /* If this is the front engine, look for the right path. */ dir = RoadFindPathToDest(v, tile, (DiagDirection)(rd.x & 3)); + } else if (no_advance_tile) { + /* Follow previous vehicle out of custom bridge wormhole */ + dir = (Trackdir) prev->state; } else { dir = FollowPreviousRoadVehicle(v, prev, tile, (DiagDirection)(rd.x & 3), false); } @@ -1201,6 +1212,10 @@ again: /* When turning around we can't be overtaking. */ v->overtaking = 0; + if (no_advance_tile) { + DEBUG(misc, 0, "Road vehicle attempted to turn around on a single road piece bridge head"); + } + /* Turning around */ if (v->roadtype == ROADTYPE_TRAM) { /* Determine the road bits the tram needs to be able to turn around @@ -1213,9 +1228,16 @@ again: case TRACKDIR_RVREV_SW: needed = ROAD_NE; break; case TRACKDIR_RVREV_NW: needed = ROAD_SE; break; } - if ((v->Previous() != NULL && v->Previous()->tile == tile) || - (v->IsFrontEngine() && IsNormalRoadTile(tile) && !HasRoadWorks(tile) && - (needed & GetRoadBits(tile, ROADTYPE_TRAM)) != ROAD_NONE)) { + auto tile_turn_ok = [&]() -> bool { + if (IsNormalRoadTile(tile)) { + return !HasRoadWorks(tile) && (needed & GetRoadBits(tile, ROADTYPE_TRAM)) != ROAD_NONE; + } else if (IsRoadCustomBridgeHeadTile(tile)) { + return (needed & GetCustomBridgeHeadRoadBits(tile, ROADTYPE_TRAM)) != ROAD_NONE; + } else { + return false; + } + }; + if ((v->Previous() != NULL && v->Previous()->tile == tile) || tile_turn_ok()) { /* * Taking the 'big' corner for trams only happens when: * - The previous vehicle in this (articulated) tram chain is diff --git a/src/saveload/company_sl.cpp b/src/saveload/company_sl.cpp index 27c069fe08..57a33732c9 100644 --- a/src/saveload/company_sl.cpp +++ b/src/saveload/company_sl.cpp @@ -200,7 +200,8 @@ void AfterLoadCompanyStats() if (tile < other_end) { /* Count each tunnel/bridge TUNNELBRIDGE_TRACKBIT_FACTOR times to simulate * the higher structural maintenance needs, and don't forget the end tiles. */ - uint len = (GetTunnelBridgeLength(tile, other_end) + 2) * TUNNELBRIDGE_TRACKBIT_FACTOR; + const uint middle_len = GetTunnelBridgeLength(tile, other_end) * TUNNELBRIDGE_TRACKBIT_FACTOR; + const uint len = middle_len + (2 * TUNNELBRIDGE_TRACKBIT_FACTOR); switch (GetTunnelBridgeTransportType(tile)) { case TRANSPORT_RAIL: @@ -209,12 +210,7 @@ void AfterLoadCompanyStats() break; case TRANSPORT_ROAD: { - /* Iterate all present road types as each can have a different owner. */ - RoadType rt; - FOR_EACH_SET_ROADTYPE(rt, GetRoadTypes(tile)) { - c = Company::GetIfValid(GetRoadOwner(tile, rt)); - if (c != NULL) c->infrastructure.road[rt] += len * 2; // A full diagonal road has two road bits. - } + AddRoadTunnelBridgeInfrastructure(tile, other_end); break; } diff --git a/src/tunnelbridge_cmd.cpp b/src/tunnelbridge_cmd.cpp index 9ba9b26e31..4b8845f0a4 100644 --- a/src/tunnelbridge_cmd.cpp +++ b/src/tunnelbridge_cmd.cpp @@ -52,6 +52,8 @@ TileIndex _build_tunnel_endtile; ///< The end of a tunnel; as hidden return from /** Z position of the bridge sprites relative to bridge height (downwards) */ static const int BRIDGE_Z_START = 3; +extern void DrawRoadBits(TileInfo *ti); +extern const RoadBits _invalid_tileh_slopes_road[2][15]; /** * Mark bridge tiles dirty. @@ -925,19 +927,11 @@ static CommandCost DoClearBridge(TileIndex tile, DoCommandFlag flags) if (rail) { if (Company::IsValidID(owner)) Company::Get(owner)->infrastructure.rail[GetRailType(tile)] -= len * TUNNELBRIDGE_TRACKBIT_FACTOR; } else if (GetTunnelBridgeTransportType(tile) == TRANSPORT_ROAD) { - RoadType rt; - FOR_EACH_SET_ROADTYPE(rt, GetRoadTypes(tile)) { - Company *c = Company::GetIfValid(GetRoadOwner(tile, rt)); - if (c != NULL) { - /* A full diagonal road tile has two road bits. */ - c->infrastructure.road[rt] -= len * 2 * TUNNELBRIDGE_TRACKBIT_FACTOR; - DirtyCompanyInfrastructureWindows(c->index); - } - } + SubtractRoadTunnelBridgeInfrastructure(tile, endtile); } else { // Aqueduct if (Company::IsValidID(owner)) Company::Get(owner)->infrastructure.water -= len * TUNNELBRIDGE_TRACKBIT_FACTOR; } - DirtyCompanyInfrastructureWindows(owner); + DirtyAllCompanyInfrastructureWindows(); DoClearSquare(tile); DoClearSquare(endtile); @@ -1241,6 +1235,12 @@ static void DrawTile_TunnelBridge(TileInfo *ti) DrawBridgeMiddle(ti); } else { // IsBridge(ti->tile) + if (transport_type == TRANSPORT_ROAD && IsRoadCustomBridgeHead(ti->tile)) { + DrawRoadBits(ti); + DrawBridgeMiddle(ti); + return; + } + const PalSpriteID *psid; int base_offset; bool ice = HasTunnelBridgeSnowOrDesert(ti->tile); @@ -1462,11 +1462,19 @@ void DrawBridgeMiddle(const TileInfo *ti) psid++; if (transport_type == TRANSPORT_ROAD) { - RoadTypes rts = GetRoadTypes(rampsouth); + const RoadTypes rts = GetRoadTypes(rampsouth); - if (HasBit(rts, ROADTYPE_TRAM)) { + bool has_tram = HasBit(rts, ROADTYPE_TRAM); + bool has_road = HasBit(rts, ROADTYPE_ROAD); + if (IsRoadCustomBridgeHeadTile(rampsouth)) { + RoadBits entrance_bit = DiagDirToRoadBits(GetTunnelBridgeDirection(rampsouth)); + has_tram = has_tram && (GetCustomBridgeHeadRoadBits(rampsouth, ROADTYPE_TRAM) & entrance_bit); + has_road = has_road && (GetCustomBridgeHeadRoadBits(rampsouth, ROADTYPE_ROAD) & entrance_bit); + } + + if (has_tram) { /* DrawBridgeTramBits() calls EndSpriteCombine() and StartSpriteCombine() */ - DrawBridgeTramBits(x, y, bridge_z, axis ^ 1, HasBit(rts, ROADTYPE_ROAD), false); + DrawBridgeTramBits(x, y, bridge_z, axis ^ 1, has_road, false); } else { EndSpriteCombine(); StartSpriteCombine(); @@ -1547,6 +1555,10 @@ static int GetSlopePixelZ_TunnelBridge(TileIndex tile, uint x, uint y) /* In the tunnel entrance? */ if (5 <= pos && pos <= 10) return z; } else { // IsBridge(tile) + if (IsRoadCustomBridgeHeadTile(tile)) { + return z + TILE_HEIGHT; + } + DiagDirection dir = GetTunnelBridgeDirection(tile); uint pos = (DiagDirToAxis(dir) == AXIS_X ? y : x); @@ -1574,6 +1586,7 @@ static int GetSlopePixelZ_TunnelBridge(TileIndex tile, uint x, uint y) static Foundation GetFoundation_TunnelBridge(TileIndex tile, Slope tileh) { + if (IsRoadCustomBridgeHeadTile(tile)) return FOUNDATION_LEVELED; return IsTunnel(tile) ? FOUNDATION_NONE : GetBridgeFoundation(tileh, DiagDirToAxis(GetTunnelBridgeDirection(tile))); } @@ -1654,42 +1667,102 @@ static void TileLoop_TunnelBridge(TileIndex tile) } } +extern const TrackBits _road_trackbits[16]; + static TrackStatus GetTileTrackStatus_TunnelBridge(TileIndex tile, TransportType mode, uint sub_mode, DiagDirection side) { TransportType transport_type = GetTunnelBridgeTransportType(tile); if (transport_type != mode || (transport_type == TRANSPORT_ROAD && (GetRoadTypes(tile) & sub_mode) == 0)) return 0; DiagDirection dir = GetTunnelBridgeDirection(tile); + + if (mode == TRANSPORT_ROAD && IsRoadCustomBridgeHeadTile(tile)) { + if (side != INVALID_DIAGDIR && side == dir) return 0; + TrackBits bits = TRACK_BIT_NONE; + if (sub_mode & ROADTYPES_TRAM) bits |= _road_trackbits[GetCustomBridgeHeadRoadBits(tile, ROADTYPE_TRAM)]; + if (sub_mode & ROADTYPES_ROAD) bits |= _road_trackbits[GetCustomBridgeHeadRoadBits(tile, ROADTYPE_ROAD)]; + return CombineTrackStatus(TrackBitsToTrackdirBits(bits), TRACKDIR_BIT_NONE); + } if (side != INVALID_DIAGDIR && side != ReverseDiagDir(dir)) return 0; return CombineTrackStatus(TrackBitsToTrackdirBits(DiagDirToDiagTrackBits(dir)), TRACKDIR_BIT_NONE); } +static void UpdateRoadTunnelBridgeInfrastructure(TileIndex begin, TileIndex end, bool add) { + /* A full diagonal road has two road bits. */ + const uint middle_len = 2 * GetTunnelBridgeLength(begin, end) * TUNNELBRIDGE_TRACKBIT_FACTOR; + const uint len = middle_len + (2 * TUNNELBRIDGE_TRACKBIT_FACTOR); + + /* Iterate all present road types as each can have a different owner. */ + RoadType rt; + FOR_EACH_SET_ROADTYPE(rt, GetRoadTypes(begin)) { + Company * const c = Company::GetIfValid(GetRoadOwner(begin, rt)); + if (c != NULL) { + uint infra = 0; + if (IsBridge(begin)) { + const RoadBits bits = GetCustomBridgeHeadRoadBits(begin, rt); + infra += CountBits(bits) * TUNNELBRIDGE_TRACKBIT_FACTOR; + if (bits & DiagDirToRoadBits(GetTunnelBridgeDirection(begin))) { + infra += middle_len; + } + } else { + infra += len; + } + if (add) { + c->infrastructure.road[rt] += infra; + } else { + c->infrastructure.road[rt] -= infra; + } + } + } + FOR_EACH_SET_ROADTYPE(rt, GetRoadTypes(end)) { + Company * const c = Company::GetIfValid(GetRoadOwner(end, rt)); + if (c != NULL) { + uint infra = 0; + if (IsBridge(end)) { + const RoadBits bits = GetCustomBridgeHeadRoadBits(end, rt); + infra += CountBits(bits) * TUNNELBRIDGE_TRACKBIT_FACTOR; + } + if (add) { + c->infrastructure.road[rt] += infra; + } else { + c->infrastructure.road[rt] -= infra; + } + } + } +} + +void AddRoadTunnelBridgeInfrastructure(TileIndex begin, TileIndex end) { + UpdateRoadTunnelBridgeInfrastructure(begin, end, true); +} + +void SubtractRoadTunnelBridgeInfrastructure(TileIndex begin, TileIndex end) { + UpdateRoadTunnelBridgeInfrastructure(begin, end, false); +} + static void ChangeTileOwner_TunnelBridge(TileIndex tile, Owner old_owner, Owner new_owner) { - TileIndex other_end = GetOtherTunnelBridgeEnd(tile); + const TileIndex other_end = GetOtherTunnelBridgeEnd(tile); /* Set number of pieces to zero if it's the southern tile as we * don't want to update the infrastructure counts twice. */ - uint num_pieces = tile < other_end ? (GetTunnelBridgeLength(tile, other_end) + 2) * TUNNELBRIDGE_TRACKBIT_FACTOR : 0; + const uint num_pieces = tile < other_end ? (GetTunnelBridgeLength(tile, other_end) + 2) * TUNNELBRIDGE_TRACKBIT_FACTOR : 0; + const TransportType tt = GetTunnelBridgeTransportType(tile); + + if (tt == TRANSPORT_ROAD) SubtractRoadTunnelBridgeInfrastructure(tile, other_end); for (RoadType rt = ROADTYPE_ROAD; rt < ROADTYPE_END; rt++) { /* Update all roadtypes, no matter if they are present */ if (GetRoadOwner(tile, rt) == old_owner) { - if (HasBit(GetRoadTypes(tile), rt)) { - /* Update company infrastructure counts. A full diagonal road tile has two road bits. - * No need to dirty windows here, we'll redraw the whole screen anyway. */ - Company::Get(old_owner)->infrastructure.road[rt] -= num_pieces * 2; - if (new_owner != INVALID_OWNER) Company::Get(new_owner)->infrastructure.road[rt] += num_pieces * 2; - } - SetRoadOwner(tile, rt, new_owner == INVALID_OWNER ? OWNER_NONE : new_owner); } } + if (tt == TRANSPORT_ROAD) AddRoadTunnelBridgeInfrastructure(tile, other_end); + if (!IsTileOwner(tile, old_owner)) return; /* Update company infrastructure counts for rail and water as well. * No need to dirty windows here, we'll redraw the whole screen anyway. */ - TransportType tt = GetTunnelBridgeTransportType(tile); + Company *old = Company::Get(old_owner); if (tt == TRANSPORT_RAIL) { old->infrastructure.rail[GetRailType(tile)] -= num_pieces; @@ -1883,6 +1956,14 @@ static CommandCost TerraformTile_TunnelBridge(TileIndex tile, DoCommandFlag flag int z_old; Slope tileh_old = GetTileSlope(tile, &z_old); + if (IsRoadCustomBridgeHeadTile(tile)) { + const RoadBits pieces = GetCustomBridgeHeadAllRoadBits(tile); + const RoadBits entrance_piece = DiagDirToRoadBits(direction); + if ((_invalid_tileh_slopes_road[0][tileh_new] & (pieces & ~entrance_piece)) != ROAD_NONE) { + return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); + } + } + /* Check if new slope is valid for bridges in general (so we can safely call GetBridgeFoundation()) */ if ((direction == DIAGDIR_NW) || (direction == DIAGDIR_NE)) { CheckBridgeSlopeSouth(axis, &tileh_old, &z_old); diff --git a/src/tunnelbridge_map.h b/src/tunnelbridge_map.h index 0f7f17b3ac..0437fd1e1f 100644 --- a/src/tunnelbridge_map.h +++ b/src/tunnelbridge_map.h @@ -121,4 +121,7 @@ static inline TrackBits GetTunnelBridgeReservationTrackBits(TileIndex t) return HasTunnelBridgeReservation(t) ? DiagDirToDiagTrackBits(GetTunnelBridgeDirection(t)) : TRACK_BIT_NONE; } +void AddRoadTunnelBridgeInfrastructure(TileIndex begin, TileIndex end); +void SubtractRoadTunnelBridgeInfrastructure(TileIndex begin, TileIndex end); + #endif /* TUNNELBRIDGE_MAP_H */