diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 23d55ceba3..ba37949253 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -835,6 +835,8 @@ struct OvertakeData { const RoadVehicle *v; TileIndex tile; Trackdir trackdir; + int tunnelbridge_min; + int tunnelbridge_max; }; static Vehicle *EnumFindVehBlockingOvertake(Vehicle *v, void *data) @@ -864,6 +866,32 @@ static Vehicle *EnumFindVehBlockingOvertake(Vehicle *v, void *data) return v; } +static Vehicle *EnumFindVehBlockingOvertakeTunnelBridge(Vehicle *v, void *data) +{ + const OvertakeData *od = (OvertakeData*)data; + + switch (DiagDirToAxis(DirToDiagDir(v->direction))) { + case AXIS_X: + if (v->x_pos < od->tunnelbridge_min || v->x_pos > od->tunnelbridge_max) return nullptr; + break; + case AXIS_Y: + if (v->y_pos < od->tunnelbridge_min || v->y_pos > od->tunnelbridge_max) return nullptr; + break; + default: + NOT_REACHED(); + } + return EnumFindVehBlockingOvertake(v, data); +} + +static Vehicle *EnumFindVehBlockingOvertakeBehind(Vehicle *v, void *data) +{ + const OvertakeData *od = (OvertakeData*)data; + + if (v->First() == od->u || v->First() == od->v) return nullptr; + if (RoadVehicle::From(v)->overtaking != 0 && TileVirtXY(v->x_pos, v->y_pos) == od->tile) return v; + return nullptr; +} + static bool CheckRoadInfraUnsuitableForOvertaking(OvertakeData *od) { if (!HasTileAnyRoadType(od->tile, od->v->compatible_roadtypes)) return true; @@ -884,7 +912,7 @@ static bool CheckRoadInfraUnsuitableForOvertaking(OvertakeData *od) return true; } } else { - return rcows != RCOWS_SIDE_JUNCTION_NO_EXIT; + return rcows == RCOWS_NORMAL || rcows == RCOWS_NO_ACCESS; } } @@ -924,6 +952,34 @@ inline bool IsValidRoadVehStateForOvertake(const RoadVehicle *v) return true; } +static bool CheckTunnelBridgeBlockedForOvertaking(OvertakeData *od, TileIndex behind_end, TileIndex ahead_end, TileIndex pos, int ahead_extent, int behind_extent) +{ + switch (DirToDiagDir(od->v->direction)) { + case DIAGDIR_NE: + od->tunnelbridge_min = (TileX(pos) - ahead_extent) * TILE_SIZE; + od->tunnelbridge_max = ((TileX(pos) + behind_extent) * TILE_SIZE) + TILE_UNIT_MASK; + break; + case DIAGDIR_SE: + od->tunnelbridge_min = (TileY(pos) - behind_extent) * TILE_SIZE; + od->tunnelbridge_max = ((TileY(pos) + ahead_extent) * TILE_SIZE) + TILE_UNIT_MASK; + break; + case DIAGDIR_SW: + od->tunnelbridge_min = (TileX(pos) - behind_extent) * TILE_SIZE; + od->tunnelbridge_max = ((TileX(pos) + ahead_extent) * TILE_SIZE) + TILE_UNIT_MASK; + break; + case DIAGDIR_NW: + od->tunnelbridge_min = (TileY(pos) - ahead_extent) * TILE_SIZE; + od->tunnelbridge_max = ((TileY(pos) + behind_extent) * TILE_SIZE) + TILE_UNIT_MASK; + break; + default: + NOT_REACHED(); + } + + if (HasVehicleOnPos(behind_end, VEH_ROAD, od, EnumFindVehBlockingOvertakeTunnelBridge)) return true; + if (HasVehicleOnPos(ahead_end, VEH_ROAD, od, EnumFindVehBlockingOvertakeTunnelBridge)) return true; + return false; +} + static void RoadVehCheckOvertake(RoadVehicle *v, RoadVehicle *u) { /* Trams can't overtake other trams */ @@ -955,9 +1011,9 @@ static void RoadVehCheckOvertake(RoadVehicle *v, RoadVehicle *u) /* 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 */ + /* Check if vehicle is in a road stop, depot, or not on a straight road */ 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)) { + !IsInsideMM(w->state, RVSB_IN_DT_ROAD_STOP, RVSB_IN_DT_ROAD_STOP_END) && w->state != RVSB_WORMHOLE) { return; } } @@ -983,12 +1039,40 @@ static void RoadVehCheckOvertake(RoadVehicle *v, RoadVehicle *u) * - No barred levelcrossing * - No other vehicles in the way */ - uint tile_count = 1 + CeilDiv(v->gcache.cached_total_length, TILE_SIZE); + int tile_count = 1 + CeilDiv(v->gcache.cached_total_length, TILE_SIZE); TileIndex check_tile = v->tile; + DiagDirection dir = DirToDiagDir(v->direction); TileIndexDiff check_tile_diff = TileOffsByDiagDir(DirToDiagDir(v->direction)); - for (; tile_count != 0; tile_count--, check_tile += check_tile_diff) { + TileIndex behind_check_tile = v->tile - check_tile_diff; + + int tile_offset = ((DiagDirToAxis(DirToDiagDir(v->direction)) == AXIS_X) ? v->x_pos : v->y_pos) & 0xF; + int tile_ahead_margin = ((dir == DIAGDIR_SE || dir == DIAGDIR_SW) ? TILE_SIZE - 1 - tile_offset : tile_offset);; + int behind_tile_count = (v->gcache.cached_total_length + tile_ahead_margin) / TILE_SIZE; + + if (IsTileType(check_tile, MP_TUNNELBRIDGE)) { + TileIndex behind_end = GetOtherTunnelBridgeEnd(check_tile); + if (IsBridgeTile(check_tile) && (IsRoadCustomBridgeHeadTile(check_tile) || IsRoadCustomBridgeHeadTile(behind_end))) return; + if (GetTunnelBridgeDirection(check_tile) == dir) std::swap(check_tile, behind_end); + TileIndex veh_tile = TileVirtXY(v->x_pos, v->y_pos); + bool one_way = GetRoadCachedOneWayState(check_tile) != RCOWS_NORMAL; + if (CheckTunnelBridgeBlockedForOvertaking(&od, behind_end, check_tile, veh_tile, one_way ? 0 : (tile_count - 1), behind_tile_count)) return; + + tile_count -= DistanceManhattan(check_tile, veh_tile); + behind_tile_count -= DistanceManhattan(behind_end, veh_tile); + check_tile += check_tile_diff; + behind_check_tile = behind_end - check_tile_diff; + } + for (; tile_count > 0; tile_count--, check_tile += check_tile_diff) { od.tile = check_tile; if (CheckRoadInfraUnsuitableForOvertaking(&od)) return; + if (IsTileType(check_tile, MP_TUNNELBRIDGE)) { + TileIndex ahead_end = GetOtherTunnelBridgeEnd(check_tile); + if (IsBridgeTile(check_tile) && (IsRoadCustomBridgeHeadTile(check_tile) || IsRoadCustomBridgeHeadTile(ahead_end))) return; + if (GetRoadCachedOneWayState(check_tile) == RCOWS_NORMAL && CheckTunnelBridgeBlockedForOvertaking(&od, check_tile, ahead_end, check_tile, tile_count - 1, 0)) return; + tile_count -= DistanceManhattan(check_tile, ahead_end); + check_tile = ahead_end; + continue; + } if (IsDriveThroughStopTile(check_tile) && GetDriveThroughStopDisallowedRoadDirections(check_tile) != DRD_NONE) { const RoadStop *rs = RoadStop::GetByTile(check_tile, GetRoadStopType(check_tile)); DiagDirection dir = DirToDiagDir(v->direction); @@ -1003,18 +1087,22 @@ static void RoadVehCheckOvertake(RoadVehicle *v, RoadVehicle *u) } if (CheckRoadBlockedForOvertaking(&od)) return; } - DiagDirection dir = DirToDiagDir(v->direction); - int tile_offset = ((DiagDirToAxis(DirToDiagDir(v->direction)) == AXIS_X) ? v->x_pos : v->y_pos) & 0xF; - int tile_ahead_margin = ((dir == DIAGDIR_SE || dir == DIAGDIR_SW) ? TILE_SIZE - 1 - tile_offset : tile_offset);; - tile_count = (v->gcache.cached_total_length + tile_ahead_margin) / 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 (tile_count == 1) { - RoadBits rb = GetAnyRoadBits(check_tile, RTT_ROAD); - if ((rb & DiagDirToRoadBits(dir)) && HasVehicleOnPos(check_tile, VEH_ROAD, &od, EnumFindVehBlockingOvertakeBehind)) return; + + for (; behind_tile_count > 0; behind_tile_count--, behind_check_tile -= check_tile_diff) { + od.tile = behind_check_tile; + if (behind_tile_count == 1) { + RoadBits rb = GetAnyRoadBits(behind_check_tile, RTT_ROAD); + if ((rb & DiagDirToRoadBits(dir)) && HasVehicleOnPos(behind_check_tile, VEH_ROAD, &od, EnumFindVehBlockingOvertakeBehind)) return; } else { if (CheckRoadInfraUnsuitableForOvertaking(&od)) return; + if (IsTileType(behind_check_tile, MP_TUNNELBRIDGE)) { + TileIndex behind_end = GetOtherTunnelBridgeEnd(behind_check_tile); + if (IsBridgeTile(behind_check_tile) && (IsRoadCustomBridgeHeadTile(behind_check_tile) || IsRoadCustomBridgeHeadTile(behind_end))) return; + if (CheckTunnelBridgeBlockedForOvertaking(&od, behind_check_tile, behind_end, behind_check_tile, 0, behind_tile_count - 1)) return; + behind_tile_count -= DistanceManhattan(behind_check_tile, behind_end); + check_tile = behind_end; + continue; + } if (CheckRoadBlockedForOvertaking(&od)) return; } } @@ -1390,7 +1478,8 @@ static void RoadVehCheckFinishOvertake(RoadVehicle *v) const RoadVehicle *last = v->Last(); const int front_margin = 10; const int back_margin = 10; - switch (DirToDiagDir(v->direction)) { + DiagDirection dir = DirToDiagDir(v->direction); + switch (dir) { case DIAGDIR_NE: od.min_coord = v->x_pos - front_margin; od.max_coord = last->x_pos + back_margin; @@ -1416,13 +1505,43 @@ static void RoadVehCheckFinishOvertake(RoadVehicle *v) } TileIndexDiffC ti = TileIndexDiffCByDiagDir(DirToDiagDir(v->direction)); - TileIndex ahead_tile = TileAddWrap(v->tile, ti.x, ti.y); - if (ahead_tile != INVALID_TILE && HasVehicleOnPos(ahead_tile, VEH_ROAD, &od, EnumFindVehBlockingFinishOvertake)) return; + bool check_ahead = true; + int tiles_behind = 1 + CeilDiv(v->gcache.cached_total_length, TILE_SIZE); - uint tile_count = 1 + CeilDiv(v->gcache.cached_total_length, TILE_SIZE); TileIndex check_tile = v->tile; - for (; check_tile != INVALID_TILE && tile_count != 0; tile_count--, check_tile = TileAddWrap(check_tile, -ti.x, -ti.y)) { + if (IsTileType(check_tile, MP_TUNNELBRIDGE)) { + TileIndex ahead = GetOtherTunnelBridgeEnd(check_tile); + if (v->state == RVSB_WORMHOLE) { + check_ahead = false; + } + if (GetTunnelBridgeDirection(check_tile) == dir) { + check_ahead = false; + } else if (GetTunnelBridgeDirection(check_tile) == ReverseDiagDir(dir)) { + std::swap(ahead, check_tile); + } + + if (HasVehicleOnPos(ahead, VEH_ROAD, &od, EnumFindVehBlockingFinishOvertake)) return; if (HasVehicleOnPos(check_tile, VEH_ROAD, &od, EnumFindVehBlockingFinishOvertake)) return; + tiles_behind -= 1 + DistanceManhattan(check_tile, TileVirtXY(v->x_pos, v->y_pos)); + check_tile = TileAddWrap(check_tile, -ti.x, -ti.y); + } + + if (check_ahead > 0) { + TileIndex ahead_tile = TileAddWrap(check_tile, ti.x, ti.y); + if (ahead_tile != INVALID_TILE) { + if (HasVehicleOnPos(ahead_tile, VEH_ROAD, &od, EnumFindVehBlockingFinishOvertake)) return; + if (IsTileType(ahead_tile, MP_TUNNELBRIDGE) && HasVehicleOnPos(GetOtherTunnelBridgeEnd(ahead_tile), VEH_ROAD, &od, EnumFindVehBlockingFinishOvertake)) return; + } + } + + for (; check_tile != INVALID_TILE && tiles_behind > 0; tiles_behind--, check_tile = TileAddWrap(check_tile, -ti.x, -ti.y)) { + if (HasVehicleOnPos(check_tile, VEH_ROAD, &od, EnumFindVehBlockingFinishOvertake)) return; + if (IsTileType(check_tile, MP_TUNNELBRIDGE)) { + TileIndex other_end = GetOtherTunnelBridgeEnd(check_tile); + tiles_behind -= DistanceManhattan(other_end, check_tile); + if (HasVehicleOnPos(other_end, VEH_ROAD, &od, EnumFindVehBlockingFinishOvertake)) return; + check_tile = other_end; + } } /* road on the normal side is clear, finish overtake */ @@ -1438,11 +1557,11 @@ inline byte IncreaseOvertakingCounter(RoadVehicle *v) 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 (v->overtaking & RVSB_DRIVE_SIDE && v->IsFrontEngine()) { 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)) && !IsInsideMM(v->state, RVSB_IN_DT_ROAD_STOP, RVSB_IN_DT_ROAD_STOP_END)) { + } 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) && v->state != RVSB_WORMHOLE) { /* Articulated RVs may not overtake on corners */ v->SetRoadVehicleOvertaking(0); } else if (v->HasArticulatedPart() && IsBridgeTile(v->tile) && (IsRoadCustomBridgeHeadTile(v->tile) || IsRoadCustomBridgeHeadTile(GetOtherBridgeEnd(v->tile)))) { @@ -1455,7 +1574,7 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) /* If overtaking just aborts at a random moment, we can have a out-of-bound problem, * if the vehicle started a corner. To protect that, only allow an abort of * overtake if we are on straight roads */ - if (v->overtaking_ctr >= v->GetOvertakingCounterThreshold() && v->state < RVSB_IN_ROAD_STOP && IsStraightRoadTrackdir((Trackdir)v->state)) { + if (v->overtaking_ctr >= v->GetOvertakingCounterThreshold() && (v->state == RVSB_WORMHOLE || (v->state < RVSB_IN_ROAD_STOP && IsStraightRoadTrackdir((Trackdir)v->state)))) { if (IsOneWayRoadTile(v->tile)) { RoadVehCheckFinishOvertake(v); } else { @@ -1475,14 +1594,35 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) if (v->state == RVSB_WORMHOLE) { /* Vehicle is entering a depot or is on a bridge or in a tunnel */ GetNewVehiclePosResult gp = GetNewVehiclePos(v); - + if (v->overtaking & 1) { + DiagDirection dir = DirToDiagDir(v->direction); + switch (dir) { + case DIAGDIR_NE: + case DIAGDIR_SW: + SB(gp.y, 0, 4, (_settings_game.vehicle.road_side ^ (dir >> 1) ^ (v->overtaking >> RVS_DRIVE_SIDE)) ? 9 : 5); + break; + case DIAGDIR_SE: + case DIAGDIR_NW: + SB(gp.x, 0, 4, (_settings_game.vehicle.road_side ^ (dir >> 1) ^ (v->overtaking >> RVS_DRIVE_SIDE)) ? 9 : 5); + break; + default: + NOT_REACHED(); + } + } if (v->IsFrontEngine()) { - const Vehicle *u = RoadVehFindCloseTo(v, gp.x, gp.y, v->direction); + RoadVehicle *u = RoadVehFindCloseTo(v, gp.x, gp.y, v->direction); if (u != nullptr) { - v->cur_speed = u->First()->cur_speed; + 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 == old_overtaking) { + v->cur_speed = u->cur_speed; + } return false; } } + v->overtaking &= ~1; if (IsTileType(gp.new_tile, MP_TUNNELBRIDGE) && HasBit(VehicleEnterTile(v, gp.new_tile, gp.x, gp.y), VETS_ENTERED_WORMHOLE)) { if (IsRoadCustomBridgeHeadTile(gp.new_tile)) { @@ -1999,6 +2139,7 @@ void RoadVehicle::SetRoadVehicleOvertaking(byte overtaking) for (RoadVehicle *u = this; u != nullptr; u = u->Next()) { u->overtaking = overtaking; + if (u->state == RVSB_WORMHOLE) u->overtaking |= 1; } if (IsInsideMM(this->state, RVSB_IN_DT_ROAD_STOP, RVSB_IN_DT_ROAD_STOP_END)) RoadStop::GetByTile(this->tile, GetRoadStopType(this->tile))->Enter(this); diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp index ee61e5b4a0..db84bb2126 100644 --- a/src/saveload/extended_ver_sl.cpp +++ b/src/saveload/extended_ver_sl.cpp @@ -117,7 +117,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_DUAL_RAIL_TYPES, XSCF_NULL, 1, 1, "dual_rail_types", nullptr, nullptr, nullptr }, { XSLFI_CONSIST_SPEED_RD_FLAG, XSCF_NULL, 1, 1, "consist_speed_rd_flag", nullptr, nullptr, nullptr }, { XSLFI_SAVEGAME_UNIQUE_ID, XSCF_IGNORABLE_ALL, 1, 1, "savegame_unique_id", nullptr, nullptr, nullptr }, - { XSLFI_RV_OVERTAKING, XSCF_NULL, 1, 1, "roadveh_overtaking", nullptr, nullptr, nullptr }, + { XSLFI_RV_OVERTAKING, XSCF_NULL, 2, 2, "roadveh_overtaking", nullptr, nullptr, nullptr }, { XSLFI_LINKGRAPH_MODES, XSCF_NULL, 1, 1, "linkgraph_modes", nullptr, nullptr, nullptr }, { XSLFI_GAME_EVENTS, XSCF_NULL, 1, 1, "game_events", nullptr, nullptr, nullptr }, { XSLFI_ROAD_LAYOUT_CHANGE_CTR, XSCF_NULL, 1, 1, "road_layout_change_ctr", nullptr, nullptr, nullptr },