diff --git a/docs/landscape.html b/docs/landscape.html
index 9ec6230520..bcad6e0f7e 100644
--- a/docs/landscape.html
+++ b/docs/landscape.html
@@ -892,6 +892,18 @@
m2: index into the array of stations
m3 bits 7..4: persistent random data for railway stations/waypoints and airports)
m3 bits 7..4: owner of tram tracks (road stop)
+ m3 bits 1..0: bits to disallow vehicles to go a specific direction (drive-through road stop)
+
+
+ bit 0: |
+ set = disallow driving in south-west or south-east direction |
+
+
+ bit 1: |
+ set = disallow driving in north-west or north-east direction |
+
+
+
m4: custom station id; 0 means standard graphics
m4: Roadtype for road stops
m5: graphics index (range from 0..255 for each station type):
diff --git a/docs/landscape_grid.html b/docs/landscape_grid.html
index 1b9b1c02a4..4fa63eb215 100644
--- a/docs/landscape_grid.html
+++ b/docs/landscape_grid.html
@@ -238,7 +238,7 @@ the array so you can quickly see what is used and what is not.
-inherit- |
OXXX XXXX |
-inherit- |
- XXXX OOOO |
+ XXXX OOPP |
OOXX XXXX |
~~~~ ~XXX |
OOXX XOOO |
diff --git a/src/pathfinder/follow_track.hpp b/src/pathfinder/follow_track.hpp
index bd20fffbbe..4fbd295d03 100644
--- a/src/pathfinder/follow_track.hpp
+++ b/src/pathfinder/follow_track.hpp
@@ -92,7 +92,7 @@ struct CFollowTrackT
inline static TransportType TT() { return Ttr_type_; }
inline static bool IsWaterTT() { return TT() == TRANSPORT_WATER; }
inline static bool IsRailTT() { return TT() == TRANSPORT_RAIL; }
- inline bool IsTram() { return IsRoadTT() && RoadTypeIsTram(RoadVehicle::From(m_veh)->roadtype); }
+ inline bool IsTram() const { return IsRoadTT() && RoadTypeIsTram(RoadVehicle::From(m_veh)->roadtype); }
inline static bool IsRoadTT() { return TT() == TRANSPORT_ROAD; }
inline static bool Allow90degTurns() { return T90deg_turns_allowed_; }
inline static bool DoTrackMasking() { return Tmask_reserved_tracks; }
diff --git a/src/pathfinder/npf/npf.cpp b/src/pathfinder/npf/npf.cpp
index 7cabf44bd9..c366545c81 100644
--- a/src/pathfinder/npf/npf.cpp
+++ b/src/pathfinder/npf/npf.cpp
@@ -369,7 +369,11 @@ static int32 NPFRoadPathCost(AyStar *as, AyStarNode *current, OpenListNode *pare
/* When we're the first road stop in a 'queue' of them we increase
* cost based on the fill percentage of the whole queue. */
const RoadStop::Entry *entry = rs->GetEntry(dir);
- cost += entry->GetOccupied() * _settings_game.pf.npf.npf_road_dt_occupied_penalty / entry->GetLength();
+ if (GetDriveThroughStopDisallowedRoadDirections(tile) != DRD_NONE) {
+ cost += (entry->GetOccupied() + rs->GetEntry(ReverseDiagDir(dir))->GetOccupied()) * _settings_game.pf.npf.npf_road_dt_occupied_penalty / (2 * entry->GetLength());
+ } else {
+ cost += entry->GetOccupied() * _settings_game.pf.npf.npf_road_dt_occupied_penalty / entry->GetLength();
+ }
}
} else {
/* Increase cost for filled road stops */
diff --git a/src/pathfinder/yapf/yapf_road.cpp b/src/pathfinder/yapf/yapf_road.cpp
index 8c1a986085..033ac5f3b2 100644
--- a/src/pathfinder/yapf/yapf_road.cpp
+++ b/src/pathfinder/yapf/yapf_road.cpp
@@ -68,7 +68,7 @@ protected:
}
/** return one tile cost */
- inline int OneTileCost(TileIndex tile, Trackdir trackdir)
+ inline int OneTileCost(TileIndex tile, Trackdir trackdir, const TrackFollower *tf)
{
int cost = 0;
@@ -101,7 +101,11 @@ protected:
/* When we're the first road stop in a 'queue' of them we increase
* cost based on the fill percentage of the whole queue. */
const RoadStop::Entry *entry = rs->GetEntry(dir);
- cost += entry->GetOccupied() * Yapf().PfGetSettings().road_stop_occupied_penalty / entry->GetLength();
+ if (GetDriveThroughStopDisallowedRoadDirections(tile) != DRD_NONE && !tf->IsTram()) {
+ cost += (entry->GetOccupied() + rs->GetEntry(ReverseDiagDir(dir))->GetOccupied()) * Yapf().PfGetSettings().road_stop_occupied_penalty / (2 * entry->GetLength());
+ } else {
+ cost += entry->GetOccupied() * Yapf().PfGetSettings().road_stop_occupied_penalty / entry->GetLength();
+ }
}
if (predicted_occupied) {
@@ -153,7 +157,7 @@ public:
for (;;) {
/* base tile cost depending on distance between edges */
- segment_cost += Yapf().OneTileCost(tile, trackdir);
+ segment_cost += Yapf().OneTileCost(tile, trackdir, tf);
const RoadVehicle *v = Yapf().GetVehicle();
/* we have reached the vehicle's destination - segment should end here to avoid target skipping */
diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp
index 4ca894b75c..6b72dfeac5 100644
--- a/src/road_cmd.cpp
+++ b/src/road_cmd.cpp
@@ -37,6 +37,7 @@
#include "genworld.h"
#include "company_gui.h"
#include "road_func.h"
+#include "roadstop_base.h"
#include "table/strings.h"
#include "table/roadtypes.h"
@@ -871,7 +872,38 @@ CommandCost CmdBuildRoad(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
}
case MP_STATION: {
- if ((GetAnyRoadBits(tile, rtt) & pieces) == pieces) return_cmd_error(STR_ERROR_ALREADY_BUILT);
+ if ((GetAnyRoadBits(tile, rtt) & pieces) == pieces) {
+ if (toggle_drd != DRD_NONE && rtt == RTT_ROAD && IsDriveThroughStopTile(tile)) {
+ Owner owner = GetRoadOwner(tile, rtt);
+ if (owner != OWNER_NONE) {
+ CommandCost ret = CheckOwnership(owner, tile);
+ if (ret.Failed()) return ret;
+ }
+
+ DisallowedRoadDirections dis_existing = GetDriveThroughStopDisallowedRoadDirections(tile);
+ DisallowedRoadDirections dis_new = dis_existing ^ toggle_drd;
+
+ /* We allow removing disallowed directions to break up
+ * deadlocks, but adding them can break articulated
+ * vehicles. As such, only when less is disallowed,
+ * i.e. bits are removed, we skip the vehicle check. */
+ if (CountBits(dis_existing) <= CountBits(dis_new)) {
+ CommandCost ret = EnsureNoVehicleOnGround(tile);
+ if (ret.Failed()) return ret;
+ }
+
+ if (flags & DC_EXEC) {
+ RoadStop *rs = RoadStop::GetByTile(tile, GetRoadStopType(tile));
+ rs->ChangeDriveThroughDisallowedRoadDirections(dis_new);
+ MarkTileDirtyByTile(tile);
+ NotifyRoadLayoutChanged(CountBits(dis_existing) > CountBits(dis_new));
+ }
+ return CommandCost();
+ }
+ return_cmd_error(STR_ERROR_ALREADY_BUILT);
+ } else {
+ toggle_drd = DRD_NONE;
+ }
if (!IsDriveThroughStopTile(tile)) goto do_clear;
RoadBits curbits = AxisToRoadBits(DiagDirToAxis(GetRoadStopDir(tile)));
diff --git a/src/roadstop.cpp b/src/roadstop.cpp
index 924280bc38..7991e5ea17 100644
--- a/src/roadstop.cpp
+++ b/src/roadstop.cpp
@@ -209,6 +209,103 @@ void RoadStop::ClearDriveThrough()
this->west = nullptr;
}
+/**
+ * Change disallowed road directions of this stop; update other neighbouring stops
+ * if needed. Also update the length etc.
+ */
+void RoadStop::ChangeDriveThroughDisallowedRoadDirections(DisallowedRoadDirections drd)
+{
+ assert(this->east != nullptr && this->west != nullptr);
+
+ RoadStopType rst = GetRoadStopType(this->xy);
+ DiagDirection dir = GetRoadStopDir(this->xy);
+ /* Use absolute so we always go towards the northern tile */
+ TileIndexDiff offset = abs(TileOffsByDiagDir(dir));
+
+ /* Information about the tile north of us */
+ TileIndex north_tile = this->xy - offset;
+ bool north = IsDriveThroughRoadStopContinuation(this->xy, north_tile);
+ RoadStop *rs_north = north ? RoadStop::GetByTile(north_tile, rst) : nullptr;
+
+ /* Information about the tile south of us */
+ TileIndex south_tile = this->xy + offset;
+ bool south = IsDriveThroughRoadStopContinuation(this->xy, south_tile);
+ RoadStop *rs_south = south ? RoadStop::GetByTile(south_tile, rst) : nullptr;
+
+ /* Must only be changed after we determined which neighbours are
+ * part of our little entry 'queue' */
+ SetDriveThroughStopDisallowedRoadDirections(this->xy, drd);
+
+ if (north) {
+ /* There is a tile to the north, so we can't clear ourselves. */
+ if (south) {
+ /* There are more southern tiles too, they must be split;
+ * first make the new southern 'base' */
+ SetBit(rs_south->status, RSSFB_BASE_ENTRY);
+ rs_south->east = new Entry();
+ rs_south->west = new Entry();
+
+ /* Keep track of the base because we need it later on */
+ RoadStop *rs_south_base = rs_south;
+ TileIndex base_tile = south_tile;
+
+ /* Make all (even more) southern stops part of the new entry queue */
+ for (south_tile += offset; IsDriveThroughRoadStopContinuation(base_tile, south_tile); south_tile += offset) {
+ rs_south = RoadStop::GetByTile(south_tile, rst);
+ rs_south->east = rs_south_base->east;
+ rs_south->west = rs_south_base->west;
+ }
+
+ /* We have to rebuild the entries because we cannot easily determine
+ * how full each part is. So instead of keeping and maintaining a list
+ * of vehicles and using that to 'rebuild' the occupied state we just
+ * rebuild it from scratch as that removes lots of maintenance code
+ * for the vehicle list and it's faster in real games as long as you
+ * do not keep split and merge road stop every tick by the millions. */
+ rs_south_base->east->Rebuild(rs_south_base);
+ rs_south_base->west->Rebuild(rs_south_base);
+ }
+
+ /* Find the other end; the northern most tile */
+ TileIndex base_tile = north_tile;
+ for (north_tile -= offset; IsDriveThroughRoadStopContinuation(base_tile, north_tile); north_tile -= offset) {
+ rs_north = RoadStop::GetByTile(north_tile, rst);
+ }
+
+ assert(HasBit(rs_north->status, RSSFB_BASE_ENTRY));
+ rs_north->east->Rebuild(rs_north);
+ rs_north->west->Rebuild(rs_north);
+ } else if (south) {
+ /* There is only something to the south. Hand over the base entry */
+ SetBit(rs_south->status, RSSFB_BASE_ENTRY);
+ rs_south->east->Rebuild(rs_south);
+ rs_south->west->Rebuild(rs_south);
+ } else {
+ /* We were the last */
+ delete this->east;
+ delete this->west;
+ }
+
+ /* Make sure we don't get used for something 'incorrect' */
+ ClrBit(this->status, RSSFB_BASE_ENTRY);
+ this->east = nullptr;
+ this->west = nullptr;
+
+ this->MakeDriveThrough();
+
+ /* Find the other end; the northern most tile */
+ TileIndex self_north = this->xy - offset;
+ RoadStop *rs_self = RoadStop::GetByTile(this->xy, rst);
+ for (; IsDriveThroughRoadStopContinuation(this->xy, self_north); self_north -= offset) {
+ rs_self = RoadStop::GetByTile(self_north, rst);
+ }
+
+ /* Update occupancy of stop covering this tile */
+ assert(HasBit(rs_self->status, RSSFB_BASE_ENTRY));
+ rs_self->east->Rebuild(rs_self);
+ rs_self->west->Rebuild(rs_self);
+}
+
/**
* Leave the road stop
* @param rv the vehicle that leaves the stop
@@ -221,7 +318,7 @@ void RoadStop::Leave(RoadVehicle *rv)
this->SetEntranceBusy(false);
} else {
/* Otherwise just leave the drive through's entry cache. */
- this->GetEntry(DirToDiagDir(rv->direction))->Leave(rv);
+ this->GetEntry(rv)->Leave(rv);
}
}
@@ -249,7 +346,7 @@ bool RoadStop::Enter(RoadVehicle *rv)
}
/* Vehicles entering a drive-through stop from the 'normal' side use first bay (bay 0). */
- this->GetEntry(DirToDiagDir(rv->direction))->Enter(rv);
+ this->GetEntry(rv)->Enter(rv);
/* Indicate a drive-through stop */
SetBit(rv->state, RVS_IN_DT_ROAD_STOP);
@@ -308,7 +405,8 @@ void RoadStop::Entry::Enter(const RoadVehicle *rv)
GetStationIndex(next) == GetStationIndex(rs) &&
GetStationType(next) == GetStationType(rs) &&
GetRoadStopDir(next) == GetRoadStopDir(rs) &&
- IsDriveThroughStopTile(next);
+ IsDriveThroughStopTile(next) &&
+ GetDriveThroughStopDisallowedRoadDirections(next) == GetDriveThroughStopDisallowedRoadDirections(rs);
}
typedef std::list RVList; ///< A list of road vehicles
@@ -329,7 +427,9 @@ Vehicle *FindVehiclesInRoadStop(Vehicle *v, void *data)
{
RoadStopEntryRebuilderHelper *rserh = (RoadStopEntryRebuilderHelper*)data;
/* Not a RV or not in the right direction or crashed :( */
- if (DirToDiagDir(v->direction) != rserh->dir || !v->IsPrimaryVehicle() || (v->vehstatus & VS_CRASHED) != 0) return nullptr;
+ DiagDirection diag_dir = DirToDiagDir(v->direction);
+ if (RoadVehicle::From(v)->overtaking != 0) diag_dir = ReverseDiagDir(diag_dir);
+ if (diag_dir != rserh->dir || !v->IsPrimaryVehicle() || (v->vehstatus & VS_CRASHED) != 0) return nullptr;
RoadVehicle *rv = RoadVehicle::From(v);
/* Don't add ones not in a road stop */
diff --git a/src/roadstop_base.h b/src/roadstop_base.h
index 768b54282d..ba8346af1e 100644
--- a/src/roadstop_base.h
+++ b/src/roadstop_base.h
@@ -14,6 +14,8 @@
#include "core/pool_type.hpp"
#include "core/bitmath_func.hpp"
#include "vehicle_type.h"
+#include "roadveh.h"
+#include "road_map.h"
typedef Pool RoadStopPool;
extern RoadStopPool _roadstop_pool;
@@ -134,8 +136,19 @@ struct RoadStop : RoadStopPool::PoolItem<&_roadstop_pool> {
return HasBit((int)dir, 1) ? this->west : this->east;
}
+ inline const Entry *GetEntry(const RoadVehicle *rv) const {
+ DiagDirection diag_dir = DirToDiagDir(rv->direction);
+ return this->GetEntry(rv->overtaking != 0 ? ReverseDiagDir(diag_dir) : diag_dir);
+ }
+
+ inline Entry *GetEntry(const RoadVehicle *rv) {
+ DiagDirection diag_dir = DirToDiagDir(rv->direction);
+ return this->GetEntry(rv->overtaking != 0 ? ReverseDiagDir(diag_dir) : diag_dir);
+ }
+
void MakeDriveThrough();
void ClearDriveThrough();
+ void ChangeDriveThroughDisallowedRoadDirections(DisallowedRoadDirections drd);
void Leave(RoadVehicle *rv);
bool Enter(RoadVehicle *rv);
diff --git a/src/roadveh.h b/src/roadveh.h
index edc3de36bf..06f202f0f4 100644
--- a/src/roadveh.h
+++ b/src/roadveh.h
@@ -171,12 +171,7 @@ struct RoadVehicle FINAL : public GroundVehicle {
return RV_OVERTAKE_TIMEOUT + (this->gcache.cached_total_length / 2) - (VEHICLE_LENGTH / 2);
}
- inline void SetRoadVehicleOvertaking(byte overtaking)
- {
- for (RoadVehicle *u = this; u != nullptr; u = u->Next()) {
- u->overtaking = overtaking;
- }
- }
+ void SetRoadVehicleOvertaking(byte overtaking);
protected: // These functions should not be called outside acceleration code.
diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp
index e6fa370057..596cc4af6e 100644
--- a/src/roadveh_cmd.cpp
+++ b/src/roadveh_cmd.cpp
@@ -373,6 +373,13 @@ bool RoadVehicle::FindClosestDepot(TileIndex *location, DestinationID *destinati
return true;
}
+static bool IsOneWayRoadTile(TileIndex tile)
+{
+ if (IsNormalRoadTile(tile) && GetDisallowedRoadDirections(tile) != DRD_NONE) return true;
+ if (IsDriveThroughStopTile(tile) && GetDriveThroughStopDisallowedRoadDirections(tile) != DRD_NONE) return true;
+ return false;
+}
+
/**
* Turn a roadvehicle around.
* @param tile unused
@@ -401,7 +408,7 @@ CommandCost CmdTurnRoadVeh(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3
return CMD_ERROR;
}
- if (IsNormalRoadTile(v->tile) && GetDisallowedRoadDirections(v->tile) != DRD_NONE) return CMD_ERROR;
+ if (IsOneWayRoadTile(v->tile)) return CMD_ERROR;
if (IsTileType(v->tile, MP_TUNNELBRIDGE) && DirToDiagDir(v->direction) == GetTunnelBridgeDirection(v->tile)) return CMD_ERROR;
@@ -848,13 +855,7 @@ static Vehicle *EnumFindVehBlockingOvertake(Vehicle *v, void *data)
return v;
}
-/**
- * Check if overtaking is possible on a piece of track
- *
- * @param od Information about the tile and the involved vehicles
- * @return true if we have to abort overtaking
- */
-static bool CheckRoadBlockedForOvertaking(OvertakeData *od)
+static bool CheckRoadInfraUnsuitableForOvertaking(OvertakeData *od)
{
if (!HasTileAnyRoadType(od->tile, od->v->compatible_roadtypes)) return true;
TrackStatus ts = GetTileTrackStatus(od->tile, TRANSPORT_ROAD, GetRoadTramType(od->v->roadtype));
@@ -865,22 +866,45 @@ static bool CheckRoadBlockedForOvertaking(OvertakeData *od)
/* Track does not continue along overtaking direction || track has junction || levelcrossing is barred */
if (!HasBit(trackdirbits, od->trackdir) || (trackbits & ~TRACK_BIT_CROSS) || (red_signals != TRACKDIR_BIT_NONE)) return true;
+ return false;
+}
+
+/**
+ * Check if overtaking is possible on a piece of track
+ *
+ * @param od Information about the tile and the involved vehicles
+ * @return true if we have to abort overtaking
+ */
+static bool CheckRoadBlockedForOvertaking(OvertakeData *od)
+{
/* Are there more vehicles on the tile except the two vehicles involved in overtaking */
return HasVehicleOnPos(od->tile, VEH_ROAD, od, EnumFindVehBlockingOvertake);
}
+/**
+ * Check if overtaking is possible on a piece of track
+ *
+ * @param od Information about the tile and the involved vehicles
+ * @return true if we have to abort overtaking
+ */
+static bool IsNonOvertakingStationTile(TileIndex tile, DiagDirection diag_dir)
+{
+ if (!IsTileType(tile, MP_STATION)) return false;
+ if (!IsDriveThroughStopTile(tile)) return true;
+ const DisallowedRoadDirections diagdir_to_drd[DIAGDIR_END] = { DRD_NORTHBOUND, DRD_NORTHBOUND, DRD_SOUTHBOUND, DRD_SOUTHBOUND };
+ return GetDriveThroughStopDisallowedRoadDirections(tile) != diagdir_to_drd[diag_dir];
+}
+
static void RoadVehCheckOvertake(RoadVehicle *v, RoadVehicle *u)
{
- OvertakeData od;
-
- od.v = v;
- od.u = u;
-
/* Trams can't overtake other trams */
if (RoadTypeIsTram(v->roadtype)) return;
+ /* Vehicles are not driving in same direction || direction is not a diagonal direction */
+ if (v->direction != u->direction || !(v->direction & 1)) return;
+
/* Don't overtake in stations */
- if (IsTileType(u->tile, MP_STATION)) return;
+ if (IsNonOvertakingStationTile(u->tile, DirToDiagDir(u->direction))) return;
/* If not permitted, articulated road vehicles can't overtake anything. */
if (!_settings_game.vehicle.roadveh_articulated_overtaking && v->HasArticulatedPart()) return;
@@ -888,21 +912,21 @@ static void RoadVehCheckOvertake(RoadVehicle *v, RoadVehicle *u)
/* Don't overtake if the vehicle is broken or about to break down */
if (v->breakdown_ctr != 0) return;
- /* Vehicles are not driving in same direction || direction is not a diagonal direction */
- if (v->direction != u->direction || !(v->direction & 1)) return;
-
/* Vehicles chain is too long to overtake */
if (v->GetOvertakingCounterThreshold() > 255) return;
for (RoadVehicle *w = v; w != nullptr; w = w->Next()) {
/* Don't overtake in stations */
- if (IsTileType(w->tile, MP_STATION)) return;
+ if (IsNonOvertakingStationTile(w->tile, DirToDiagDir(w->direction))) return;
/* Don't overtake if vehicle parts not all in same direction */
if (w->direction != v->direction) return;
/* Check if vehicle is in a road stop, depot, tunnel or bridge or not on a straight road */
- if (w->state >= RVSB_IN_ROAD_STOP || !IsStraightRoadTrackdir((Trackdir)(w->state & RVSB_TRACKDIR_MASK))) return;
+ if ((w->state >= RVSB_IN_ROAD_STOP || !IsStraightRoadTrackdir((Trackdir)(w->state & RVSB_TRACKDIR_MASK))) &&
+ !IsInsideMM(w->state, RVSB_IN_DT_ROAD_STOP, RVSB_IN_DT_ROAD_STOP_END)) {
+ return;
+ }
}
/* Can't overtake a vehicle that is moving faster than us. If the vehicle in front is
@@ -915,6 +939,9 @@ static void RoadVehCheckOvertake(RoadVehicle *v, RoadVehicle *u)
return;
}
+ OvertakeData od;
+ od.v = v;
+ od.u = u;
od.trackdir = DiagDirToDiagTrackdir(DirToDiagDir(v->direction));
/* Are the current and the next tile suitable for overtaking?
@@ -928,12 +955,22 @@ static void RoadVehCheckOvertake(RoadVehicle *v, RoadVehicle *u)
TileIndexDiff check_tile_diff = TileOffsByDiagDir(DirToDiagDir(v->direction));
for (; tile_count != 0; tile_count--, check_tile += check_tile_diff) {
od.tile = check_tile;
+ if (CheckRoadInfraUnsuitableForOvertaking(&od)) return;
+ if (IsDriveThroughStopTile(check_tile) && GetDriveThroughStopDisallowedRoadDirections(check_tile) != DRD_NONE) {
+ const RoadStop *rs = RoadStop::GetByTile(check_tile, GetRoadStopType(check_tile));
+ DiagDirection dir = DirToDiagDir(v->direction);
+ const RoadStop::Entry *entry = rs->GetEntry(dir);
+ const RoadStop::Entry *opposite_entry = rs->GetEntry(ReverseDiagDir(dir));
+ if (entry->GetOccupied() < opposite_entry->GetOccupied()) return;
+ break;
+ }
if (CheckRoadBlockedForOvertaking(&od)) return;
}
tile_count = v->gcache.cached_total_length / TILE_SIZE;
check_tile = v->tile - check_tile_diff;
for (; tile_count != 0; tile_count--, check_tile -= check_tile_diff) {
od.tile = check_tile;
+ if (CheckRoadInfraUnsuitableForOvertaking(&od)) return;
if (CheckRoadBlockedForOvertaking(&od)) return;
}
@@ -1239,14 +1276,37 @@ static bool CanBuildTramTrackOnTile(CompanyID c, TileIndex t, RoadType rt, RoadB
return ret.Succeeded();
}
+static bool IsRoadVehicleOnOtherSideOfRoad(const RoadVehicle *v)
+{
+ bool is_right;
+ switch (DirToDiagDir(v->direction)) {
+ case DIAGDIR_NE:
+ is_right = ((TILE_UNIT_MASK & v->y_pos) == 9);
+ break;
+ case DIAGDIR_SE:
+ is_right = ((TILE_UNIT_MASK & v->x_pos) == 9);
+ break;
+ case DIAGDIR_SW:
+ is_right = ((TILE_UNIT_MASK & v->y_pos) == 5);
+ break;
+ case DIAGDIR_NW:
+ is_right = ((TILE_UNIT_MASK & v->x_pos) == 5);
+ break;
+ default:
+ NOT_REACHED();
+ }
+
+ return is_right != (bool) _settings_game.vehicle.road_side;
+}
+
bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev)
{
SCOPE_INFO_FMT([&], "IndividualRoadVehicleController: %s, %s", scope_dumper().VehicleInfo(v), scope_dumper().VehicleInfo(prev));
if (v->overtaking != 0 && v->IsFrontEngine()) {
- if (IsTileType(v->tile, MP_STATION)) {
+ if (IsNonOvertakingStationTile(v->tile, DirToDiagDir(v->direction))) {
/* Force us to be not overtaking! */
v->SetRoadVehicleOvertaking(0);
- } else if (v->HasArticulatedPart() && (v->state >= RVSB_IN_ROAD_STOP || !IsStraightRoadTrackdir((Trackdir)v->state))) {
+ } else if (v->HasArticulatedPart() && (v->state >= RVSB_IN_ROAD_STOP || !IsStraightRoadTrackdir((Trackdir)v->state)) && !IsInsideMM(v->state, RVSB_IN_DT_ROAD_STOP, RVSB_IN_DT_ROAD_STOP_END)) {
/* Articulated RVs may not overtake on corners */
v->SetRoadVehicleOvertaking(0);
} else if (v->HasArticulatedPart() && IsBridgeTile(v->tile) && (IsRoadCustomBridgeHeadTile(v->tile) || IsRoadCustomBridgeHeadTile(GetOtherBridgeEnd(v->tile)))) {
@@ -1258,6 +1318,9 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev)
* overtake if we are on straight roads */
if (v->overtaking_ctr >= v->GetOvertakingCounterThreshold() && v->state < RVSB_IN_ROAD_STOP && IsStraightRoadTrackdir((Trackdir)v->state)) {
v->SetRoadVehicleOvertaking(0);
+ } else if (v->overtaking_ctr == 0) {
+ /* prevent overflow issues */
+ v->overtaking_ctr = 255;
}
}
}
@@ -1396,7 +1459,7 @@ again:
v->cur_speed = 0;
return false;
}
- } else if (IsNormalRoadTile(v->tile) && GetDisallowedRoadDirections(v->tile) != DRD_NONE) {
+ } else if (IsOneWayRoadTile(v->tile)) {
v->cur_speed = 0;
return false;
} else {
@@ -1572,14 +1635,17 @@ again:
if (u != nullptr) {
u = u->First();
/* There is a vehicle in front overtake it if possible */
+ byte old_overtaking = v->overtaking;
if (v->overtaking == 0) RoadVehCheckOvertake(v, u);
- if (v->overtaking == 0) v->cur_speed = u->cur_speed;
+ if (v->overtaking == old_overtaking) v->cur_speed = u->cur_speed;
/* In case an RV is stopped in a road stop, why not try to load? */
if (v->cur_speed == 0 && IsInsideMM(v->state, RVSB_IN_DT_ROAD_STOP, RVSB_IN_DT_ROAD_STOP_END) &&
v->current_order.ShouldStopAtStation(v, GetStationIndex(v->tile), false) &&
IsInfraTileUsageAllowed(VEH_ROAD, v->owner, v->tile) && !v->current_order.IsType(OT_LEAVESTATION) &&
GetRoadStopType(v->tile) == (v->IsBus() ? ROADSTOP_BUS : ROADSTOP_TRUCK)) {
+ byte cur_overtaking = IsRoadVehicleOnOtherSideOfRoad(v) ? RVSB_DRIVE_SIDE : 0;
+ if (cur_overtaking != v->overtaking) v->SetRoadVehicleOvertaking(cur_overtaking);
Station *st = Station::GetByTile(v->tile);
v->last_station_visited = st->index;
RoadVehArrivesAt(v, st);
@@ -1787,6 +1853,17 @@ void RoadVehicle::SetDestTile(TileIndex tile)
this->dest_tile = tile;
}
+void RoadVehicle::SetRoadVehicleOvertaking(byte overtaking)
+{
+ if (IsInsideMM(this->state, RVSB_IN_DT_ROAD_STOP, RVSB_IN_DT_ROAD_STOP_END)) RoadStop::GetByTile(this->tile, GetRoadStopType(this->tile))->Leave(this);
+
+ for (RoadVehicle *u = this; u != nullptr; u = u->Next()) {
+ u->overtaking = overtaking;
+ }
+
+ if (IsInsideMM(this->state, RVSB_IN_DT_ROAD_STOP, RVSB_IN_DT_ROAD_STOP_END)) RoadStop::GetByTile(this->tile, GetRoadStopType(this->tile))->Enter(this);
+}
+
static void CheckIfRoadVehNeedsService(RoadVehicle *v)
{
/* If we already got a slot at a stop, use that FIRST, and go to a depot later */
diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp
index 8e90270d43..c5c69b983f 100644
--- a/src/saveload/afterload.cpp
+++ b/src/saveload/afterload.cpp
@@ -3787,6 +3787,14 @@ bool AfterLoadGame()
}
}
+ if (SlXvIsFeatureMissing(XSLFI_ONE_WAY_DT_ROAD_STOP)) {
+ for (TileIndex t = 0; t < map_size; t++) {
+ if (IsDriveThroughStopTile(t)) {
+ SetDriveThroughStopDisallowedRoadDirections(t, DRD_NONE);
+ }
+ }
+ }
+
InitializeRoadGUI();
/* This needs to be done after conversion. */
diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp
index 71277da019..07ff952abc 100644
--- a/src/saveload/extended_ver_sl.cpp
+++ b/src/saveload/extended_ver_sl.cpp
@@ -136,6 +136,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = {
{ XSLFI_THROUGH_TRAIN_DEPOT, XSCF_NULL, 1, 1, "drive_through_train_depot", nullptr, nullptr, nullptr },
{ XSLFI_MORE_VEHICLE_ORDERS, XSCF_NULL, 1, 1, "more_veh_orders", nullptr, nullptr, nullptr },
{ XSLFI_ORDER_FLAGS_EXTRA, XSCF_NULL, 1, 1, "order_flags_extra", nullptr, nullptr, nullptr },
+ { XSLFI_ONE_WAY_DT_ROAD_STOP, XSCF_NULL, 1, 1, "one_way_dt_road_stop", nullptr, nullptr, nullptr },
{ XSLFI_NULL, XSCF_NULL, 0, 0, nullptr, nullptr, nullptr, nullptr },// This is the end marker
};
diff --git a/src/saveload/extended_ver_sl.h b/src/saveload/extended_ver_sl.h
index 421cbb4765..05bb25c858 100644
--- a/src/saveload/extended_ver_sl.h
+++ b/src/saveload/extended_ver_sl.h
@@ -90,6 +90,7 @@ enum SlXvFeatureIndex {
XSLFI_THROUGH_TRAIN_DEPOT, ///< Drive-through train depots
XSLFI_MORE_VEHICLE_ORDERS, ///< More vehicle orders - VehicleOrderID is 16 bits instead of 8
XSLFI_ORDER_FLAGS_EXTRA, ///< Order flags field extra size
+ XSLFI_ONE_WAY_DT_ROAD_STOP, ///< One-way drive-through road stops
XSLFI_RIFF_HEADER_60_BIT, ///< Size field in RIFF chunk header is 60 bit
XSLFI_HEIGHT_8_BIT, ///< Map tile height is 8 bit instead of 4 bit, but savegame version may be before this became true in trunk
diff --git a/src/script/api/script_road.cpp b/src/script/api/script_road.cpp
index 5d2acb0e95..7cb7d554b5 100644
--- a/src/script/api/script_road.cpp
+++ b/src/script/api/script_road.cpp
@@ -115,6 +115,7 @@
uint dir_2 = 2 ^ dir_1;
DisallowedRoadDirections drd2 = IsNormalRoadTile(t2) ? GetDisallowedRoadDirections(t2) : DRD_NONE;
+ if (IsDriveThroughStopTile(t2)) drd2 = GetDriveThroughStopDisallowedRoadDirections(t2);
return HasBit(r1, dir_1) && HasBit(r2, dir_2) && drd2 != DRD_BOTH && drd2 != (dir_1 > dir_2 ? DRD_SOUTHBOUND : DRD_NORTHBOUND);
}
diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp
index 307deedf98..5ed0c029c8 100644
--- a/src/station_cmd.cpp
+++ b/src/station_cmd.cpp
@@ -1080,11 +1080,6 @@ static CommandCost CheckFlatLandRoadStop(TileArea tile_area, DoCommandFlag flags
if (RoadTypeIsRoad(rt) && !HasPowerOnRoad(rt, road_rt)) return_cmd_error(STR_ERROR_NO_SUITABLE_ROAD);
- if (GetDisallowedRoadDirections(cur_tile) != DRD_NONE && road_owner != OWNER_TOWN) {
- CommandCost ret = CheckOwnership(road_owner);
- if (ret.Failed()) return ret;
- }
-
cost.AddCost(RoadBuildCost(road_rt) * (2 - num_pieces));
} else if (RoadTypeIsRoad(rt)) {
cost.AddCost(RoadBuildCost(rt) * 2);
@@ -2034,6 +2029,7 @@ CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uin
RoadType tram_rt = MayHaveRoad(cur_tile) ? GetRoadType(cur_tile, RTT_TRAM) : INVALID_ROADTYPE;
Owner road_owner = road_rt != INVALID_ROADTYPE ? GetRoadOwner(cur_tile, RTT_ROAD) : _current_company;
Owner tram_owner = tram_rt != INVALID_ROADTYPE ? GetRoadOwner(cur_tile, RTT_TRAM) : _current_company;
+ DisallowedRoadDirections drd = IsNormalRoadTile(cur_tile) ? GetDisallowedRoadDirections(cur_tile) : DRD_NONE;
if (IsTileType(cur_tile, MP_STATION) && IsRoadStop(cur_tile)) {
RemoveRoadStop(cur_tile, flags);
@@ -2071,6 +2067,7 @@ CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uin
UpdateCompanyRoadInfrastructure(tram_rt, tram_owner, 2);
MakeDriveThroughRoadStop(cur_tile, st->owner, road_owner, tram_owner, st->index, rs_type, road_rt, tram_rt, axis);
+ SetDriveThroughStopDisallowedRoadDirections(cur_tile, drd);
road_stop->MakeDriveThrough();
} else {
if (road_rt == INVALID_ROADTYPE && RoadTypeIsRoad(rt)) road_rt = rt;
@@ -2246,6 +2243,7 @@ CommandCost CmdRemoveRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, ui
RoadBits road_bits = ROAD_NONE;
RoadType road_type[] = { INVALID_ROADTYPE, INVALID_ROADTYPE };
Owner road_owner[] = { OWNER_NONE, OWNER_NONE };
+ DisallowedRoadDirections drd = DRD_NONE;
if (IsDriveThroughStopTile(cur_tile)) {
FOR_ALL_ROADTRAMTYPES(rtt) {
road_type[rtt] = GetRoadType(cur_tile, rtt);
@@ -2255,6 +2253,7 @@ CommandCost CmdRemoveRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, ui
if (!keep_drive_through_roads && road_owner[rtt] == _current_company) road_type[rtt] = INVALID_ROADTYPE;
}
road_bits = AxisToRoadBits(DiagDirToAxis(GetRoadStopDir(cur_tile)));
+ drd = GetDriveThroughStopDisallowedRoadDirections(cur_tile);
}
CommandCost ret = RemoveRoadStop(cur_tile, flags);
@@ -2269,6 +2268,7 @@ CommandCost CmdRemoveRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, ui
if ((flags & DC_EXEC) && (road_type[RTT_ROAD] != INVALID_ROADTYPE || road_type[RTT_TRAM] != INVALID_ROADTYPE)) {
MakeRoadNormal(cur_tile, road_bits, road_type[RTT_ROAD], road_type[RTT_TRAM], ClosestTownFromTile(cur_tile, UINT_MAX)->index,
road_owner[RTT_ROAD], road_owner[RTT_TRAM]);
+ if (drd != DRD_NONE) SetDisallowedRoadDirections(cur_tile, drd);
/* Update company infrastructure counts. */
int count = CountBits(road_bits);
@@ -3270,6 +3270,11 @@ draw_default_foundation:
uint sprite_offset = axis == AXIS_X ? 1 : 0;
DrawRoadOverlays(ti, PAL_NONE, road_rti, tram_rti, sprite_offset, sprite_offset);
+
+ DisallowedRoadDirections drd = GetDriveThroughStopDisallowedRoadDirections(ti->tile);
+ if (drd != DRD_NONE) {
+ DrawGroundSpriteAt(SPR_ONEWAY_BASE + drd - 1 + ((axis == AXIS_X) ? 0 : 3), PAL_NONE, 8, 8, 0);
+ }
} else {
/* Non-drivethrough road stops are only valid for roads. */
assert_tile(road_rt != INVALID_ROADTYPE && tram_rt == INVALID_ROADTYPE, ti->tile);
@@ -3460,23 +3465,24 @@ static void GetTileDesc_Station(TileIndex tile, TileDesc *td)
static TrackStatus GetTileTrackStatus_Station(TileIndex tile, TransportType mode, uint sub_mode, DiagDirection side)
{
- TrackBits trackbits = TRACK_BIT_NONE;
+ TrackdirBits trackdirbits = TRACKDIR_BIT_NONE;
switch (mode) {
case TRANSPORT_RAIL:
if (HasStationRail(tile) && !IsStationTileBlocked(tile)) {
- trackbits = TrackToTrackBits(GetRailStationTrack(tile));
+ trackdirbits = TrackToTrackdirBits(GetRailStationTrack(tile));
}
break;
case TRANSPORT_WATER:
/* buoy is coded as a station, it is always on open water */
if (IsBuoy(tile)) {
- trackbits = TRACK_BIT_ALL;
+ TrackBits trackbits = TRACK_BIT_ALL;
/* remove tracks that connect NE map edge */
if (TileX(tile) == 0) trackbits &= ~(TRACK_BIT_X | TRACK_BIT_UPPER | TRACK_BIT_RIGHT);
/* remove tracks that connect NW map edge */
if (TileY(tile) == 0) trackbits &= ~(TRACK_BIT_Y | TRACK_BIT_LEFT | TRACK_BIT_UPPER);
+ trackdirbits = TrackBitsToTrackdirBits(trackbits);
}
break;
@@ -3492,7 +3498,13 @@ static TrackStatus GetTileTrackStatus_Station(TileIndex tile, TransportType mode
if (axis != DiagDirToAxis(side) || (IsStandardRoadStopTile(tile) && dir != side)) break;
}
- trackbits = AxisToTrackBits(axis);
+ TrackBits trackbits = AxisToTrackBits(axis);
+ if (IsDriveThroughStopTile(tile)) {
+ const uint drd_to_multiplier[DRD_END] = { 0x101, 0x100, 0x1, 0x0 };
+ trackdirbits = (TrackdirBits)(trackbits * drd_to_multiplier[GetDriveThroughStopDisallowedRoadDirections(tile)]);
+ } else {
+ trackdirbits = TrackBitsToTrackdirBits(trackbits);
+ }
}
break;
@@ -3500,7 +3512,7 @@ static TrackStatus GetTileTrackStatus_Station(TileIndex tile, TransportType mode
break;
}
- return CombineTrackStatus(TrackBitsToTrackdirBits(trackbits), TRACKDIR_BIT_NONE);
+ return CombineTrackStatus(trackdirbits, TRACKDIR_BIT_NONE);
}
diff --git a/src/station_map.h b/src/station_map.h
index 0d3aaccb74..5f9ab36ffe 100644
--- a/src/station_map.h
+++ b/src/station_map.h
@@ -235,6 +235,29 @@ static inline bool IsDriveThroughStopTile(TileIndex t)
return IsRoadStopTile(t) && GetStationGfx(t) >= GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET;
}
+/**
+ * Gets the disallowed directions
+ * @param t the tile to get the directions from
+ * @return the disallowed directions
+ */
+static inline DisallowedRoadDirections GetDriveThroughStopDisallowedRoadDirections(TileIndex t)
+{
+ assert_tile(IsDriveThroughStopTile(t), t);
+ return (DisallowedRoadDirections)GB(_m[t].m3, 0, 2);
+}
+
+/**
+ * Sets the disallowed directions
+ * @param t the tile to set the directions for
+ * @param drd the disallowed directions
+ */
+static inline void SetDriveThroughStopDisallowedRoadDirections(TileIndex t, DisallowedRoadDirections drd)
+{
+ assert_tile(IsDriveThroughStopTile(t), t);
+ assert(drd < DRD_END);
+ SB(_m[t].m3, 0, 2, drd);
+}
+
/**
* Get the station graphics of this airport tile
* @param t the tile to query