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;