Feature: Multi-track level crossings (#9931)

This commit is contained in:
Tyler Trahan 2022-11-01 14:51:23 -06:00 committed by GitHub
parent c65a2799c9
commit c19abebf8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 217 additions and 22 deletions

View File

@ -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;

View File

@ -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);

View File

@ -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;
}

View File

@ -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;

View File

@ -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()) {

View File

@ -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);

View File

@ -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 <signal.h>
@ -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();

View File

@ -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
};

View File

@ -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

View File

@ -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);
}
}