From c19abebf8d4686439d814be8309f6ef907e81de0 Mon Sep 17 00:00:00 2001 From: Tyler Trahan Date: Tue, 1 Nov 2022 14:51:23 -0600 Subject: [PATCH] Feature: Multi-track level crossings (#9931) --- src/pbs.cpp | 3 +- src/rail_cmd.cpp | 3 ++ src/road_cmd.cpp | 51 +++++++++++++++++++++++++-- src/road_func.h | 3 +- src/roadveh_cmd.cpp | 2 +- src/roadveh_cmd.h | 2 ++ src/saveload/afterload.cpp | 46 +++++++++++++++++++++++- src/saveload/saveload.h | 1 + src/table/road_land.h | 40 +++++++++++++++++++++ src/train_cmd.cpp | 88 ++++++++++++++++++++++++++++++++++++++-------- 10 files changed, 217 insertions(+), 22 deletions(-) diff --git a/src/pbs.cpp b/src/pbs.cpp index c4dfcbce54..df3a5cf339 100644 --- a/src/pbs.cpp +++ b/src/pbs.cpp @@ -105,8 +105,7 @@ bool TryReserveRailTrack(TileIndex tile, Track t, bool trigger_stations) case MP_ROAD: if (IsLevelCrossing(tile) && !HasCrossingReservation(tile)) { SetCrossingReservation(tile, true); - BarCrossing(tile); - MarkTileDirtyByTile(tile); // crossing barred, make tile dirty + UpdateLevelCrossing(tile, false); return true; } break; diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index f61a2860f9..7ee5813606 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -548,6 +548,7 @@ CommandCost CmdBuildSingleRail(DoCommandFlag flags, TileIndex tile, RailType rai if (flags & DC_EXEC) { MakeRoadCrossing(tile, road_owner, tram_owner, _current_company, (track == TRACK_X ? AXIS_Y : AXIS_X), railtype, roadtype_road, roadtype_tram, GetTownIndex(tile)); UpdateLevelCrossing(tile, false); + MarkDirtyAdjacentLevelCrossingTiles(tile, GetCrossingRoadAxis(tile)); Company::Get(_current_company)->infrastructure.rail[railtype] += LEVELCROSSING_TRACKBIT_FACTOR; DirtyCompanyInfrastructureWindows(_current_company); if (num_new_road_pieces > 0 && Company::IsValidID(road_owner)) { @@ -649,6 +650,8 @@ CommandCost CmdRemoveSingleRail(DoCommandFlag flags, TileIndex tile, Track track cost.AddCost(RailClearCost(GetRailType(tile))); if (flags & DC_EXEC) { + MarkDirtyAdjacentLevelCrossingTiles(tile, GetCrossingRoadAxis(tile)); + if (HasReservedTracks(tile, trackbit)) { v = GetTrainForReservation(tile, track); if (v != nullptr) FreeTrainTrackReservation(v); diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index 8d2461c48f..b76f3e8e70 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -380,6 +380,8 @@ static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits piec uint len = GetTunnelBridgeLength(other_end, tile) + 2; cost.AddCost(len * 2 * RoadClearCost(existing_rt)); if (flags & DC_EXEC) { + MarkDirtyAdjacentLevelCrossingTiles(tile, GetCrossingRoadAxis(tile)); + /* A full diagonal road tile has two road bits. */ UpdateCompanyRoadInfrastructure(existing_rt, GetRoadOwner(tile, rtt), -(int)(len * 2 * TUNNELBRIDGE_TRACKBIT_FACTOR)); @@ -780,6 +782,7 @@ CommandCost CmdBuildRoad(DoCommandFlag flags, TileIndex tile, RoadBits pieces, R MakeRoadCrossing(tile, company, company, GetTileOwner(tile), roaddir, GetRailType(tile), rtt == RTT_ROAD ? rt : INVALID_ROADTYPE, (rtt == RTT_TRAM) ? rt : INVALID_ROADTYPE, town_id); SetCrossingReservation(tile, reserved); UpdateLevelCrossing(tile, false); + MarkDirtyAdjacentLevelCrossingTiles(tile, GetCrossingRoadAxis(tile)); MarkTileDirtyByTile(tile); } return CommandCost(EXPENSES_CONSTRUCTION, 2 * RoadBuildCost(rt)); @@ -1703,7 +1706,42 @@ static void DrawTile_Road(TileInfo *ti) SpriteID rail = GetCustomRailSprite(rti, ti->tile, RTSG_CROSSING) + axis; DrawGroundSprite(rail, pal); - DrawRailTileSeq(ti, &_crossing_layout, TO_CATENARY, rail, 0, PAL_NONE); + const Axis road_axis = GetCrossingRoadAxis(ti->tile); + const DiagDirection dir1 = AxisToDiagDir(road_axis); + const DiagDirection dir2 = ReverseDiagDir(dir1); + uint adjacent_diagdirs = 0; + for (DiagDirection dir : { dir1, dir2 }) { + const TileIndex t = TileAddByDiagDir(ti->tile, dir); + if (t < MapSize() && IsLevelCrossingTile(t) && GetCrossingRoadAxis(t) == road_axis) { + SetBit(adjacent_diagdirs, dir); + } + } + + switch (adjacent_diagdirs) { + case 0: + DrawRailTileSeq(ti, &_crossing_layout, TO_CATENARY, rail, 0, PAL_NONE); + break; + + case (1 << DIAGDIR_NE): + DrawRailTileSeq(ti, &_crossing_layout_SW, TO_CATENARY, rail, 0, PAL_NONE); + break; + + case (1 << DIAGDIR_SE): + DrawRailTileSeq(ti, &_crossing_layout_NW, TO_CATENARY, rail, 0, PAL_NONE); + break; + + case (1 << DIAGDIR_SW): + DrawRailTileSeq(ti, &_crossing_layout_NE, TO_CATENARY, rail, 0, PAL_NONE); + break; + + case (1 << DIAGDIR_NW): + DrawRailTileSeq(ti, &_crossing_layout_SE, TO_CATENARY, rail, 0, PAL_NONE); + break; + + default: + /* Show no sprites */ + break; + } } else if (draw_pbs || tram_rti != nullptr || road_rti->UsesOverlay()) { /* Add another rail overlay, unless there is only the base road sprite. */ PaletteID pal = draw_pbs ? PALETTE_CRASH : PAL_NONE; @@ -2033,7 +2071,16 @@ static TrackStatus GetTileTrackStatus_Road(TileIndex tile, TransportType mode, u if (side != INVALID_DIAGDIR && axis != DiagDirToAxis(side)) break; trackdirbits = TrackBitsToTrackdirBits(AxisToTrackBits(axis)); - if (IsCrossingBarred(tile)) red_signals = trackdirbits; + if (IsCrossingBarred(tile)) { + red_signals = trackdirbits; + auto mask_red_signal_bits_if_crossing_barred = [&](TileIndex t, TrackdirBits mask) { + if (IsLevelCrossingTile(t) && IsCrossingBarred(t)) red_signals &= mask; + }; + /* Check for blocked adjacent crossing to south, keep only southbound red signal trackdirs, allow northbound traffic */ + mask_red_signal_bits_if_crossing_barred(TileAddByDiagDir(tile, AxisToDiagDir(axis)), TRACKDIR_BIT_X_SW | TRACKDIR_BIT_Y_SE); + /* Check for blocked adjacent crossing to north, keep only northbound red signal trackdirs, allow southbound traffic */ + mask_red_signal_bits_if_crossing_barred(TileAddByDiagDir(tile, ReverseDiagDir(AxisToDiagDir(axis))), TRACKDIR_BIT_X_NE | TRACKDIR_BIT_Y_NW); + } break; } diff --git a/src/road_func.h b/src/road_func.h index 9376460575..af756a2570 100644 --- a/src/road_func.h +++ b/src/road_func.h @@ -153,7 +153,8 @@ RoadTypes GetCompanyRoadTypes(CompanyID company, bool introduces = true); RoadTypes GetRoadTypes(bool introduces); RoadTypes AddDateIntroducedRoadTypes(RoadTypes current, Date date); -void UpdateLevelCrossing(TileIndex tile, bool sound = true); +void UpdateLevelCrossing(TileIndex tile, bool sound = true, bool force_bar = false); +void MarkDirtyAdjacentLevelCrossingTiles(TileIndex tile, Axis road_axis); void UpdateCompanyRoadInfrastructure(RoadType rt, Owner o, int count); struct TileInfo; diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 69e92f789f..35558bd58d 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -1004,7 +1004,7 @@ struct RoadDriveEntry { #include "table/roadveh_movement.h" -static bool RoadVehLeaveDepot(RoadVehicle *v, bool first) +bool RoadVehLeaveDepot(RoadVehicle *v, bool first) { /* Don't leave unless v and following wagons are in the depot. */ for (const RoadVehicle *u = v; u != nullptr; u = u->Next()) { diff --git a/src/roadveh_cmd.h b/src/roadveh_cmd.h index 3d71abce73..b168675f98 100644 --- a/src/roadveh_cmd.h +++ b/src/roadveh_cmd.h @@ -14,6 +14,8 @@ #include "engine_type.h" #include "vehicle_type.h" +bool RoadVehLeaveDepot(RoadVehicle *v, bool first); + CommandCost CmdBuildRoadVehicle(DoCommandFlag flags, TileIndex tile, const Engine *e, Vehicle **v); CommandCost CmdTurnRoadVeh(DoCommandFlag flags, VehicleID veh_id); diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index a68a6accc6..6fbc2ac9a2 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -24,6 +24,7 @@ #include "../string_func.h" #include "../date_func.h" #include "../roadveh.h" +#include "../roadveh_cmd.h" #include "../train.h" #include "../station_base.h" #include "../waypoint_base.h" @@ -57,7 +58,6 @@ #include "../ship.h" #include "../water.h" - #include "saveload_internal.h" #include @@ -3155,6 +3155,50 @@ bool AfterLoadGame() } } + /* Road vehicles stopped on multitrack level crossings need teleporting to a depot + * to avoid crashing into the side of the train they're waiting for. */ + if (IsSavegameVersionBefore(SLV_MULTITRACK_LEVEL_CROSSINGS)) { + /* Teleport road vehicles to the nearest depot. */ + for (RoadVehicle *rv : RoadVehicle::Iterate()) { + /* Ignore trailers of articulated vehicles. */ + if (rv->IsArticulatedPart()) continue; + + /* Ignore moving vehicles. */ + if (rv->cur_speed > 0) continue; + + /* Ignore vehicles not on level crossings. */ + TileIndex cur_tile = rv->tile; + if (!IsLevelCrossingTile(cur_tile)) continue; + + TileIndex location; + DestinationID destination; + bool reverse = true; + + /* Try to find a depot with a distance limit of 512 tiles (Manhattan distance). */ + if (rv->FindClosestDepot(&location, &destination, &reverse) && DistanceManhattan(rv->tile, location) < 512u) { + /* Teleport all parts of articulated vehicles. */ + for (RoadVehicle *u = rv; u != nullptr; u = u->Next()) { + u->tile = location; + int x = TileX(location) * TILE_SIZE + TILE_SIZE / 2; + int y = TileY(location) * TILE_SIZE + TILE_SIZE / 2; + u->x_pos = x; + u->y_pos = y; + u->z_pos = GetSlopePixelZ(x, y); + + u->vehstatus |= VS_HIDDEN; + u->state = RVSB_IN_DEPOT; + u->UpdatePosition(); + } + RoadVehLeaveDepot(rv, false); + } + } + + /* Refresh all level crossings to bar adjacent crossing tiles. */ + for (TileIndex tile = 0; tile < MapSize(); tile++) { + if (IsLevelCrossingTile(tile)) UpdateLevelCrossing(tile, false, true); + } + } + /* Compute station catchment areas. This is needed here in case UpdateStationAcceptance is called below. */ Station::RecomputeCatchmentForAll(); diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 19e1089430..16352cc749 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -342,6 +342,7 @@ enum SaveLoadVersion : uint16 { SLV_REPAIR_OBJECT_DOCKING_TILES, ///< 299 PR#9594 v12.0 Fixing issue with docking tiles overlapping objects. SLV_U64_TICK_COUNTER, ///< 300 PR#10035 Make _tick_counter 64bit to avoid wrapping. SLV_LAST_LOADING_TICK, ///< 301 PR#9693 Store tick of last loading for vehicles. + SLV_MULTITRACK_LEVEL_CROSSINGS, ///< 302 PR#9931 Multi-track level crossings. SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/table/road_land.h b/src/table/road_land.h index df38f935d5..0f84ebf183 100644 --- a/src/table/road_land.h +++ b/src/table/road_land.h @@ -53,6 +53,46 @@ static const DrawTileSprites _crossing_layout = { {0, PAL_NONE}, _crossing_layout_ALL }; +static const DrawTileSeqStruct _crossing_layout_SW_ALL[] = { + TILE_SEQ_LINE(6, PAL_NONE, 13, 0, 3, 3) + TILE_SEQ_LINE(8, PAL_NONE, 13, 13, 3, 3) + TILE_SEQ_END() +}; + +static const DrawTileSprites _crossing_layout_SW = { + {0, PAL_NONE}, _crossing_layout_SW_ALL +}; + +static const DrawTileSeqStruct _crossing_layout_NW_ALL[] = { + TILE_SEQ_LINE(2, PAL_NONE, 0, 0, 3, 3) + TILE_SEQ_LINE(6, PAL_NONE, 13, 0, 3, 3) + TILE_SEQ_END() +}; + +static const DrawTileSprites _crossing_layout_NW = { + {0, PAL_NONE}, _crossing_layout_NW_ALL +}; + +static const DrawTileSeqStruct _crossing_layout_NE_ALL[] = { + TILE_SEQ_LINE(2, PAL_NONE, 0, 0, 3, 3) + TILE_SEQ_LINE(4, PAL_NONE, 0, 13, 3, 3) + TILE_SEQ_END() +}; + +static const DrawTileSprites _crossing_layout_NE = { + {0, PAL_NONE}, _crossing_layout_NE_ALL +}; + +static const DrawTileSeqStruct _crossing_layout_SE_ALL[] = { + TILE_SEQ_LINE(4, PAL_NONE, 0, 13, 3, 3) + TILE_SEQ_LINE(8, PAL_NONE, 13, 13, 3, 3) + TILE_SEQ_END() +}; + +static const DrawTileSprites _crossing_layout_SE = { + {0, PAL_NONE}, _crossing_layout_SE_ALL +}; + #undef TILE_SEQ_LINE #undef TILE_SEQ_END diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index bb23f4b34f..e5789d807f 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -1674,29 +1674,88 @@ static bool TrainApproachingCrossing(TileIndex tile) return HasVehicleOnPos(tile_from, &tile, &TrainApproachingCrossingEnum); } +/** + * Check if a level crossing should be barred. + * @param tile The tile to check. + * @return True if the crossing should be barred, else false. + */ +static inline bool CheckLevelCrossing(TileIndex tile) +{ + /* reserved || train on crossing || train approaching crossing */ + return HasCrossingReservation(tile) || HasVehicleOnPos(tile, NULL, &TrainOnTileEnum) || TrainApproachingCrossing(tile); +} /** - * Sets correct crossing state - * @param tile tile to update - * @param sound should we play sound? - * @pre tile is a rail-road crossing + * Sets a level crossing tile to the correct state. + * @param tile Tile to update. + * @param sound Should we play sound? + * @param force_barred Should we set the crossing to barred? + * @pre tile is a rail-road crossing. */ -void UpdateLevelCrossing(TileIndex tile, bool sound) +static void UpdateLevelCrossingTile(TileIndex tile, bool sound, bool force_barred) { assert(IsLevelCrossingTile(tile)); + bool set_barred; - /* reserved || train on crossing || train approaching crossing */ - bool new_state = HasCrossingReservation(tile) || HasVehicleOnPos(tile, nullptr, &TrainOnTileEnum) || TrainApproachingCrossing(tile); + /* We force the crossing to be barred when an adjacent crossing is barred, otherwise let it decide for itself. */ + set_barred = force_barred || CheckLevelCrossing(tile); - if (new_state != IsCrossingBarred(tile)) { - if (new_state && sound) { - if (_settings_client.sound.ambient) SndPlayTileFx(SND_0E_LEVEL_CROSSING, tile); - } - SetCrossingBarred(tile, new_state); + /* The state has changed */ + if (set_barred != IsCrossingBarred(tile)) { + if (set_barred && sound && _settings_client.sound.ambient) SndPlayTileFx(SND_0E_LEVEL_CROSSING, tile); + SetCrossingBarred(tile, set_barred); MarkTileDirtyByTile(tile); } } +/** + * Update a level crossing to barred or open (crossing may include multiple adjacent tiles). + * @param tile Tile which causes the update. + * @param sound Should we play sound? + * @param force_bar Should we force the crossing to be barred? + */ +void UpdateLevelCrossing(TileIndex tile, bool sound, bool force_bar) +{ + if (!IsLevelCrossingTile(tile)) return; + + bool forced_state = force_bar; + + const Axis axis = GetCrossingRoadAxis(tile); + const DiagDirection dir1 = AxisToDiagDir(axis); + const DiagDirection dir2 = ReverseDiagDir(dir1); + + /* Check if an adjacent crossing is barred. */ + for (DiagDirection dir : { dir1, dir2 }) { + for (TileIndex t = tile; !forced_state && t < MapSize() && IsLevelCrossingTile(t) && GetCrossingRoadAxis(t) == axis; t = TileAddByDiagDir(t, dir)) { + forced_state |= CheckLevelCrossing(t); + } + } + + /* Now that we know whether all tiles in this crossing should be barred or open, + * we need to update those tiles. We start with the tile itself, then look along the road axis. */ + UpdateLevelCrossingTile(tile, sound, forced_state); + for (DiagDirection dir : { dir1, dir2 }) { + for (TileIndex t = TileAddByDiagDir(tile, dir); t < MapSize() && IsLevelCrossingTile(t) && GetCrossingRoadAxis(t) == axis; t = TileAddByDiagDir(t, dir)) { + UpdateLevelCrossingTile(t, sound, forced_state); + } + } +} + +/** + * Find adjacent level crossing tiles in this multi-track crossing and mark them dirty. + * @param The tile which causes the update. + */ +void MarkDirtyAdjacentLevelCrossingTiles(TileIndex tile, Axis road_axis) +{ + const DiagDirection dir1 = AxisToDiagDir(road_axis); + const DiagDirection dir2 = ReverseDiagDir(dir1); + for (DiagDirection dir : { dir1, dir2 }) { + const TileIndex t = TileAddByDiagDir(tile, dir); + if (t < MapSize() && IsLevelCrossingTile(t) && GetCrossingRoadAxis(t) == road_axis) { + MarkTileDirtyByTile(t); + } + } +} /** * Bars crossing and plays ding-ding sound if not barred already @@ -1706,9 +1765,8 @@ void UpdateLevelCrossing(TileIndex tile, bool sound) static inline void MaybeBarCrossingWithSound(TileIndex tile) { if (!IsCrossingBarred(tile)) { - BarCrossing(tile); - if (_settings_client.sound.ambient) SndPlayTileFx(SND_0E_LEVEL_CROSSING, tile); - MarkTileDirtyByTile(tile); + SetCrossingReservation(tile, true); + UpdateLevelCrossing(tile, true); } } -- 2.11.4.GIT