# HG changeset patch # User rubidium # Date 2009-12-04 20:52:19 # Node ID 208a75ffd2fd05c0384cc4fc1d53ab0ee6f52230 # Parent 548fb0872b2525d6ebea0627b2478f0c5c942463 (svn r18404) -Codechange: link drive through stops better together -Feature: make penalty for road stop occupancy user configurable -Fix [FS#1944]: road vehicles would not pick an empty drive through stop. Now they will *if* the penalty for driving around is less than the occupancy penalty -Fix [FS#1495]: long (articulated) road vehicles could block loading of others when the following road vehicle already got 'permission' to go to the next bay even when it could not reach it -Change: improve the throughput of the drive through road stops by letting them stop closer together diff --git a/src/openttd.cpp b/src/openttd.cpp --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -1096,6 +1096,16 @@ void StateGameLoop() CallWindowTickEvent(); NewsLoop(); } else { + /* Temporary strict checking of the road stop cache entries */ + const RoadStop *rs; + FOR_ALL_ROADSTOPS(rs) { + if (IsStandardRoadStopTile(rs->xy)) continue; + + assert(rs->GetEntry(DIAGDIR_NE) != rs->GetEntry(DIAGDIR_NW)); + rs->GetEntry(DIAGDIR_NE)->CheckIntegrity(rs); + rs->GetEntry(DIAGDIR_NW)->CheckIntegrity(rs); + } + if (_debug_desync_level > 1) { Vehicle *v; FOR_ALL_VEHICLES(v) { diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -21,12 +21,12 @@ #include "timetable.h" #include "vehicle_func.h" #include "depot_base.h" -#include "roadstop_base.h" #include "core/pool_func.hpp" #include "aircraft.h" #include "roadveh.h" #include "station_base.h" #include "waypoint_base.h" +#include "roadstop_base.h" #include "table/strings.h" diff --git a/src/pathfinder/npf/npf.cpp b/src/pathfinder/npf/npf.cpp --- a/src/pathfinder/npf/npf.cpp +++ b/src/pathfinder/npf/npf.cpp @@ -351,10 +351,21 @@ static int32 NPFRoadPathCost(AyStar *as, case MP_STATION: { cost = NPF_TILE_LENGTH; - /* Increase the cost for drive-through road stops */ - if (IsDriveThroughStopTile(tile)) cost += _settings_game.pf.npf.npf_road_drive_through_penalty; - RoadStop *rs = RoadStop::GetByTile(tile, GetRoadStopType(tile)); - cost += 8 * NPF_TILE_LENGTH * ((!rs->IsFreeBay(0)) + (!rs->IsFreeBay(1))); + const RoadStop *rs = RoadStop::GetByTile(tile, GetRoadStopType(tile)); + if (IsDriveThroughStopTile(tile)) { + /* Increase the cost for drive-through road stops */ + cost += _settings_game.pf.npf.npf_road_drive_through_penalty; + DiagDirection dir = TrackdirToExitdir(current->direction); + if (!RoadStop::IsDriveThroughRoadStopContinuation(tile, tile - TileOffsByDiagDir(dir))) { + /* 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(); + } + } else { + /* Increase cost for filled road stops */ + cost += _settings_game.pf.npf.npf_road_bay_occupied_penalty * (!rs->IsFreeBay(0) + !rs->IsFreeBay(1)) / 2; + } } break; default: diff --git a/src/pathfinder/yapf/yapf_road.cpp b/src/pathfinder/yapf/yapf_road.cpp --- a/src/pathfinder/yapf/yapf_road.cpp +++ b/src/pathfinder/yapf/yapf_road.cpp @@ -66,9 +66,21 @@ protected: break; case MP_STATION: { - if (IsDriveThroughStopTile(tile)) cost += Yapf().PfGetSettings().road_stop_penalty; - RoadStop *rs = RoadStop::GetByTile(tile, GetRoadStopType(tile)); - cost += 8 * YAPF_TILE_LENGTH * ((!rs->IsFreeBay(0)) + (!rs->IsFreeBay(1))); + const RoadStop *rs = RoadStop::GetByTile(tile, GetRoadStopType(tile)); + if (IsDriveThroughStopTile(tile)) { + /* Increase the cost for drive-through road stops */ + cost += Yapf().PfGetSettings().road_stop_penalty; + DiagDirection dir = TrackdirToExitdir(trackdir); + if (!RoadStop::IsDriveThroughRoadStopContinuation(tile, tile - TileOffsByDiagDir(dir))) { + /* 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(); + } + } else { + /* Increase cost for filled road stops */ + cost += Yapf().PfGetSettings().road_stop_bay_occupied_penalty * (!rs->IsFreeBay(0) + !rs->IsFreeBay(1)) / 2; + } } break; default: diff --git a/src/roadstop.cpp b/src/roadstop.cpp --- a/src/roadstop.cpp +++ b/src/roadstop.cpp @@ -14,6 +14,8 @@ #include "core/pool_func.hpp" #include "roadstop_base.h" #include "station_base.h" +#include "vehicle_func.h" +#include "landscape.h" RoadStopPool _roadstop_pool("RoadStop"); INSTANTIATE_POOL_METHODS(RoadStop) @@ -23,6 +25,12 @@ INSTANTIATE_POOL_METHODS(RoadStop) */ RoadStop::~RoadStop() { + /* When we are the head we need to free the entries */ + if (HasBit(this->status, RSSFB_BASE_ENTRY)) { + delete this->east; + delete this->west; + } + if (CleaningPool()) return; } @@ -47,18 +55,174 @@ RoadStop *RoadStop::GetNextRoadStop(cons } /** + * Join this road stop to another 'base' road stop if possible; + * fill all necessary data to become an actual drive through road stop. + * Also update the length etc. + */ +void RoadStop::MakeDriveThrough() +{ + assert(this->east == NULL && this->west == NULL); + + RoadStopType rst = GetRoadStopType(this->xy); + DiagDirection dir = GetRoadStopDir(this->xy); + /* Use absolute so we always go towards the nortern 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) : NULL; + + /* 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) : NULL; + + /* Amount of road stops that will be added to the 'northern' head */ + int added = 1; + if (north && rs_north->east != NULL) { // (east != NULL) == (west != NULL) + /* There is a more nothern one, so this can join them */ + this->east = rs_north->east; + this->west = rs_north->west; + + if (south && rs_south->east != NULL) { // (east != NULL) == (west != NULL) + /* There more southern tiles too, they must 'join' us too */ + ClrBit(rs_south->status, RSSFB_BASE_ENTRY); + this->east->occupied += rs_south->east->occupied; + this->west->occupied += rs_south->west->occupied; + + /* Free the now unneeded entry structs */ + delete rs_south->east; + delete rs_south->west; + + /* Make all 'children' of the southern tile take the new master */ + for (; IsDriveThroughRoadStopContinuation(this->xy, south_tile); south_tile += offset) { + rs_south = RoadStop::GetByTile(south_tile, rst); + if (rs_south->east == NULL) break; + rs_south->east = rs_north->east; + rs_south->west = rs_north->west; + added++; + } + } + } else if (south && rs_south->east != NULL) { // (east != NULL) == (west != NULL) + /* There is one to the south, but not to the north... so we become 'parent' */ + this->east = rs_south->east; + this->west = rs_south->west; + SetBit(this->status, RSSFB_BASE_ENTRY); + ClrBit(rs_south->status, RSSFB_BASE_ENTRY); + } else { + /* We are the only... so we are automatically the master */ + this->east = new Entry(); + this->west = new Entry(); + SetBit(this->status, RSSFB_BASE_ENTRY); + } + + /* Now update the lengths */ + added *= TILE_SIZE; + this->east->length += added; + this->west->length += added; +} + +/** + * Prepare for removal of this stop; update other neighbouring stops + * if needed. Also update the length etc. + */ +void RoadStop::ClearDriveThrough() +{ + assert(this->east != NULL && this->west != NULL); + + RoadStopType rst = GetRoadStopType(this->xy); + DiagDirection dir = GetRoadStopDir(this->xy); + /* Use absolute so we always go towards the nortern 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) : NULL; + + /* 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) : NULL; + + /* Must only be cleared after we determined which neighbours are + * part of our little entry 'queue' */ + DoClearSquare(this->xy); + + 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; + } + + /* Find the other end; the northern most tile */ + for (; IsDriveThroughRoadStopContinuation(base_tile, north_tile); north_tile -= offset) { + rs_north = RoadStop::GetByTile(north_tile, rst); + } + + /* 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 maintainance 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); + + assert(HasBit(rs_north->status, RSSFB_BASE_ENTRY)); + rs_north->east->Rebuild(rs_north); + rs_north->west->Rebuild(rs_north); + } else { + /* Only we left, so simple update the length. */ + rs_north->east->length -= TILE_SIZE; + rs_north->west->length -= TILE_SIZE; + } + } 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->length -= TILE_SIZE; + rs_south->west->length -= TILE_SIZE; + } 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 = NULL; + this->west = NULL; +} + +/** * Leave the road stop * @param rv the vehicle that leaves the stop */ void RoadStop::Leave(RoadVehicle *rv) { - /* Vehicle is leaving a road stop tile, mark bay as free - * For drive-through stops, only do it if the vehicle stopped here */ - if (IsStandardRoadStopTile(rv->tile) || HasBit(rv->state, RVS_IS_STOPPING)) { + if (IsStandardRoadStopTile(rv->tile)) { + /* Vehicle is leaving a road stop tile, mark bay as free */ this->FreeBay(HasBit(rv->state, RVS_USING_SECOND_BAY)); - ClrBit(rv->state, RVS_IS_STOPPING); + this->SetEntranceBusy(false); + } else { + /* Otherwise just leave the drive through's entry cache. */ + this->GetEntry(DirToDiagDir(rv->direction))->Leave(rv); } - if (IsStandardRoadStopTile(rv->tile)) this->SetEntranceBusy(false); } /** @@ -85,19 +249,8 @@ bool RoadStop::Enter(RoadVehicle *rv) } /* Vehicles entering a drive-through stop from the 'normal' side use first bay (bay 0). */ - byte side = ((DirToDiagDir(rv->direction) == ReverseDiagDir(GetRoadStopDir(this->xy))) == (rv->overtaking == 0)) ? 0 : 1; - - if (!this->IsFreeBay(side)) return false; + this->GetEntry(DirToDiagDir(rv->direction))->Enter(rv); - /* Check if the vehicle is stopping at this road stop */ - if (GetRoadStopType(this->xy) == (rv->IsBus() ? ROADSTOP_BUS : ROADSTOP_TRUCK) && - rv->current_order.ShouldStopAtStation(rv, GetStationIndex(this->xy))) { - SetBit(rv->state, RVS_IS_STOPPING); - this->AllocateDriveThroughBay(side); - } - - /* Indicate if vehicle is using second bay. */ - if (side == 1) SetBit(rv->state, RVS_USING_SECOND_BAY); /* Indicate a drive-through stop */ SetBit(rv->state, RVS_IN_DT_ROAD_STOP); return true; @@ -120,6 +273,121 @@ bool RoadStop::Enter(RoadVehicle *rv) } } +/** + * Leave the road stop + * @param rv the vehicle that leaves the stop + */ +void RoadStop::Entry::Leave(const RoadVehicle *rv) +{ + this->occupied -= rv->rcache.cached_veh_length; + assert(this->occupied >= 0); +} + +/** + * Enter the road stop + * @param rv the vehicle that enters the stop + */ +void RoadStop::Entry::Enter(const RoadVehicle *rv) +{ + assert(this->occupied < this->length); + this->occupied += rv->rcache.cached_veh_length; +} + +/** + * Checks whether the 'next' tile is still part of the road same drive through + * stop 'rs' in the same direction for the same vehicle. + * @param rs the road stop tile to check against + * @param next the 'next' tile to check + * @return true if the 'next' tile is part of the road stop at 'next'. + */ +/* static */ bool RoadStop::IsDriveThroughRoadStopContinuation(TileIndex rs, TileIndex next) +{ + return IsTileType(next, MP_STATION) && + GetStationIndex(next) == GetStationIndex(rs) && + GetStationType(next) == GetStationType(rs) && + GetRoadStopDir(next) == GetRoadStopDir(rs) && + IsDriveThroughStopTile(next); +} + +typedef std::list RVList; ///< A list of road vehicles + +/** Helper for finding RVs in a road stop. */ +struct RoadStopEntryRebuilderHelper { + RVList vehicles; ///< The list of vehicles to possibly add to. + DiagDirection dir; ///< The direction the vehicle has to face to be added. +}; + +/** + * Add road vehicles to the station's list if needed. + * @param v the found vehicle + * @param data the extra data used to make our decision + * @return always NULL + */ +Vehicle *FindVehiclesInRoadStop(Vehicle *v, void *data) +{ + RoadStopEntryRebuilderHelper *rserh = (RoadStopEntryRebuilderHelper*)data; + /* Not a RV or not in the right direction or crashed :( */ + if (v->type != VEH_ROAD || DirToDiagDir(v->direction) != rserh->dir || !v->IsPrimaryVehicle() || (v->vehstatus & VS_CRASHED) != 0) return NULL; + + RoadVehicle *rv = RoadVehicle::From(v); + /* Don't add ones not in a road stop */ + if (rv->state < RVSB_IN_ROAD_STOP) return NULL; + + /* Do not add duplicates! */ + for (RVList::iterator it = rserh->vehicles.begin(); it != rserh->vehicles.end(); it++) { + if (rv == *it) return NULL; + } + + rserh->vehicles.push_back(rv); + return NULL; +} + +/** + * Rebuild, from scratch, the vehicles and other metadata on this stop. + * @param rs the roadstop this entry is part of + * @param side the side of the road stop to look at + */ +void RoadStop::Entry::Rebuild(const RoadStop *rs, int side) +{ + assert(HasBit(rs->status, RSSFB_BASE_ENTRY)); + + DiagDirection dir = GetRoadStopDir(rs->xy); + if (side == -1) side = (rs->east == this); + + RoadStopEntryRebuilderHelper rserh; + rserh.dir = side ? dir : ReverseDiagDir(dir); + + this->length = 0; + TileIndexDiff offset = abs(TileOffsByDiagDir(dir)); + for (TileIndex tile = rs->xy; IsDriveThroughRoadStopContinuation(rs->xy, tile); tile += offset) { + this->length += TILE_SIZE; + FindVehicleOnPos(tile, &rserh, FindVehiclesInRoadStop); + } + + this->occupied = 0; + for (RVList::iterator it = rserh.vehicles.begin(); it != rserh.vehicles.end(); it++) { + this->occupied += (*it)->rcache.cached_veh_length; + } +} + + +/** + * Check the integrity of the data in this struct. + * @param rs the roadstop this entry is part of + */ +void RoadStop::Entry::CheckIntegrity(const RoadStop *rs) const +{ + if (!HasBit(rs->status, RSSFB_BASE_ENTRY)) return; + + /* The tile 'before' the road stop must not be part of this 'line' */ + assert(!IsDriveThroughRoadStopContinuation(rs->xy, rs->xy - abs(TileOffsByDiagDir(GetRoadStopDir(rs->xy))))); + + Entry temp; + temp.Rebuild(rs, rs->east == this); + if (temp.length != this->length || temp.occupied != this->occupied) NOT_REACHED(); +} + + void InitializeRoadStops() { _roadstop_pool.CleanPool(); diff --git a/src/roadstop_base.h b/src/roadstop_base.h --- a/src/roadstop_base.h +++ b/src/roadstop_base.h @@ -25,9 +25,46 @@ struct RoadStop : RoadStopPool::PoolItem RSSFB_BAY0_FREE = 0, ///< Non-zero when bay 0 is free RSSFB_BAY1_FREE = 1, ///< Non-zero when bay 1 is free RSSFB_BAY_COUNT = 2, ///< Max. number of bays + RSSFB_BASE_ENTRY = 6, ///< Non-zero when the entries on this road stop are the primary, i.e. the ones to delete RSSFB_ENTRY_BUSY = 7, ///< Non-zero when roadstop entry is busy }; + /** Container for each entry point of a drive through road stop */ + struct Entry { + private: + int length; ///< The length of the stop in tile 'units' + int occupied; ///< The amount of occupied stop in tile 'units' + + public: + friend class RoadStop; ///< Oh yeah, the road stop may play with me. + + /** Create an entry */ + Entry() : length(0), occupied(0) {} + + /** + * Get the length of this drive through stop. + * @return the length in tile units. + */ + FORCEINLINE int GetLength() const + { + return this->length; + } + + /** + * Get the amount of occupied space in this drive through stop. + * @return the occupied space in tile units. + */ + FORCEINLINE int GetOccupied() const + { + return this->occupied; + } + + void Leave(const RoadVehicle *rv); + void Enter(const RoadVehicle *rv); + void CheckIntegrity(const RoadStop *rs) const; + void Rebuild(const RoadStop *rs, int side = -1); + }; + static const uint LIMIT = 16; ///< The maximum amount of roadstops that are allowed at a single station TileIndex xy; ///< Position on the map @@ -80,6 +117,29 @@ struct RoadStop : RoadStopPool::PoolItem SB(this->status, RSSFB_ENTRY_BUSY, 1, busy); } + /** + * Get the drive through road stop entry struct for the given direction. + * @param direction the direciton to get the entry for + * @return the entry + */ + FORCEINLINE const Entry *GetEntry(DiagDirection dir) const + { + return HasBit((int)dir, 1) ? this->west : this->east; + } + + /** + * Get the drive through road stop entry struct for the given direction. + * @param direction the direciton to get the entry for + * @return the entry + */ + FORCEINLINE Entry *GetEntry(DiagDirection dir) + { + return HasBit((int)dir, 1) ? this->west : this->east; + } + + void MakeDriveThrough(); + void ClearDriveThrough(); + void Leave(RoadVehicle *rv); bool Enter(RoadVehicle *rv); @@ -87,7 +147,12 @@ struct RoadStop : RoadStopPool::PoolItem static RoadStop *GetByTile(TileIndex tile, RoadStopType type); + static bool IsDriveThroughRoadStopContinuation(TileIndex rs, TileIndex next); + private: + Entry *east; ///< The vehicles that entered from the east + Entry *west; ///< The vehicles that entered from the west + /** * Allocates a bay * @return the allocated bay number diff --git a/src/roadveh.h b/src/roadveh.h --- a/src/roadveh.h +++ b/src/roadveh.h @@ -39,7 +39,6 @@ enum RoadVehicleStates { /* Bit numbers */ RVS_USING_SECOND_BAY = 1, ///< Only used while in a road stop - RVS_IS_STOPPING = 2, ///< Only used for drive-through stops. Vehicle will stop here RVS_DRIVE_SIDE = 4, ///< Only used when retrieving move data RVS_IN_ROAD_STOP = 5, ///< The vehicle is in a road stop RVS_IN_DT_ROAD_STOP = 6, ///< The vehicle is in a drive-through road stop @@ -70,7 +69,7 @@ enum { RVC_START_FRAME_AFTER_LONG_TRAM = 21, RVC_TURN_AROUND_START_FRAME_SHORT_TRAM = 16, /* Stop frame for a vehicle in a drive-through stop */ - RVC_DRIVE_THROUGH_STOP_FRAME = 7, + RVC_DRIVE_THROUGH_STOP_FRAME = 11, RVC_DEPOT_STOP_FRAME = 11, }; diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -463,18 +463,14 @@ void RoadVehicle::UpdateDeltaXY(Directio this->z_extent = 6; } -static void ClearCrashedStation(RoadVehicle *v) -{ - RoadStop::GetByTile(v->tile, GetRoadStopType(v->tile))->Leave(v); -} - static void DeleteLastRoadVeh(RoadVehicle *v) { Vehicle *u = v; for (; v->Next() != NULL; v = v->Next()) u = v; u->SetNext(NULL); - if (IsTileType(v->tile, MP_STATION)) ClearCrashedStation(v); + /* Only leave the road stop when we're really gone. */ + if (IsInsideMM(v->state, RVSB_IN_ROAD_STOP, RVSB_IN_ROAD_STOP_END)) RoadStop::GetByTile(v->tile, GetRoadStopType(v->tile))->Leave(v); delete v; } @@ -540,7 +536,14 @@ static Vehicle *EnumCheckRoadVehCrashTra uint RoadVehicle::Crash(bool flooded) { uint pass = Vehicle::Crash(flooded); - if (this->IsRoadVehFront()) pass += 1; // driver + if (this->IsRoadVehFront()) { + pass += 1; // driver + + /* If we're in a drive through road stop we ought to leave it */ + if (IsInsideMM(this->state, RVSB_IN_DT_ROAD_STOP, RVSB_IN_DT_ROAD_STOP_END)) { + RoadStop::GetByTile(this->tile, GetRoadStopType(this->tile))->Leave(this); + } + } this->crashed_ctr = flooded ? 2000 : 1; // max 2220, disappear pretty fast when flooded return pass; } @@ -1413,6 +1416,17 @@ again: /* There is a vehicle in front overtake it if possible */ if (v->overtaking == 0) RoadVehCheckOvertake(v, u); if (v->overtaking == 0) 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)) && + v->owner == GetTileOwner(v->tile) && !v->current_order.IsType(OT_LEAVESTATION) && + GetRoadStopType(v->tile) == (v->IsBus() ? ROADSTOP_BUS : ROADSTOP_TRUCK)) { + Station *st = Station::GetByTile(v->tile); + v->last_station_visited = st->index; + RoadVehArrivesAt(v, st); + v->BeginLoading(); + } return false; } } @@ -1455,20 +1469,12 @@ again: if (IsDriveThroughStopTile(v->tile)) { TileIndex next_tile = TILE_ADD(v->tile, TileOffsByDir(v->direction)); - RoadStopType type = v->IsBus() ? ROADSTOP_BUS : ROADSTOP_TRUCK; /* Check if next inline bay is free */ - if (IsDriveThroughStopTile(next_tile) && (GetRoadStopType(next_tile) == type) && GetStationIndex(v->tile) == GetStationIndex(next_tile)) { - RoadStop *rs_n = RoadStop::GetByTile(next_tile, type); - - if (rs_n->IsFreeBay(HasBit(v->state, RVS_USING_SECOND_BAY))) { - /* Bay in next stop along is free - use it */ - v->dest_tile = rs_n->xy; - - v->frame++; - RoadZPosAffectSpeed(v, SetRoadVehPosition(v, x, y, false)); - return true; - } + if (RoadStop::IsDriveThroughRoadStopContinuation(v->tile, next_tile)) { + v->frame++; + RoadZPosAffectSpeed(v, SetRoadVehPosition(v, x, y, false)); + return true; } } diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -1360,7 +1360,7 @@ bool AfterLoadGame() RoadVehicle *rv; FOR_ALL_ROADVEHICLES(rv) { if (rv->state == 250 || rv->state == 251) { - SetBit(rv->state, RVS_IS_STOPPING); + SetBit(rv->state, 2); } } } @@ -1959,6 +1959,8 @@ bool AfterLoadGame() } } + /* Road stops is 'only' updating some caches */ + AfterLoadRoadStops(); AfterLoadLabelMaps(); GamelogPrintDebug(1); diff --git a/src/saveload/oldloader_sl.cpp b/src/saveload/oldloader_sl.cpp --- a/src/saveload/oldloader_sl.cpp +++ b/src/saveload/oldloader_sl.cpp @@ -190,7 +190,7 @@ void FixOldVehicles() if (v->type == VEH_ROAD) { RoadVehicle *rv = RoadVehicle::From(v); if (rv->state != RVSB_IN_DEPOT && rv->state != RVSB_WORMHOLE) { - ClrBit(rv->state, RVS_IS_STOPPING); + ClrBit(rv->state, 2); } } diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -47,7 +47,7 @@ #include "saveload_internal.h" -extern const uint16 SAVEGAME_VERSION = 129; +extern const uint16 SAVEGAME_VERSION = 130; SavegameType _savegame_type; ///< type of savegame we are loading diff --git a/src/saveload/saveload_internal.h b/src/saveload/saveload_internal.h --- a/src/saveload/saveload_internal.h +++ b/src/saveload/saveload_internal.h @@ -29,6 +29,7 @@ const SaveLoad *GetBaseStationDescriptio void AfterLoadVehicles(bool part_of_load); void AfterLoadStations(); +void AfterLoadRoadStops(); void AfterLoadLabelMaps(); void UpdateHousesAndTowns(); diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp --- a/src/saveload/station_sl.cpp +++ b/src/saveload/station_sl.cpp @@ -15,6 +15,7 @@ #include "../roadstop_base.h" #include "../vehicle_base.h" #include "../newgrf_station.h" +#include "../station_map.h" #include "saveload.h" #include "table/strings.h" @@ -108,6 +109,25 @@ void AfterLoadStations() } } +/** + * (Re)building of road stop caches after loading a savegame. + */ +void AfterLoadRoadStops() +{ + /* First construct the drive through entries */ + RoadStop *rs; + FOR_ALL_ROADSTOPS(rs) { + if (IsDriveThroughStopTile(rs->xy)) rs->MakeDriveThrough(); + } + /* And then rebuild the data in those entries */ + FOR_ALL_ROADSTOPS(rs) { + if (!HasBit(rs->status, RoadStop::RSSFB_BASE_ENTRY)) continue; + + rs->GetEntry(DIAGDIR_NE)->Rebuild(rs); + rs->GetEntry(DIAGDIR_NW)->Rebuild(rs); + } +} + static const SaveLoad _roadstop_desc[] = { SLE_VAR(RoadStop, xy, SLE_UINT32), SLE_CONDNULL(1, 0, 44), diff --git a/src/settings_type.h b/src/settings_type.h --- a/src/settings_type.h +++ b/src/settings_type.h @@ -223,6 +223,8 @@ struct NPFSettings { uint32 npf_road_curve_penalty; ///< the penalty for curves uint32 npf_crossing_penalty; ///< the penalty for level crossings uint32 npf_road_drive_through_penalty; ///< the penalty for going through a drive-through road stop + uint32 npf_road_dt_occupied_penalty; ///< the penalty multiplied by the fill percentage of a drive-through road stop + uint32 npf_road_bay_occupied_penalty; ///< the penalty multiplied by the fill percentage of a road bay }; /** Settings related to the yet another pathfinder. */ @@ -236,6 +238,8 @@ struct YAPFSettings { uint32 road_curve_penalty; ///< penalty for curves uint32 road_crossing_penalty; ///< penalty for level crossing uint32 road_stop_penalty; ///< penalty for going through a drive-through road stop + uint32 road_stop_occupied_penalty; ///< penalty multiplied by the fill percentage of a drive-through road stop + uint32 road_stop_bay_occupied_penalty; ///< penalty multiplied by the fill percentage of a road bay bool rail_firstred_twoway_eol; ///< treat first red two-way signal as dead end uint32 rail_firstred_penalty; ///< penalty for first red signal uint32 rail_firstred_exit_penalty; ///< penalty for first red exit signal diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -1638,6 +1638,7 @@ CommandCost CmdBuildRoadStop(TileIndex t RoadStopType rs_type = type ? ROADSTOP_TRUCK : ROADSTOP_BUS; if (is_drive_through) { MakeDriveThroughRoadStop(tile, st->owner, road_owner, tram_owner, st->index, rs_type, rts, (Axis)p1); + road_stop->MakeDriveThrough(); } else { MakeRoadStop(tile, st->owner, st->index, rs_type, rts, (DiagDirection)p1); } @@ -1721,6 +1722,13 @@ static CommandCost RemoveRoadStop(TileIn pred->next = cur_stop->next; } + if (IsDriveThroughStopTile(tile)) { + /* Clears the tile for us */ + cur_stop->ClearDriveThrough(); + } else { + DoClearSquare(tile); + } + SetWindowWidgetDirty(WC_STATION_VIEW, st->index, SVW_ROADVEHS); delete cur_stop; @@ -1733,7 +1741,6 @@ static CommandCost RemoveRoadStop(TileIn } } - DoClearSquare(tile); st->rect.AfterRemoveTile(st, tile); st->UpdateVirtCoord(); diff --git a/src/table/settings.h b/src/table/settings.h --- a/src/table/settings.h +++ b/src/table/settings.h @@ -473,6 +473,8 @@ const SettingDesc _settings[] = { SDT_VAR(GameSettings, pf.npf.npf_road_curve_penalty, SLE_UINT, 0, 0, 1, 0, 100000, 0, STR_NULL, NULL), SDT_VAR(GameSettings, pf.npf.npf_crossing_penalty, SLE_UINT, 0, 0, ( 3 * NPF_TILE_LENGTH), 0, 100000, 0, STR_NULL, NULL), SDT_CONDVAR(GameSettings, pf.npf.npf_road_drive_through_penalty, SLE_UINT, 47, SL_MAX_VERSION, 0, 0, ( 8 * NPF_TILE_LENGTH), 0, 100000, 0, STR_NULL, NULL), + SDT_CONDVAR(GameSettings, pf.npf.npf_road_dt_occupied_penalty, SLE_UINT,130, SL_MAX_VERSION, 0, 0, ( 8 * NPF_TILE_LENGTH), 0, 100000, 0, STR_NULL, NULL), + SDT_CONDVAR(GameSettings, pf.npf.npf_road_bay_occupied_penalty, SLE_UINT,130, SL_MAX_VERSION, 0, 0, ( 15 * NPF_TILE_LENGTH), 0, 100000, 0, STR_NULL, NULL), SDT_CONDBOOL(GameSettings, pf.yapf.disable_node_optimization, 28, SL_MAX_VERSION, 0, 0, false, STR_NULL, NULL), @@ -504,6 +506,8 @@ const SettingDesc _settings[] = { SDT_CONDVAR(GameSettings, pf.yapf.road_curve_penalty, SLE_UINT, 33, SL_MAX_VERSION, 0, 0, 1 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL), SDT_CONDVAR(GameSettings, pf.yapf.road_crossing_penalty, SLE_UINT, 33, SL_MAX_VERSION, 0, 0, 3 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL), SDT_CONDVAR(GameSettings, pf.yapf.road_stop_penalty, SLE_UINT, 47, SL_MAX_VERSION, 0, 0, 8 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL), + SDT_CONDVAR(GameSettings, pf.yapf.road_stop_occupied_penalty, SLE_UINT,130, SL_MAX_VERSION, 0, 0, 8 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL), + SDT_CONDVAR(GameSettings, pf.yapf.road_stop_bay_occupied_penalty, SLE_UINT,130, SL_MAX_VERSION, 0, 0, 15 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL), SDT_CONDVAR(GameSettings, game_creation.land_generator, SLE_UINT8, 30, SL_MAX_VERSION, 0,MS, 1, 0, 1, 0, STR_CONFIG_SETTING_LAND_GENERATOR, NULL), SDT_CONDVAR(GameSettings, game_creation.oil_refinery_limit, SLE_UINT8, 30, SL_MAX_VERSION, 0, 0, 32, 12, 48, 0, STR_CONFIG_SETTING_OIL_REF_EDGE_DISTANCE, NULL),