diff --git a/docs/landscape.html b/docs/landscape.html
index 4d7b193ffb..2251696a38 100644
--- a/docs/landscape.html
+++ b/docs/landscape.html
@@ -650,6 +650,7 @@
m5 bit 5: set if crossing lights are on
m7 bits 4..0: owner of the road type 0 (normal road)
m5 bit 4: pbs reservation state
+ m5 bit 1: set if crossing is possibly occupied by a road vehicle
diff --git a/docs/landscape_grid.html b/docs/landscape_grid.html
index 0cab753d00..1346c7cafe 100644
--- a/docs/landscape_grid.html
+++ b/docs/landscape_grid.html
@@ -150,7 +150,7 @@ the array so you can quickly see what is used and what is not.
-inherit- |
XXXX XXXX |
-inherit- |
- XXXX OOOX |
+ XXXX OOPX |
OOXX XOOO |
XXXX XXXX |
diff --git a/src/lang/english.txt b/src/lang/english.txt
index a667c7aa5d..5979a355cc 100644
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -1850,6 +1850,8 @@ STR_CONFIG_SETTING_QUERY_CAPTION :{WHITE}Change s
STR_CONFIG_SETTING_ADJACENT_CROSSINGS :Close adjacent level crossings: {STRING2}
STR_CONFIG_SETTING_ADJACENT_CROSSINGS_HELPTEXT :Closes all adjacent level crossings on parallel tracks whenever one or more is occupied
+STR_CONFIG_SETTING_SAFER_CROSSINGS :Safer level crossings: {STRING2}
+STR_CONFIG_SETTING_SAFER_CROSSINGS_HELPTEXT :Changes to level crossings to improve road vehicle safety
STR_CONFIG_SETTING_PAY_FOR_REPAIR_VEHICLE :Pay for repairing vehicles: {STRING2}
STR_CONFIG_SETTING_PAY_FOR_REPAIR_VEHICLE_HELPTEXT :Pay for repairing vehicles which have broken down
diff --git a/src/pbs.cpp b/src/pbs.cpp
index e4bd4fb400..d27c4ef3a2 100644
--- a/src/pbs.cpp
+++ b/src/pbs.cpp
@@ -125,6 +125,18 @@ bool TryReserveRailTrack(TileIndex tile, Track t, bool trigger_stations)
case MP_ROAD:
if (IsLevelCrossing(tile) && !HasCrossingReservation(tile)) {
+ if (_settings_game.vehicle.safer_crossings) {
+ if (IsCrossingOccupiedByRoadVehicle(tile)) return false;
+ if (_settings_game.vehicle.adjacent_crossings) {
+ const Axis axis = GetCrossingRoadAxis(tile);
+ for (TileIndex t = tile; IsLevelCrossingTile(t) && GetCrossingRoadAxis(t) == axis; t = TileAddByDiagDir(t, AxisToDiagDir(GetCrossingRoadAxis(t)))) {
+ if (IsCrossingOccupiedByRoadVehicle(t)) return false;
+ }
+ for (TileIndex t = tile; IsLevelCrossingTile(t) && GetCrossingRoadAxis(t) == axis; t = TileAddByDiagDir(t, ReverseDiagDir(AxisToDiagDir(GetCrossingRoadAxis(t))))) {
+ if (IsCrossingOccupiedByRoadVehicle(t)) return false;
+ }
+ }
+ }
SetCrossingReservation(tile, true);
UpdateLevelCrossing(tile, false);
return true;
diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp
index cc309c6236..457eadbfec 100644
--- a/src/road_cmd.cpp
+++ b/src/road_cmd.cpp
@@ -1887,6 +1887,12 @@ static VehicleEnterTileStatus VehicleEnter_Road(Vehicle *v, TileIndex tile, int
break;
}
+ case ROAD_TILE_CROSSING: {
+ if (v->type != VEH_ROAD) break;
+ SetCrossingOccupiedByRoadVehicle(tile, true);
+ break;
+ }
+
default: break;
}
return VETSB_CONTINUE;
diff --git a/src/road_func.h b/src/road_func.h
index c4af229d53..5940268400 100644
--- a/src/road_func.h
+++ b/src/road_func.h
@@ -180,5 +180,6 @@ bool ValParamRoadType(const RoadType rt);
RoadTypes GetCompanyRoadtypes(const CompanyID company);
void UpdateLevelCrossing(TileIndex tile, bool sound = true);
+bool IsCrossingOccupiedByRoadVehicle(TileIndex t);
#endif /* ROAD_FUNC_H */
diff --git a/src/road_map.h b/src/road_map.h
index 9849ad474a..970027f03e 100644
--- a/src/road_map.h
+++ b/src/road_map.h
@@ -402,21 +402,27 @@ static inline void SetCrossingBarred(TileIndex t, bool barred)
}
/**
- * Unbar a level crossing.
- * @param t The tile to change.
+ * Check if the level crossing is possibly occupied by road vehicle(s).
+ * @param t The tile to query.
+ * @pre IsLevelCrossing(t)
+ * @return True if the level crossing is marked as occupied. This may return false positives.
*/
-static inline void UnbarCrossing(TileIndex t)
+static inline bool IsCrossingPossiblyOccupiedByRoadVehicle(TileIndex t)
{
- SetCrossingBarred(t, false);
+ assert(IsLevelCrossing(t));
+ return HasBit(_m[t].m5, 1);
}
/**
- * Bar a level crossing.
- * @param t The tile to change.
+ * Set whether the level crossing is occupied by road vehicle(s).
+ * @param t The tile to modify.
+ * @param barred True if the crossing should be marked as occupied, false otherwise.
+ * @pre IsLevelCrossing(t)
*/
-static inline void BarCrossing(TileIndex t)
+static inline void SetCrossingOccupiedByRoadVehicle(TileIndex t, bool occupied)
{
- SetCrossingBarred(t, true);
+ assert(IsLevelCrossing(t));
+ SB(_m[t].m5, 1, 1, occupied ? 1 : 0);
}
/** Check if a road tile has snow/desert. */
diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp
index b89790e8ea..323cc1d61a 100644
--- a/src/saveload/afterload.cpp
+++ b/src/saveload/afterload.cpp
@@ -3386,6 +3386,14 @@ bool AfterLoadGame()
_settings_game.economy.day_length_factor = 1;
}
+ if (SlXvIsFeatureMissing(XSLFI_SAFER_CROSSINGS)) {
+ for (TileIndex t = 0; t < map_size; t++) {
+ if (IsLevelCrossingTile(t)) {
+ SetCrossingOccupiedByRoadVehicle(t, EnsureNoRoadVehicleOnGround(t).Failed());
+ }
+ }
+ }
+
/* Road stops is 'only' updating some caches */
AfterLoadRoadStops();
AfterLoadLabelMaps();
diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp
index 9a78eb7863..c2a4b19997 100644
--- a/src/saveload/extended_ver_sl.cpp
+++ b/src/saveload/extended_ver_sl.cpp
@@ -51,6 +51,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = {
{ XSLFI_TRACE_RESTRICT_OWNER, XSCF_NULL, 1, 1, "tracerestrict_owner", NULL, NULL, NULL },
{ XSLFI_PROG_SIGS, XSCF_NULL, 1, 1, "programmable_signals", NULL, NULL, "SPRG" },
{ XSLFI_ADJACENT_CROSSINGS, XSCF_NULL, 1, 1, "adjacent_crossings", NULL, NULL, NULL },
+ { XSLFI_SAFER_CROSSINGS, XSCF_NULL, 1, 1, "safer_crossings", NULL, NULL, NULL },
{ XSLFI_DEPARTURE_BOARDS, XSCF_IGNORABLE_UNKNOWN, 1, 1, "departure_boards", NULL, NULL, NULL },
{ XSLFI_TIMETABLES_START_TICKS, XSCF_NULL, 2, 2, "timetable_start_ticks", NULL, NULL, NULL },
{ XSLFI_TOWN_CARGO_ADJ, XSCF_IGNORABLE_UNKNOWN, 2, 2, "town_cargo_adj", NULL, NULL, NULL },
diff --git a/src/saveload/extended_ver_sl.h b/src/saveload/extended_ver_sl.h
index 835410010d..c5977dd5c3 100644
--- a/src/saveload/extended_ver_sl.h
+++ b/src/saveload/extended_ver_sl.h
@@ -25,6 +25,7 @@ enum SlXvFeatureIndex {
XSLFI_TRACE_RESTRICT_OWNER, ///< Trace restrict: train owner test
XSLFI_PROG_SIGS, ///< programmable signals patch
XSLFI_ADJACENT_CROSSINGS, ///< Adjacent level crossings closure patch
+ XSLFI_SAFER_CROSSINGS, ///< Safer level crossings
XSLFI_DEPARTURE_BOARDS, ///< Departure boards patch, in ticks mode
XSLFI_TIMETABLES_START_TICKS, ///< Timetable start time is in ticks, instead of days (from departure boards patch)
XSLFI_TOWN_CARGO_ADJ, ///< Town cargo adjustment patch
diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp
index ea0295f20e..df22e1895a 100644
--- a/src/settings_gui.cpp
+++ b/src/settings_gui.cpp
@@ -1694,6 +1694,7 @@ static SettingsContainer &GetSettingsTree()
vehicles->Add(new SettingEntry("order.no_servicing_if_no_breakdowns"));
vehicles->Add(new SettingEntry("order.serviceathelipad"));
vehicles->Add(new SettingEntry("vehicle.adjacent_crossings"));
+ vehicles->Add(new SettingEntry("vehicle.safer_crossings"));
}
SettingsPage *limitations = main->Add(new SettingsPage(STR_CONFIG_SETTING_LIMITATIONS));
diff --git a/src/settings_type.h b/src/settings_type.h
index e825b1e2fb..b15416ad2d 100644
--- a/src/settings_type.h
+++ b/src/settings_type.h
@@ -515,6 +515,7 @@ struct VehicleSettings {
byte road_side; ///< the side of the road vehicles drive on
uint8 plane_crashes; ///< number of plane crashes, 0 = none, 1 = reduced, 2 = normal
bool adjacent_crossings; ///< enable closing of adjacent level crossings
+ bool safer_crossings; ///< enable safer level crossings
bool improved_breakdowns; ///< different types, chances and severities of breakdowns
bool pay_for_repair; ///< pay for repairing vehicle
uint8 repair_cost; ///< cost of repairing vehicle
diff --git a/src/signal.cpp b/src/signal.cpp
index 667a0a2494..e846ffe373 100644
--- a/src/signal.cpp
+++ b/src/signal.cpp
@@ -400,6 +400,7 @@ static SigInfo ExploreSegment(Owner owner)
if (DiagDirToAxis(enterdir) == GetCrossingRoadAxis(tile)) continue; // different axis
if (!(info.flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) info.flags |= SF_TRAIN;
+ if (_settings_game.vehicle.safer_crossings) info.flags |= SF_PBS;
tile += TileOffsByDiagDir(exitdir);
break;
diff --git a/src/table/settings.ini b/src/table/settings.ini
index 8b00cdc9ab..fe969b5064 100644
--- a/src/table/settings.ini
+++ b/src/table/settings.ini
@@ -3057,6 +3057,15 @@ strhelp = STR_CONFIG_SETTING_ADJACENT_CROSSINGS_HELPTEXT
cat = SC_BASIC
patxname = ""adjacent_crossings.vehicle.adjacent_crossings""
+[SDT_BOOL]
+base = GameSettings
+var = vehicle.safer_crossings
+def = false
+str = STR_CONFIG_SETTING_SAFER_CROSSINGS
+strhelp = STR_CONFIG_SETTING_SAFER_CROSSINGS_HELPTEXT
+cat = SC_BASIC
+patxname = ""safer_crossings.vehicle.safer_crossings""
+
;***************************************************************************
; Unsaved setting variables.
diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp
index dece5837cd..b3be588874 100644
--- a/src/train_cmd.cpp
+++ b/src/train_cmd.cpp
@@ -1866,6 +1866,20 @@ void UpdateLevelCrossing(TileIndex tile, bool sound)
}
}
+/**
+ * Check if the level crossing is occupied by road vehicle(s).
+ * @param t The tile to query.
+ * @pre IsLevelCrossing(t)
+ * @return True if the level crossing is marked as occupied.
+ */
+bool IsCrossingOccupiedByRoadVehicle(TileIndex t)
+{
+ if (!IsCrossingPossiblyOccupiedByRoadVehicle(t)) return false;
+ const bool occupied = EnsureNoRoadVehicleOnGround(t).Failed();
+ SetCrossingOccupiedByRoadVehicle(t, occupied);
+ return occupied;
+}
+
/**
* Bars crossing and plays ding-ding sound if not barred already
diff --git a/src/vehicle.cpp b/src/vehicle.cpp
index edd7087492..efeda3be8a 100644
--- a/src/vehicle.cpp
+++ b/src/vehicle.cpp
@@ -575,6 +575,40 @@ CommandCost EnsureNoVehicleOnGround(TileIndex tile)
return CommandCost();
}
+/**
+ * Callback that returns 'real' vehicles lower or at height \c *(int*)data, for road vehicles.
+ * @param v Vehicle to examine.
+ * @param data Pointer to height data.
+ * @return \a v if conditions are met, else \c NULL.
+ */
+static Vehicle *EnsureNoRoadVehicleProcZ(Vehicle *v, void *data)
+{
+ int z = *(int*)data;
+
+ if (v->type != VEH_ROAD) return NULL;
+ if (v->z_pos > z) return NULL;
+
+ return v;
+}
+
+/**
+ * Ensure there is no road vehicle at the ground at the given position.
+ * @param tile Position to examine.
+ * @return Succeeded command (ground is free) or failed command (a vehicle is found).
+ */
+CommandCost EnsureNoRoadVehicleOnGround(TileIndex tile)
+{
+ int z = GetTileMaxPixelZ(tile);
+
+ /* Value v is not safe in MP games, however, it is used to generate a local
+ * error message only (which may be different for different machines).
+ * Such a message does not affect MP synchronisation.
+ */
+ Vehicle *v = VehicleFromPos(tile, &z, &EnsureNoRoadVehicleProcZ, true);
+ if (v != NULL) return_cmd_error(STR_ERROR_ROAD_VEHICLE_IN_THE_WAY);
+ return CommandCost();
+}
+
/** Procedure called for every vehicle found in tunnel/bridge in the hash map */
static Vehicle *GetVehicleTunnelBridgeProc(Vehicle *v, void *data)
{
diff --git a/src/vehicle_func.h b/src/vehicle_func.h
index a06449d62b..85f620dd97 100644
--- a/src/vehicle_func.h
+++ b/src/vehicle_func.h
@@ -165,6 +165,7 @@ static inline uint32 GetCmdSendToDepot(const BaseVehicle *v)
}
CommandCost EnsureNoVehicleOnGround(TileIndex tile);
+CommandCost EnsureNoRoadVehicleOnGround(TileIndex tile);
CommandCost EnsureNoTrainOnTrackBits(TileIndex tile, TrackBits track_bits);
extern VehicleID _new_vehicle_id;