changeset 13875:052c69fbc76b draft

(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
author rubidium <rubidium@openttd.org>
date Fri, 04 Dec 2009 20:52:19 +0000
parents 8110208583b6
children c511ca6f1594
files src/openttd.cpp src/order_cmd.cpp src/pathfinder/npf/npf.cpp src/pathfinder/yapf/yapf_road.cpp src/roadstop.cpp src/roadstop_base.h src/roadveh.h src/roadveh_cmd.cpp src/saveload/afterload.cpp src/saveload/oldloader_sl.cpp src/saveload/saveload.cpp src/saveload/saveload_internal.h src/saveload/station_sl.cpp src/settings_type.h src/station_cmd.cpp src/table/settings.h
diffstat 16 files changed, 459 insertions(+), 50 deletions(-) [+]
line wrap: on
line diff
--- a/src/openttd.cpp
+++ b/src/openttd.cpp
@@ -1096,6 +1096,16 @@
 		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) {
--- 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"
 
--- a/src/pathfinder/npf/npf.cpp
+++ b/src/pathfinder/npf/npf.cpp
@@ -351,10 +351,21 @@
 
 		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:
--- a/src/pathfinder/yapf/yapf_road.cpp
+++ b/src/pathfinder/yapf/yapf_road.cpp
@@ -66,9 +66,21 @@
 					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:
--- 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 @@
  */
 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 @@
 }
 
 /**
+ * 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 @@
 	}
 
 	/* 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 @@
 	}
 }
 
+/**
+ * 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<const RoadVehicle *> 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();
--- a/src/roadstop_base.h
+++ b/src/roadstop_base.h
@@ -25,9 +25,46 @@
 		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 @@
 		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 @@
 
 	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
--- a/src/roadveh.h
+++ b/src/roadveh.h
@@ -39,7 +39,6 @@
 
 	/* 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 @@
 	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,
 };
 
--- a/src/roadveh_cmd.cpp
+++ b/src/roadveh_cmd.cpp
@@ -463,18 +463,14 @@
 	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 @@
 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 @@
 			/* 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 @@
 
 			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;
 				}
 			}
 
--- a/src/saveload/afterload.cpp
+++ b/src/saveload/afterload.cpp
@@ -1360,7 +1360,7 @@
 		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 @@
 		}
 	}
 
+	/* Road stops is 'only' updating some caches */
+	AfterLoadRoadStops();
 	AfterLoadLabelMaps();
 
 	GamelogPrintDebug(1);
--- a/src/saveload/oldloader_sl.cpp
+++ b/src/saveload/oldloader_sl.cpp
@@ -190,7 +190,7 @@
 		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);
 			}
 		}
 
--- 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
 
--- a/src/saveload/saveload_internal.h
+++ b/src/saveload/saveload_internal.h
@@ -29,6 +29,7 @@
 
 void AfterLoadVehicles(bool part_of_load);
 void AfterLoadStations();
+void AfterLoadRoadStops();
 void AfterLoadLabelMaps();
 void UpdateHousesAndTowns();
 
--- 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 @@
 	}
 }
 
+/**
+ * (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),
--- a/src/settings_type.h
+++ b/src/settings_type.h
@@ -223,6 +223,8 @@
 	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 @@
 	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
--- a/src/station_cmd.cpp
+++ b/src/station_cmd.cpp
@@ -1638,6 +1638,7 @@
 		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 @@
 			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 @@
 			}
 		}
 
-		DoClearSquare(tile);
 		st->rect.AfterRemoveTile(st, tile);
 
 		st->UpdateVirtCoord();
--- a/src/table/settings.h
+++ b/src/table/settings.h
@@ -473,6 +473,8 @@
 	     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 @@
 	 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),