From 3a75f138747d6dc63d48164f55e4b192370b56f1 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Sat, 24 Oct 2020 20:39:15 +0100 Subject: [PATCH] Allow drive-through road stops to be one-way --- docs/landscape.html | 12 +++ docs/landscape_grid.html | 2 +- src/pathfinder/follow_track.hpp | 2 +- src/pathfinder/npf/npf.cpp | 6 +- src/pathfinder/yapf/yapf_road.cpp | 10 ++- src/road_cmd.cpp | 34 ++++++++- src/roadstop.cpp | 108 +++++++++++++++++++++++++- src/roadstop_base.h | 13 ++++ src/roadveh.h | 7 +- src/roadveh_cmd.cpp | 123 ++++++++++++++++++++++++------ src/saveload/afterload.cpp | 8 ++ src/saveload/extended_ver_sl.cpp | 1 + src/saveload/extended_ver_sl.h | 1 + src/script/api/script_road.cpp | 1 + src/station_cmd.cpp | 32 +++++--- src/station_map.h | 23 ++++++ 16 files changed, 333 insertions(+), 50 deletions(-) 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); } -static void RoadVehCheckOvertake(RoadVehicle *v, RoadVehicle *u) +/** + * 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) { - OvertakeData od; - - od.v = v; - od.u = u; + 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) +{ /* 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