changeset 6857:0005a9ed1903 draft

(svn r10097) -Feature: Add support for articulated road vehicles, or callbacks 11 and 17 for road vehicles for those who prefer the technical explanation.
author maedhros <maedhros@openttd.org>
date Mon, 11 Jun 2007 14:00:16 +0000
parents 7d2e01558528
children de19e5b3f7ff
files src/articulated_vehicles.cpp src/articulated_vehicles.h src/depot_gui.cpp src/economy.cpp src/lang/english.txt src/main_gui.cpp src/newgrf.cpp src/newgrf_engine.cpp src/player_gui.cpp src/road_cmd.cpp src/roadveh.h src/roadveh_cmd.cpp src/roadveh_gui.cpp src/station_cmd.cpp src/train_cmd.cpp src/tunnelbridge_cmd.cpp src/vehicle.cpp src/vehicle.h src/vehicle_gui.h src/viewport.cpp src/water_cmd.cpp
diffstat 21 files changed, 570 insertions(+), 212 deletions(-) [+]
line wrap: on
line diff
--- a/src/articulated_vehicles.cpp
+++ b/src/articulated_vehicles.cpp
@@ -4,16 +4,17 @@
 
 #include "stdafx.h"
 #include "openttd.h"
-#include "debug.h"
 #include "functions.h"
 #include "command.h"
 #include "vehicle.h"
 #include "articulated_vehicles.h"
 #include "engine.h"
 #include "train.h"
+#include "roadveh.h"
 #include "newgrf_callbacks.h"
 #include "newgrf_engine.h"
 
+
 uint CountArticulatedParts(EngineID engine_type)
 {
 	if (!HASBIT(EngInfo(engine_type)->callbackmask, CBM_ARTIC_ENGINE)) return 0;
@@ -27,7 +28,7 @@
 	return i - 1;
 }
 
-void AddArticulatedParts(Vehicle **vl)
+void AddArticulatedParts(Vehicle **vl, VehicleType type)
 {
 	const Vehicle *v = vl[0];
 	Vehicle *u = vl[0];
@@ -46,9 +47,8 @@
 
 		u = u->next;
 
-		EngineID engine_type = GB(callback, 0, 7);
+		EngineID engine_type = GetFirstEngineOfType(type) + GB(callback, 0, 7);
 		bool flip_image = HASBIT(callback, 7);
-		const RailVehicleInfo *rvi_artic = RailVehInfo(engine_type);
 
 		/* get common values from first engine */
 		u->direction = v->direction;
@@ -57,28 +57,57 @@
 		u->x_pos = v->x_pos;
 		u->y_pos = v->y_pos;
 		u->z_pos = v->z_pos;
-		u->u.rail.track = v->u.rail.track;
-		u->u.rail.railtype = v->u.rail.railtype;
 		u->build_year = v->build_year;
 		u->vehstatus = v->vehstatus & ~VS_STOPPED;
-		u->u.rail.first_engine = v->engine_type;
 
-		/* get more settings from rail vehicle info */
-		u->spritenum = rvi_artic->image_index;
-		if (flip_image) u->spritenum++;
-		u->cargo_type = rvi_artic->cargo_type;
 		u->cargo_subtype = 0;
-		u->cargo_cap = rvi_artic->capacity;
 		u->max_speed = 0;
 		u->max_age = 0;
 		u->engine_type = engine_type;
 		u->value = 0;
-		u = new (u) Train();
 		u->subtype = 0;
-		SetArticulatedPart(u);
 		u->cur_image = 0xAC2;
 		u->random_bits = VehicleRandomBits();
 
+		switch (type) {
+			default: NOT_REACHED();
+
+			case VEH_TRAIN: {
+				const RailVehicleInfo *rvi_artic = RailVehInfo(engine_type);
+
+				u = new (u) Train();
+				u->u.rail.track = v->u.rail.track;
+				u->u.rail.railtype = v->u.rail.railtype;
+				u->u.rail.first_engine = v->engine_type;
+
+				u->spritenum = rvi_artic->image_index;
+				u->cargo_type = rvi_artic->cargo_type;
+				u->cargo_cap = rvi_artic->capacity;
+
+				SetArticulatedPart(u);
+			} break;
+
+			case VEH_ROAD: {
+				const RoadVehicleInfo *rvi_artic = RoadVehInfo(engine_type);
+
+				u = new (u) RoadVehicle();
+				u->u.road.first_engine = v->engine_type;
+				u->u.road.cached_veh_length = GetRoadVehLength(u);
+				u->u.road.state = RVSB_IN_DEPOT;
+
+				u->u.road.roadtype = v->u.road.roadtype;
+				u->u.road.compatible_roadtypes = v->u.road.compatible_roadtypes;
+
+				u->spritenum = rvi_artic->image_index;
+				u->cargo_type = rvi_artic->cargo_type;
+				u->cargo_cap = rvi_artic->capacity;
+
+				SetRoadVehArticPart(u);
+			} break;
+		}
+
+		if (flip_image) u->spritenum++;
+
 		VehiclePositionChanged(u);
 	}
 }
--- a/src/articulated_vehicles.h
+++ b/src/articulated_vehicles.h
@@ -6,6 +6,6 @@
 #define ARTICULATED_VEHICLES_H
 
 uint CountArticulatedParts(EngineID engine_type);
-void AddArticulatedParts(Vehicle **vl);
+void AddArticulatedParts(Vehicle **vl, VehicleType type);
 
 #endif /* ARTICULATED_VEHICLES_H */
--- a/src/depot_gui.cpp
+++ b/src/depot_gui.cpp
@@ -184,7 +184,7 @@
 			DrawStringRightAligned(w->widget[DEPOT_WIDGET_MATRIX].right - 1, y + 4, STR_TINY_BLACK, 0); // Draw the counter
 			break;
 
-		case VEH_ROAD:     DrawRoadVehImage( v, x + 24, sprite_y, WP(w, depot_d).sel); break;
+		case VEH_ROAD:     DrawRoadVehImage( v, x + 24, sprite_y, 1, WP(w, depot_d).sel); break;
 		case VEH_SHIP:     DrawShipImage(    v, x + 19, sprite_y - 1, WP(w, depot_d).sel); break;
 		case VEH_AIRCRAFT: {
 			const Sprite *spr = GetSprite(GetAircraftImage(v, DIR_W));
--- a/src/economy.cpp
+++ b/src/economy.cpp
@@ -30,6 +30,7 @@
 #include "vehicle_gui.h"
 #include "ai/ai.h"
 #include "train.h"
+#include "roadveh.h"
 #include "aircraft.h"
 #include "newgrf_engine.h"
 #include "newgrf_sound.h"
@@ -340,7 +341,7 @@
 			if (v->owner == new_player) {
 				switch (v->type) {
 					case VEH_TRAIN:    if (IsFrontEngine(v)) num_train++; break;
-					case VEH_ROAD:     num_road++; break;
+					case VEH_ROAD:     if (IsRoadVehFront(v)) num_road++; break;
 					case VEH_SHIP:     num_ship++; break;
 					case VEH_AIRCRAFT: if (IsNormalAircraft(v)) num_aircraft++; break;
 					default: break;
@@ -361,7 +362,7 @@
 					if (IsEngineCountable(v)) GetPlayer(new_player)->num_engines[v->engine_type]++;
 					switch (v->type) {
 						case VEH_TRAIN:    if (IsFrontEngine(v)) v->unitnumber = ++num_train; break;
-						case VEH_ROAD:     v->unitnumber = ++num_road; break;
+						case VEH_ROAD:     if (IsRoadVehFront(v)) v->unitnumber = ++num_road; break;
 						case VEH_SHIP:     v->unitnumber = ++num_ship; break;
 						case VEH_AIRCRAFT: if (IsNormalAircraft(v)) v->unitnumber = ++num_aircraft; break;
 						default: NOT_REACHED();
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -2776,6 +2776,8 @@
 STR_9026_ROAD_VEHICLE_SELECTION                                 :{BLACK}Road vehicle selection list - click on vehicle for information
 STR_9027_BUILD_THE_HIGHLIGHTED_ROAD                             :{BLACK}Build the highlighted road vehicle
 STR_902A_COST_SPEED_RUNNING_COST                                :{BLACK}Cost: {CURRENCY}{}Speed: {VELOCITY}{}Running Cost: {CURRENCY}/yr{}Capacity: {CARGO}
+STR_ARTICULATED_RV_CAPACITY                                     :{BLACK}Capacity: {LTBLUE}
+STR_BARE_CARGO                                                  :{CARGO}
 
 STR_902C_NAME_ROAD_VEHICLE                                      :{WHITE}Name road vehicle
 STR_902D_CAN_T_NAME_ROAD_VEHICLE                                :{WHITE}Can't name road vehicle...
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -30,6 +30,7 @@
 #include "waypoint.h"
 #include "variables.h"
 #include "train.h"
+#include "roadveh.h"
 #include "unmovable_map.h"
 #include "string.h"
 #include "screenshot.h"
@@ -849,7 +850,7 @@
 	int dis = -1;
 
 	FOR_ALL_VEHICLES(v) {
-		if (v->type == VEH_ROAD) CLRBIT(dis, v->owner);
+		if (v->type == VEH_ROAD && IsRoadVehFront(v)) CLRBIT(dis, v->owner);
 	}
 	PopupMainPlayerToolbMenu(w, 332, 14, dis);
 }
--- a/src/newgrf.cpp
+++ b/src/newgrf.cpp
@@ -4268,8 +4268,8 @@
 	                   |                                        (0 << 0x13)  // followvehicle
 	                   |                                        (1 << 0x14)  // trams
 	                   |                                        (0 << 0x15)  // enhancetunnels
-	                   |                                        (0 << 0x16)  // shortrvs
-	                   |                                        (0 << 0x17)  // articulatedrvs
+	                   |                                        (1 << 0x16)  // shortrvs
+	                   |                                        (1 << 0x17)  // articulatedrvs
 	                   |                                        (1 << 0x1E); // variablerunningcosts
 }
 
--- a/src/newgrf_engine.cpp
+++ b/src/newgrf_engine.cpp
@@ -507,7 +507,7 @@
 	switch (variable) {
 		case 0x40: // Get length of consist
 		case 0x41: // Get length of same consecutive wagons
-			if (v->type != VEH_TRAIN) return 1;
+			if (!v->HasFront()) return 1;
 
 			{
 				const Vehicle* u;
@@ -830,7 +830,7 @@
 	res->ResolveReal   = &VehicleResolveReal;
 
 	res->u.vehicle.self   = v;
-	res->u.vehicle.parent = (v != NULL && v->type == VEH_TRAIN) ? GetFirstVehicleInChain(v) : v;
+	res->u.vehicle.parent = (v != NULL && v->HasFront()) ? GetFirstVehicleInChain(v) : v;
 
 	res->u.vehicle.self_type = engine_type;
 
--- a/src/player_gui.cpp
+++ b/src/player_gui.cpp
@@ -17,6 +17,7 @@
 #include "economy.h"
 #include "network/network.h"
 #include "variables.h"
+#include "roadveh.h"
 #include "train.h"
 #include "aircraft.h"
 #include "date.h"
@@ -649,7 +650,7 @@
 		if (v->owner == player) {
 			switch (v->type) {
 				case VEH_TRAIN:    if (IsFrontEngine(v)) train++; break;
-				case VEH_ROAD:     road++; break;
+				case VEH_ROAD:     if (IsRoadVehFront(v)) road++; break;
 				case VEH_AIRCRAFT: if (IsNormalAircraft(v)) air++; break;
 				case VEH_SHIP:     ship++; break;
 				default: break;
--- a/src/road_cmd.cpp
+++ b/src/road_cmd.cpp
@@ -13,6 +13,7 @@
 #include "table/sprites.h"
 #include "table/strings.h"
 #include "functions.h"
+#include "window.h"
 #include "map.h"
 #include "landscape.h"
 #include "tile.h"
@@ -1359,7 +1360,13 @@
 			if (v->type == VEH_ROAD &&
 					v->u.road.frame == 11 &&
 					_roadveh_enter_depot_dir[GetRoadDepotDirection(tile)] == v->u.road.state) {
-				VehicleEnterDepot(v);
+				v->u.road.state = RVSB_IN_DEPOT;
+				v->vehstatus |= VS_HIDDEN;
+				v->direction = ReverseDir(v->direction);
+				if (v->next == NULL) VehicleEnterDepot(v);
+				v->tile = tile;
+
+				InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile);
 				return VETSB_ENTERED_WORMHOLE;
 			}
 			break;
--- a/src/roadveh.h
+++ b/src/roadveh.h
@@ -8,6 +8,42 @@
 #include "vehicle.h"
 
 
+enum RoadVehicleSubType {
+	RVST_FRONT,
+	RVST_ARTIC_PART,
+};
+
+static inline bool IsRoadVehFront(const Vehicle *v)
+{
+	assert(v->type == VEH_ROAD);
+	return v->subtype == RVST_FRONT;
+}
+
+static inline void SetRoadVehFront(Vehicle *v)
+{
+	assert(v->type == VEH_ROAD);
+	v->subtype = RVST_FRONT;
+}
+
+static inline bool IsRoadVehArticPart(const Vehicle *v)
+{
+	assert(v->type == VEH_ROAD);
+	return v->subtype == RVST_ARTIC_PART;
+}
+
+static inline void SetRoadVehArticPart(Vehicle *v)
+{
+	assert(v->type == VEH_ROAD);
+	v->subtype = RVST_ARTIC_PART;
+}
+
+static inline bool RoadVehHasArticPart(const Vehicle *v)
+{
+	assert(v->type == VEH_ROAD);
+	return v->next != NULL && IsRoadVehArticPart(v->next);
+}
+
+
 static inline bool IsRoadVehInDepot(const Vehicle* v)
 {
 	assert(v->type == VEH_ROAD);
@@ -43,7 +79,12 @@
 	void UpdateDeltaXY(Direction direction);
 	ExpensesType GetExpenseType(bool income) const { return income ? EXPENSES_ROADVEH_INC : EXPENSES_ROADVEH_RUN; }
 	WindowClass GetVehicleListWindowClass() const { return WC_ROADVEH_LIST; }
-	bool IsPrimaryVehicle() const { return true; }
+	bool IsPrimaryVehicle() const { return IsRoadVehFront(this); }
+	bool HasFront() const { return true; }
 };
 
+byte GetRoadVehLength(const Vehicle *v);
+
+void RoadVehUpdateCache(Vehicle *v);
+
 #endif /* ROADVEH_H */
--- a/src/roadveh_cmd.cpp
+++ b/src/roadveh_cmd.cpp
@@ -27,6 +27,7 @@
 #include "tunnel_map.h"
 #include "bridge_map.h"
 #include "vehicle_gui.h"
+#include "articulated_vehicles.h"
 #include "newgrf_callbacks.h"
 #include "newgrf_engine.h"
 #include "newgrf_text.h"
@@ -89,7 +90,7 @@
 	int image;
 
 	if (is_custom_sprite(img)) {
-		image = GetCustomVehicleSprite(v, direction);
+		image = GetCustomVehicleSprite(v, (Direction)(direction + 4 * IS_CUSTOM_SECONDHEAD_SPRITE(img)));
 		if (image != 0) return image;
 		img = orig_road_vehicle_info[v->engine_type - ROAD_ENGINES_INDEX].image_index;
 	}
@@ -120,6 +121,35 @@
 	return ((_price.roadveh_base >> 3) * GetEngineProperty(engine_type, 0x11, RoadVehInfo(engine_type)->base_cost)) >> 5;
 }
 
+byte GetRoadVehLength(const Vehicle *v)
+{
+	byte length = 8;
+
+	uint16 veh_len = GetVehicleCallback(CBID_TRAIN_VEHICLE_LENGTH, 0, 0, v->engine_type, v);
+	if (veh_len != CALLBACK_FAILED) {
+		length -= clamp(veh_len, 0, 7);
+	}
+
+	return length;
+}
+
+void RoadVehUpdateCache(Vehicle *v)
+{
+	assert(v->type == VEH_ROAD);
+	assert(IsRoadVehFront(v));
+
+	for (Vehicle *u = v; u != NULL; u = u->next) {
+		/* Update the v->first cache. */
+		if (u->first == NULL) u->first = v;
+
+		/* Update the 'first engine' */
+		u->u.road.first_engine = (v == u) ? INVALID_ENGINE : v->engine_type;
+
+		/* Update the length of the vehicle. */
+		u->u.road.cached_veh_length = GetRoadVehLength(u);
+	}
+}
+
 /** Build a road vehicle.
  * @param tile tile of depot where road vehicle is built
  * @param flags operation to perform
@@ -147,8 +177,17 @@
 
 	if (HASBIT(GetRoadTypes(tile), ROADTYPE_TRAM) != HASBIT(EngInfo(p1)->misc_flags, EF_ROAD_TRAM)) return_cmd_error(STR_DEPOT_WRONG_DEPOT_TYPE);
 
-	v = AllocateVehicle();
-	if (v == NULL) return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME);
+	uint num_vehicles = 1 + CountArticulatedParts(p1);
+
+	/* Allow for the front and up to 10 articulated parts. */
+	Vehicle *vl[11];
+	memset(&vl, 0, sizeof(vl));
+
+	if (!AllocateVehicles(vl, num_vehicles)) {
+		return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME);
+	}
+
+	v = vl[0];
 
 	/* find the first free roadveh id */
 	unit_num = HASBIT(p2, 0) ? 0 : GetFreeUnitNumber(VEH_ROAD);
@@ -162,7 +201,7 @@
 		const RoadVehicleInfo *rvi = RoadVehInfo(p1);
 
 		v->unitnumber = unit_num;
-		v->direction = INVALID_DIR;
+		v->direction = DiagDirToDir(GetRoadDepotDirection(tile));
 		v->owner = _current_player;
 
 		v->tile = tile;
@@ -193,9 +232,6 @@
 		v->max_speed = rvi->max_speed;
 		v->engine_type = (byte)p1;
 
-		v->u.road.roadtype = HASBIT(EngInfo(v->engine_type)->misc_flags, EF_ROAD_TRAM) ? ROADTYPE_TRAM : ROADTYPE_ROAD;
-		v->u.road.compatible_roadtypes = RoadTypeToRoadTypes(v->u.road.roadtype);
-
 		e = GetEngine(p1);
 		v->reliability = e->reliability;
 		v->reliability_spd_dec = e->reliability_spd_dec;
@@ -212,12 +248,20 @@
 		v = new (v) RoadVehicle();
 		v->cur_image = 0xC15;
 		v->random_bits = VehicleRandomBits();
+		SetRoadVehFront(v);
+
+		v->u.road.roadtype = HASBIT(EngInfo(v->engine_type)->misc_flags, EF_ROAD_TRAM) ? ROADTYPE_TRAM : ROADTYPE_ROAD;
+		v->u.road.compatible_roadtypes = RoadTypeToRoadTypes(v->u.road.roadtype);
+		v->u.road.cached_veh_length = GetRoadVehLength(v);
 
 		v->vehicle_flags = 0;
 		if (e->flags & ENGINE_EXCLUSIVE_PREVIEW) SETBIT(v->vehicle_flags, VF_BUILT_AS_PROTOTYPE);
 
+		v->first = NULL;
 		v->cargo_cap = GetVehicleProperty(v, 0x0F, rvi->capacity);
 
+		AddArticulatedParts(vl, VEH_ROAD);
+
 		VehiclePositionChanged(v);
 
 		InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile);
@@ -285,6 +329,18 @@
 	DEBUG(ms, 3, "Clearing slot at 0x%X", rs->xy);
 }
 
+static bool CheckRoadVehInDepotStopped(const Vehicle *v)
+{
+	TileIndex tile = v->tile;
+
+	if (!IsTileDepotType(tile, TRANSPORT_ROAD) || !(v->vehstatus & VS_STOPPED)) return false;
+
+	for (; v != NULL; v = v->next) {
+		if (v->u.road.state != RVSB_IN_DEPOT || v->tile != tile) return false;
+	}
+	return true;
+}
+
 /** Sell a road vehicle.
  * @param tile unused
  * @param flags operation to perform
@@ -303,7 +359,7 @@
 
 	SET_EXPENSES_TYPE(EXPENSES_NEW_VEHICLES);
 
-	if (!IsRoadVehInDepotStopped(v)) {
+	if (!CheckRoadVehInDepotStopped(v)) {
 		return_cmd_error(STR_9013_MUST_BE_STOPPED_INSIDE);
 	}
 
@@ -536,8 +592,12 @@
 	rs->FreeBay(HASBIT(v->u.road.state, RVS_USING_SECOND_BAY));
 }
 
-static void RoadVehDelete(Vehicle *v)
+static void DeleteLastRoadVeh(Vehicle *v)
 {
+	Vehicle *u = v;
+	for (; v->next != NULL; v = v->next) u = v;
+	u->next = NULL;
+
 	DeleteWindowById(WC_VEHICLE_VIEW, v->index);
 
 	RebuildVehicleLists();
@@ -574,13 +634,15 @@
 		DIRDIFF_45LEFT, DIRDIFF_SAME, DIRDIFF_SAME, DIRDIFF_45RIGHT
 	};
 
-	uint32 r = Random();
+	do {
+		uint32 r = Random();
 
-	v->direction = ChangeDir(v->direction, delta[r & 3]);
-	BeginVehicleMove(v);
-	v->UpdateDeltaXY(v->direction);
-	v->cur_image = GetRoadVehImage(v, v->direction);
-	SetRoadVehPosition(v, v->x_pos, v->y_pos);
+		v->direction = ChangeDir(v->direction, delta[r & 3]);
+		BeginVehicleMove(v);
+		v->UpdateDeltaXY(v->direction);
+		v->cur_image = GetRoadVehImage(v, v->direction);
+		SetRoadVehPosition(v, v->x_pos, v->y_pos);
+	} while ((v = v->next) != NULL);
 }
 
 static void RoadVehIsCrashed(Vehicle *v)
@@ -590,8 +652,8 @@
 		CreateEffectVehicleRel(v, 4, 4, 8, EV_EXPLOSION_LARGE);
 	} else if (v->u.road.crashed_ctr <= 45) {
 		if ((v->tick_counter & 7) == 0) RoadVehSetRandomDirection(v);
-	} else if (v->u.road.crashed_ctr >= 2220) {
-		RoadVehDelete(v);
+	} else if (v->u.road.crashed_ctr >= 2220 && !(v->tick_counter & 0x1F)) {
+		DeleteLastRoadVeh(v);
 	}
 }
 
@@ -609,18 +671,22 @@
 
 static void RoadVehCrash(Vehicle *v)
 {
-	uint16 pass;
+	uint16 pass = 1;
 
 	v->u.road.crashed_ctr++;
-	v->vehstatus |= VS_CRASHED;
+
+	for (Vehicle *u = v; u != NULL; u = u->next) {
+		if (IsCargoInClass(u->cargo_type, CC_PASSENGERS)) pass += u->cargo_count;
+
+		u->vehstatus |= VS_CRASHED;
+
+		MarkAllViewportsDirty(u->left_coord, u->top_coord, u->right_coord + 1, u->bottom_coord + 1);
+	}
+
 	ClearSlot(v);
 
 	InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR);
 
-	pass = 1;
-	if (IsCargoInClass(v->cargo_type, CC_PASSENGERS)) pass += v->cargo_count;
-	v->cargo_count = 0;
-
 	SetDParam(0, pass);
 	AddNewsItem(
 		(pass == 1) ?
@@ -636,16 +702,18 @@
 
 static void RoadVehCheckTrainCrash(Vehicle *v)
 {
-	TileIndex tile;
+	for (Vehicle *u = v; u != NULL; u = u->next) {
+		if (u->u.road.state == RVSB_WORMHOLE) continue;
 
-	if (v->u.road.state == RVSB_WORMHOLE) return;
+		TileIndex tile = u->tile;
 
-	tile = v->tile;
+		if (!IsLevelCrossingTile(tile)) continue;
 
-	if (!IsLevelCrossingTile(tile)) return;
-
-	if (VehicleFromPos(tile, v, EnumCheckRoadVehCrashTrain) != NULL)
-		RoadVehCrash(v);
+		if (VehicleFromPos(tile, u, EnumCheckRoadVehCrashTrain) != NULL) {
+			RoadVehCrash(v);
+			return;
+		}
+	}
 }
 
 static void HandleBrokenRoadVeh(Vehicle *v)
@@ -798,11 +866,11 @@
 	short y_diff = v->y_pos - rvf->y;
 
 	return
-		rvf->veh != v &&
 		v->type == VEH_ROAD &&
 		!IsRoadVehInDepot(v) &&
 		myabs(v->z_pos - rvf->veh->z_pos) < 6 &&
 		v->direction == rvf->dir &&
+		GetFirstVehicleInChain(rvf->veh) != GetFirstVehicleInChain(v) &&
 		(dist_x[v->direction] >= 0 || (x_diff > dist_x[v->direction] && x_diff <= 0)) &&
 		(dist_x[v->direction] <= 0 || (x_diff < dist_x[v->direction] && x_diff >= 0)) &&
 		(dist_y[v->direction] >= 0 || (y_diff > dist_y[v->direction] && y_diff <= 0)) &&
@@ -972,6 +1040,9 @@
 	/* Trams can't overtake other trams */
 	if (v->u.road.roadtype == ROADTYPE_TRAM) return;
 
+	/* For now, articulated road vehicles can't overtake anything. */
+	if (RoadVehHasArticPart(v)) return;
+
 	if (v->direction != u->direction || !(v->direction & 1)) return;
 
 	/* Check if vehicle is in a road stop, depot, tunnel or bridge or not on a straight road */
@@ -1267,7 +1338,95 @@
 	15, 15, 11, 11
 };
 
-static void RoadVehController(Vehicle *v)
+static bool RoadVehLeaveDepot(Vehicle *v, bool first)
+{
+	/* Don't leave if not all the wagons are in the depot. */
+	for (const Vehicle *u = v; u != NULL; u = u->next) {
+		if (u->u.road.state != RVSB_IN_DEPOT || u->tile != v->tile) return false;
+	}
+
+	DiagDirection dir = GetRoadDepotDirection(v->tile);
+	v->direction = DiagDirToDir(dir);
+
+	Trackdir tdir = _roadveh_depot_exit_trackdir[dir];
+	const RoadDriveEntry *rdp = _road_drive_data[v->u.road.roadtype][(_opt.road_side << RVS_DRIVE_SIDE) + tdir];
+
+	int x = TileX(v->tile) * TILE_SIZE + (rdp[RVC_DEPOT_START_FRAME].x & 0xF);
+	int y = TileY(v->tile) * TILE_SIZE + (rdp[RVC_DEPOT_START_FRAME].y & 0xF);
+
+	if (first) {
+		if (RoadVehFindCloseTo(v, x, y, v->direction) != NULL) return true;
+
+		VehicleServiceInDepot(v);
+
+		StartRoadVehSound(v);
+
+		/* Vehicle is about to leave a depot */
+		v->cur_speed = 0;
+	}
+
+	BeginVehicleMove(v);
+
+	v->vehstatus &= ~VS_HIDDEN;
+	v->u.road.state = tdir;
+	v->u.road.frame = RVC_DEPOT_START_FRAME;
+
+	v->cur_image = GetRoadVehImage(v, v->direction);
+	v->UpdateDeltaXY(v->direction);
+	SetRoadVehPosition(v,x,y);
+
+	InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile);
+
+	return true;
+}
+
+static Trackdir FollowPreviousRoadVehicle(const Vehicle *v, const Vehicle *prev, TileIndex tile, DiagDirection entry_dir)
+{
+	if (prev->tile == v->tile) {
+		/* If the previous vehicle is on the same tile as this vehicle is
+		 * then it must have reversed. */
+		return _road_reverse_table[entry_dir];
+	}
+
+	byte prev_state = prev->u.road.state;
+	Trackdir dir;
+
+	if (prev_state == RVSB_WORMHOLE || prev_state == RVSB_IN_DEPOT) {
+		DiagDirection diag_dir = INVALID_DIAGDIR;
+
+		if (IsTunnelTile(tile)) {
+			diag_dir = GetTunnelDirection(tile);
+		} else if (IsBridgeTile(tile)) {
+			diag_dir = GetBridgeRampDirection(tile);
+		} else if (IsTileType(tile, MP_STREET) && GetRoadTileType(tile) == ROAD_TILE_DEPOT) {
+			diag_dir = ReverseDiagDir(GetRoadDepotDirection(tile));
+		}
+
+		if (diag_dir == INVALID_DIAGDIR) return INVALID_TRACKDIR;
+		dir = DiagdirToDiagTrackdir(diag_dir);
+	} else if (HASBIT(prev_state, RVS_IN_DT_ROAD_STOP)) {
+		dir = (Trackdir)(prev_state & RVSB_ROAD_STOP_TRACKDIR_MASK);
+	} else if (prev_state < TRACKDIR_END) {
+		dir = (Trackdir)prev_state;
+	} else {
+		return INVALID_TRACKDIR;
+	}
+
+	/* Do some sanity checking. */
+	static const RoadBits required_roadbits[] = {
+		ROAD_X,            ROAD_Y,            ROAD_NW | ROAD_NE, ROAD_SW | ROAD_SE,
+		ROAD_NW | ROAD_SW, ROAD_NE | ROAD_SE, ROAD_X,            ROAD_Y
+	};
+	RoadBits required = required_roadbits[dir & 0x07];
+
+	if ((required & GetAnyRoadBits(tile, v->u.road.roadtype)) == ROAD_NONE) {
+		dir = INVALID_TRACKDIR;
+	}
+
+	return dir;
+}
+
+static bool IndividualRoadVehicleController(Vehicle *v, const Vehicle *prev)
 {
 	Direction new_dir;
 	Direction old_dir;
@@ -1275,74 +1434,6 @@
 	int x,y;
 	uint32 r;
 
-	/* decrease counters */
-	v->tick_counter++;
-	if (v->u.road.reverse_ctr != 0) v->u.road.reverse_ctr--;
-
-	/* handle crashed */
-	if (v->u.road.crashed_ctr != 0) {
-		RoadVehIsCrashed(v);
-		return;
-	}
-
-	RoadVehCheckTrainCrash(v);
-
-	/* road vehicle has broken down? */
-	if (v->breakdown_ctr != 0) {
-		if (v->breakdown_ctr <= 2) {
-			HandleBrokenRoadVeh(v);
-			return;
-		}
-		v->breakdown_ctr--;
-	}
-
-	if (v->vehstatus & VS_STOPPED) return;
-
-	ProcessRoadVehOrder(v);
-	v->HandleLoading();
-
-	if (v->current_order.type == OT_LOADING) return;
-
-	if (IsRoadVehInDepot(v)) {
-		/* Vehicle is about to leave a depot */
-		DiagDirection dir;
-		const RoadDriveEntry* rdp;
-		Trackdir tdir;
-
-		v->cur_speed = 0;
-
-		dir = GetRoadDepotDirection(v->tile);
-		v->direction = DiagDirToDir(dir);
-
-		tdir = _roadveh_depot_exit_trackdir[dir];
-		rdp = _road_drive_data[v->u.road.roadtype][(_opt.road_side << RVS_DRIVE_SIDE) + tdir];
-
-		x = TileX(v->tile) * TILE_SIZE + (rdp[RVC_DEPOT_START_FRAME].x & 0xF);
-		y = TileY(v->tile) * TILE_SIZE + (rdp[RVC_DEPOT_START_FRAME].y & 0xF);
-
-		if (RoadVehFindCloseTo(v, x, y, v->direction) != NULL) return;
-
-		VehicleServiceInDepot(v);
-
-		StartRoadVehSound(v);
-
-		BeginVehicleMove(v);
-
-		v->vehstatus &= ~VS_HIDDEN;
-		v->u.road.state = tdir;
-		v->u.road.frame = RVC_DEPOT_START_FRAME;
-
-		v->cur_image = GetRoadVehImage(v, v->direction);
-		v->UpdateDeltaXY(v->direction);
-		SetRoadVehPosition(v,x,y);
-
-		InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile);
-		return;
-	}
-
-	/* Check if vehicle needs to proceed, return if it doesn't */
-	if (!RoadVehAccelerate(v)) return;
-
 	if (v->u.road.overtaking != 0)  {
 		if (++v->u.road.overtaking_ctr >= 35)
 			/* If overtaking just aborts at a random moment, we can have a out-of-bound problem,
@@ -1353,6 +1444,11 @@
 			}
 	}
 
+	/* If this vehicle is in a depot and we've reached this point it must be
+	 * one of the articulated parts. It will stay in the depot until activated
+	 * by the previous vehicle in the chain when it gets to the right place. */
+	if (IsRoadVehInDepot(v)) return true;
+
 	/* Save old vehicle position to use at end of move to set viewport area dirty */
 	BeginVehicleMove(v);
 
@@ -1363,7 +1459,7 @@
 		const Vehicle *u = RoadVehFindCloseTo(v, gp.x, gp.y, v->direction);
 		if (u != NULL && u->cur_speed < v->cur_speed) {
 			v->cur_speed = u->cur_speed;
-			return;
+			return false;
 		}
 
 		if ((IsTunnelTile(gp.new_tile) || IsBridgeTile(gp.new_tile)) && HASBIT(VehicleEnterTile(v, gp.new_tile, gp.x, gp.y), VETS_ENTERED_WORMHOLE)) {
@@ -1371,14 +1467,14 @@
 			v->cur_image = GetRoadVehImage(v, v->direction);
 			v->UpdateDeltaXY(v->direction);
 			SetRoadVehPosition(v,gp.x,gp.y);
-			return;
+			return true;
 		}
 
 		v->x_pos = gp.x;
 		v->y_pos = gp.y;
 		VehiclePositionChanged(v);
 		if (!(v->vehstatus & VS_HIDDEN)) EndVehicleMove(v);
-		return;
+		return true;
 	}
 
 	/* Get move position data for next frame.
@@ -1390,14 +1486,22 @@
 
 	if (rd.x & RDE_NEXT_TILE) {
 		TileIndex tile = v->tile + TileOffsByDiagDir(rd.x & 3);
-		Trackdir dir = RoadFindPathToDest(v, tile, (DiagDirection)(rd.x & 3));
+		Trackdir dir;
 		uint32 r;
 		Direction newdir;
 		const RoadDriveEntry *rdp;
 
+		if (IsRoadVehFront(v)) {
+			/* If this is the front engine, look for the right path. */
+			dir = RoadFindPathToDest(v, tile, (DiagDirection)(rd.x & 3));
+		} else {
+			dir = FollowPreviousRoadVehicle(v, prev, tile, (DiagDirection)(rd.x & 3));
+		}
+
 		if (dir == INVALID_TRACKDIR) {
+			if (!IsRoadVehFront(v)) error("!Disconnecting road vehicle.");
 			v->cur_speed = 0;
-			return;
+			return false;
 		}
 
 again:
@@ -1415,11 +1519,11 @@
 				if (!IsTileType(tile, MP_STREET) || GetRoadTileType(tile) != ROAD_TILE_NORMAL || HasRoadWorks(tile) || (needed & GetRoadBits(tile, ROADTYPE_TRAM)) == ROAD_NONE) {
 					/* The tram cannot turn here */
 					v->cur_speed = 0;
-					return;
+					return false;
 				}
 			} else if (IsTileType(v->tile, MP_STREET) && GetRoadTileType(v->tile) == ROAD_TILE_NORMAL && GetDisallowedRoadDirections(v->tile) != DRD_NONE) {
 				v->cur_speed = 0;
-				return;
+				return false;
 			} else {
 				tile = v->tile;
 			}
@@ -1432,13 +1536,13 @@
 		y = TileY(tile) * TILE_SIZE + rdp[RVC_DEFAULT_START_FRAME].y;
 
 		newdir = RoadVehGetSlidingDirection(v, x, y);
-		if (RoadVehFindCloseTo(v, x, y, newdir) != NULL) return;
+		if (IsRoadVehFront(v) && RoadVehFindCloseTo(v, x, y, newdir) != NULL) return false;
 
 		r = VehicleEnterTile(v, tile, x, y);
 		if (HASBIT(r, VETS_CANNOT_ENTER)) {
 			if (!IsTileType(tile, MP_TUNNELBRIDGE)) {
 				v->cur_speed = 0;
-				return;
+				return false;
 			}
 			/* Try an about turn to re-enter the previous tile */
 			dir = _road_reverse_table[rd.x & 3];
@@ -1450,7 +1554,7 @@
 				/* New direction is trying to turn vehicle around.
 				 * We can't turn at the exit of a road stop so wait.*/
 				v->cur_speed = 0;
-				return;
+				return false;
 			}
 			if (IsRoadStop(v->tile)) {
 				RoadStop *rs = GetRoadStopByTile(v->tile, GetRoadStopType(v->tile));
@@ -1478,7 +1582,7 @@
 		v->cur_image = GetRoadVehImage(v, newdir);
 		v->UpdateDeltaXY(v->direction);
 		RoadZPosAffectSpeed(v, SetRoadVehPosition(v, x, y));
-		return;
+		return true;
 	}
 
 	if (rd.x & RDE_TURNED) {
@@ -1490,7 +1594,7 @@
 
 		if (dir == INVALID_TRACKDIR) {
 			v->cur_speed = 0;
-			return;
+			return false;
 		}
 
 		rdp = _road_drive_data[v->u.road.roadtype][(_opt.road_side << RVS_DRIVE_SIDE) + dir];
@@ -1499,12 +1603,12 @@
 		y = TileY(v->tile) * TILE_SIZE + rdp[RVC_TURN_AROUND_START_FRAME].y;
 
 		newdir = RoadVehGetSlidingDirection(v, x, y);
-		if (RoadVehFindCloseTo(v, x, y, newdir) != NULL) return;
+		if (IsRoadVehFront(v) && RoadVehFindCloseTo(v, x, y, newdir) != NULL) return false;
 
 		r = VehicleEnterTile(v, v->tile, x, y);
 		if (HASBIT(r, VETS_CANNOT_ENTER)) {
 			v->cur_speed = 0;
-			return;
+			return false;
 		}
 
 		v->u.road.state = dir;
@@ -1518,7 +1622,18 @@
 		v->cur_image = GetRoadVehImage(v, newdir);
 		v->UpdateDeltaXY(v->direction);
 		RoadZPosAffectSpeed(v, SetRoadVehPosition(v, x, y));
-		return;
+		return true;
+	}
+
+	/* This vehicle is not in a wormhole and it hasn't entered a new tile. If
+	 * it's on a depot tile, check if it's time to activate the next vehicle in
+	 * the chain yet. */
+	if (v->next != NULL &&
+			IsTileType(v->tile, MP_STREET) && GetRoadTileType(v->tile) == ROAD_TILE_DEPOT) {
+
+		if (v->u.road.frame == v->u.road.cached_veh_length + RVC_DEPOT_START_FRAME) {
+			RoadVehLeaveDepot(v->next, false);
+		}
 	}
 
 	/* Calculate new position for the vehicle */
@@ -1527,7 +1642,7 @@
 
 	new_dir = RoadVehGetSlidingDirection(v, x, y);
 
-	if (!IS_BYTE_INSIDE(v->u.road.state, RVSB_IN_ROAD_STOP, RVSB_IN_ROAD_STOP_END)) {
+	if (IsRoadVehFront(v) && !IS_BYTE_INSIDE(v->u.road.state, RVSB_IN_ROAD_STOP, RVSB_IN_ROAD_STOP_END)) {
 		/* Vehicle is not in a road stop.
 		 * Check for another vehicle to overtake */
 		Vehicle* u = RoadVehFindCloseTo(v, x, y, new_dir);
@@ -1536,7 +1651,7 @@
 			v->cur_speed = u->cur_speed;
 			/* There is a vehicle in front overtake it if possible */
 			if (v->u.road.overtaking == 0) RoadVehCheckOvertake(v, u);
-			return;
+			return false;
 		}
 	}
 
@@ -1552,7 +1667,7 @@
 			/* Note, return here means that the frame counter is not incremented
 			 * for vehicles changing direction in a road stop. This causes frames to
 			 * be repeated. (XXX) Is this intended? */
-			return;
+			return true;
 		}
 	}
 
@@ -1561,12 +1676,12 @@
 	 * and it's the correct type of stop (bus or truck) and the frame equals the stop frame...
 	 * (the station test and stop type test ensure that other vehicles, using the road stop as
 	 * a through route, do not stop) */
-	if ((IS_BYTE_INSIDE(v->u.road.state, RVSB_IN_ROAD_STOP, RVSB_IN_ROAD_STOP_END) &&
+	if (IsRoadVehFront(v) && ((IS_BYTE_INSIDE(v->u.road.state, RVSB_IN_ROAD_STOP, RVSB_IN_ROAD_STOP_END) &&
 			_road_veh_data_1[v->u.road.state - RVSB_IN_ROAD_STOP + (_opt.road_side << RVS_DRIVE_SIDE)] == v->u.road.frame) ||
 			(IS_BYTE_INSIDE(v->u.road.state, RVSB_IN_DT_ROAD_STOP, RVSB_IN_DT_ROAD_STOP_END) &&
 			v->current_order.dest == GetStationIndex(v->tile) &&
 			GetRoadStopType(v->tile) == (IsCargoInClass(v->cargo_type, CC_PASSENGERS) ? RoadStop::BUS : RoadStop::TRUCK) &&
-			v->u.road.frame == RVC_DRIVE_THROUGH_STOP_FRAME)) {
+			v->u.road.frame == RVC_DRIVE_THROUGH_STOP_FRAME))) {
 
 		RoadStop *rs = GetRoadStopByTile(v->tile, GetRoadStopType(v->tile));
 		Station* st = GetStationByTile(v->tile);
@@ -1596,7 +1711,7 @@
 
 						v->u.road.frame++;
 						RoadZPosAffectSpeed(v, SetRoadVehPosition(v, x, y));
-						return;
+						return true;
 					}
 				}
 			}
@@ -1608,7 +1723,7 @@
 			RoadVehArrivesAt(v, st);
 			v->BeginLoading();
 
-			return;
+			return false;
 		}
 
 		/* Vehicle is ready to leave a bay in a road stop */
@@ -1616,7 +1731,7 @@
 			if (rs->IsEntranceBusy()) {
 				/* Road stop entrance is busy, so wait as there is nowhere else to go */
 				v->cur_speed = 0;
-				return;
+				return false;
 			}
 			v->current_order.Free();
 			ClearSlot(v);
@@ -1659,7 +1774,7 @@
 	r = VehicleEnterTile(v, v->tile, x, y);
 	if (HASBIT(r, VETS_CANNOT_ENTER)) {
 		v->cur_speed = 0;
-		return;
+		return false;
 	}
 
 	/* Move to next frame unless vehicle arrived at a stop position
@@ -1669,6 +1784,47 @@
 	v->cur_image = GetRoadVehImage(v, v->direction);
 	v->UpdateDeltaXY(v->direction);
 	RoadZPosAffectSpeed(v, SetRoadVehPosition(v, x, y));
+	return true;
+}
+
+static void RoadVehController(Vehicle *v)
+{
+	/* decrease counters */
+	v->tick_counter++;
+	if (v->u.road.reverse_ctr != 0) v->u.road.reverse_ctr--;
+
+	/* handle crashed */
+	if (v->u.road.crashed_ctr != 0) {
+		RoadVehIsCrashed(v);
+		return;
+	}
+
+	RoadVehCheckTrainCrash(v);
+
+	/* road vehicle has broken down? */
+	if (v->breakdown_ctr != 0) {
+		if (v->breakdown_ctr <= 2) {
+			HandleBrokenRoadVeh(v);
+			return;
+		}
+		v->breakdown_ctr--;
+	}
+
+	if (v->vehstatus & VS_STOPPED) return;
+
+	ProcessRoadVehOrder(v);
+	v->HandleLoading();
+
+	if (v->current_order.type == OT_LOADING) return;
+
+	if (IsRoadVehInDepot(v) && RoadVehLeaveDepot(v, true)) return;
+
+	/* Check if vehicle needs to proceed, return if it doesn't */
+	if (!RoadVehAccelerate(v)) return;
+
+	for (Vehicle *prev = NULL; v != NULL; prev = v, v = v->next) {
+		if (!IndividualRoadVehicleController(v, prev)) break;
+	}
 }
 
 static void AgeRoadVehCargo(Vehicle *v)
@@ -1680,7 +1836,8 @@
 void RoadVeh_Tick(Vehicle *v)
 {
 	AgeRoadVehCargo(v);
-	RoadVehController(v);
+
+	if (IsRoadVehFront(v)) RoadVehController(v);
 }
 
 static void CheckIfRoadVehNeedsService(Vehicle *v)
@@ -1738,6 +1895,8 @@
 {
 	int32 cost;
 
+	if (!IsRoadVehFront(v)) return;
+
 	if ((++v->day_counter & 7) == 0) DecreaseVehicleValue(v);
 	if (v->u.road.blocked_ctr == 0) CheckVehicleBreakdown(v);
 
@@ -1862,7 +2021,7 @@
 	v = GetVehicle(p1);
 
 	if (v->type != VEH_ROAD || !CheckOwnership(v->owner)) return CMD_ERROR;
-	if (!IsRoadVehInDepotStopped(v)) return_cmd_error(STR_9013_MUST_BE_STOPPED_INSIDE);
+	if (!CheckRoadVehInDepotStopped(v)) return_cmd_error(STR_9013_MUST_BE_STOPPED_INSIDE);
 
 	if (new_cid >= NUM_CARGO || !CanRefitTo(v->engine_type, new_cid)) return CMD_ERROR;
 
--- a/src/roadveh_gui.cpp
+++ b/src/roadveh_gui.cpp
@@ -11,6 +11,7 @@
 #include "table/strings.h"
 #include "window.h"
 #include "gui.h"
+#include "strings.h"
 #include "vehicle.h"
 #include "viewport.h"
 #include "command.h"
@@ -18,22 +19,61 @@
 #include "vehicle_gui.h"
 #include "newgrf_engine.h"
 
-void DrawRoadVehImage(const Vehicle *v, int x, int y, VehicleID selection)
+static inline int RoadVehLengthToPixels(int length)
+{
+	return (length * 28) / 8;
+}
+
+void DrawRoadVehImage(const Vehicle *v, int x, int y, int count, VehicleID selection)
 {
-	SpriteID pal = (v->vehstatus & VS_CRASHED) ? PALETTE_CRASH : GetVehiclePalette(v);
-	DrawSprite(GetRoadVehImage(v, DIR_W), pal, x + 14, y + 6);
+	int dx = 0;
+
+	/* Road vehicle lengths are measured in eighths of the standard length, so
+	 * count is the number of standard vehicles that should be drawn. If it is
+	 * 0, we draw enough vehicles for 10 standard vehicle lengths. */
+	int max_length = (count == 0) ? 80 : count * 8;
+
+	do {
+		int length = v->u.road.cached_veh_length;
 
-	if (v->index == selection) {
-		DrawFrameRect(x - 1, y - 1, x + 28, y + 12, 15, FR_BORDERONLY);
-	}
+		if (dx + length > 0 && dx <= max_length) {
+			SpriteID pal = (v->vehstatus & VS_CRASHED) ? PALETTE_CRASH : GetVehiclePalette(v);
+			DrawSprite(GetRoadVehImage(v, DIR_W), pal, x + 14 + RoadVehLengthToPixels(dx), y + 6);
+
+			if (v->index == selection) {
+				DrawFrameRect(x - 1, y - 1, x + 28, y + 12, 15, FR_BORDERONLY);
+			}
+		}
+
+		dx += length;
+		v = v->next;
+	} while (v != NULL && dx < max_length);
 }
 
 static void RoadVehDetailsWndProc(Window *w, WindowEvent *e)
 {
 	switch (e->event) {
+	case WE_CREATE: {
+		const Vehicle *v = GetVehicle(w->window_number);
+
+		if (!RoadVehHasArticPart(v)) break;
+
+		/* Draw the text under the vehicle instead of next to it, minus the
+		 * height already allocated for the cargo of the first vehicle. */
+		uint height_extension = 15 - 11;
+
+		/* Add space for the cargo amount for each part. */
+		do {
+			height_extension += 11;
+		} while ((v = v->next) != NULL);
+
+		ResizeWindow(w, 0, height_extension);
+	} break;
+
 	case WE_PAINT: {
 		const Vehicle *v = GetVehicle(w->window_number);
 		StringID str;
+		uint y_offset = RoadVehHasArticPart(v) ? 15 :0;
 
 		SetWindowWidgetDisabledState(w, 2, v->owner != _local_player);
 		/* disable service-scroller when interval is set to disabled */
@@ -76,37 +116,81 @@
 			DrawString(2, 45, STR_9010_RELIABILITY_BREAKDOWNS, 0);
 		}
 
-		/* Draw service interval text */
-		{
-			SetDParam(0, v->service_interval);
-			SetDParam(1, v->date_of_last_service);
-			DrawString(13, 102, _patches.servint_ispercent?STR_SERVICING_INTERVAL_PERCENT:STR_883C_SERVICING_INTERVAL_DAYS, 0);
-		}
-
-		DrawRoadVehImage(v, 3, 57, INVALID_VEHICLE);
+		DrawRoadVehImage(v, 3, 57, 0, INVALID_VEHICLE);
 
 		SetDParam(0, GetCustomEngineName(v->engine_type));
 		SetDParam(1, v->build_year);
 		SetDParam(2, v->value);
-		DrawString(34, 57, STR_9011_BUILT_VALUE, 0);
+		DrawString(34, 57 + y_offset, STR_9011_BUILT_VALUE, 0);
+
+		if (RoadVehHasArticPart(v)) {
+			AcceptedCargo max_cargo;
+			char capacity[512];
+
+			memset(max_cargo, 0, sizeof(max_cargo));
+
+			for (const Vehicle *u = v; u != NULL; u = u->next) {
+				max_cargo[u->cargo_type] += u->cargo_cap;
+			}
+
+			GetString(capacity, STR_ARTICULATED_RV_CAPACITY, lastof(capacity));
 
-		SetDParam(0, v->cargo_type);
-		SetDParam(1, v->cargo_cap);
-		DrawString(34, 67, STR_9012_CAPACITY, 0);
+			bool first = true;
+			for (CargoID i = 0; i < NUM_CARGO; i++) {
+				if (max_cargo[i] > 0) {
+					char buffer[128];
+
+					SetDParam(0, i);
+					SetDParam(1, max_cargo[i]);
+					GetString(buffer, STR_BARE_CARGO, lastof(buffer));
+
+					if (!first) strecat(capacity, ", ", lastof(capacity));
+					strecat(capacity, buffer, lastof(capacity));
+				}
+			}
 
-		str = STR_8812_EMPTY;
-		if (v->cargo_count != 0) {
+			SetDParamStr(0, capacity);
+			DrawStringTruncated(34, 67 + y_offset, STR_JUST_STRING, 0, w->width - 34);
+
+			for (const Vehicle *u = v; u != NULL; u = u->next) {
+				str = STR_8812_EMPTY;
+				if (u->cargo_count != 0) {
+					SetDParam(0, u->cargo_type);
+					SetDParam(1, u->cargo_count);
+					SetDParam(2, u->cargo_source);
+					str = STR_8813_FROM;
+				}
+				DrawString(34, 78 + y_offset, str, 0);
+
+				y_offset += 11;
+			}
+
+			y_offset -= 11;
+		} else {
 			SetDParam(0, v->cargo_type);
-			SetDParam(1, v->cargo_count);
-			SetDParam(2, v->cargo_source);
-			str = STR_8813_FROM;
+			SetDParam(1, v->cargo_cap);
+			DrawString(34, 67 + y_offset, STR_9012_CAPACITY, 0);
+
+			str = STR_8812_EMPTY;
+			if (v->cargo_count != 0) {
+				SetDParam(0, v->cargo_type);
+				SetDParam(1, v->cargo_count);
+				SetDParam(2, v->cargo_source);
+				str = STR_8813_FROM;
+			}
+			DrawString(34, 78 + y_offset, str, 0);
 		}
-		DrawString(34, 78, str, 0);
 
 		/* Draw Transfer credits text */
 		SetDParam(0, v->cargo_feeder_share);
-		DrawString(34, 89, STR_FEEDER_CARGO_VALUE, 0);
+		DrawString(34, 90 + y_offset, STR_FEEDER_CARGO_VALUE, 0);
 
+		/* Draw service interval text */
+		{
+			SetDParam(0, v->service_interval);
+			SetDParam(1, v->date_of_last_service);
+			DrawString(13, 102 + y_offset, _patches.servint_ispercent ? STR_SERVICING_INTERVAL_PERCENT : STR_883C_SERVICING_INTERVAL_DAYS, 0);
+		}
 	} break;
 
 	case WE_CLICK: {
@@ -151,10 +235,10 @@
 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   339,     0,    13, STR_900C_DETAILS, STR_018C_WINDOW_TITLE_DRAG_THIS},
 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   340,   379,     0,    13, STR_01AA_NAME,    STR_902E_NAME_ROAD_VEHICLE},
 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   379,    14,    55, 0x0,              STR_NULL},
-{      WWT_PANEL,   RESIZE_NONE,    14,     0,   379,    56,   100, 0x0,              STR_NULL},
-{ WWT_PUSHTXTBTN,   RESIZE_NONE,    14,     0,    10,   101,   106, STR_0188,         STR_884D_INCREASE_SERVICING_INTERVAL},
-{ WWT_PUSHTXTBTN,   RESIZE_NONE,    14,     0,    10,   107,   112, STR_0189,         STR_884E_DECREASE_SERVICING_INTERVAL},
-{      WWT_PANEL,   RESIZE_NONE,    14,    11,   379,   101,   112, 0x0,              STR_NULL},
+{      WWT_PANEL,   RESIZE_BOTTOM,  14,     0,   379,    56,   100, 0x0,              STR_NULL},
+{ WWT_PUSHTXTBTN,   RESIZE_TB,      14,     0,    10,   101,   106, STR_0188,         STR_884D_INCREASE_SERVICING_INTERVAL},
+{ WWT_PUSHTXTBTN,   RESIZE_TB,      14,     0,    10,   107,   112, STR_0189,         STR_884E_DECREASE_SERVICING_INTERVAL},
+{      WWT_PANEL,   RESIZE_TB,      14,    11,   379,   101,   112, 0x0,              STR_NULL},
 {   WIDGETS_END},
 };
 
--- a/src/station_cmd.cpp
+++ b/src/station_cmd.cpp
@@ -30,6 +30,7 @@
 #include "sprite.h"
 #include "depot.h"
 #include "train.h"
+#include "roadveh.h"
 #include "water_map.h"
 #include "industry_map.h"
 #include "newgrf_callbacks.h"
@@ -2280,7 +2281,7 @@
 		}
 	} else if (v->type == VEH_ROAD) {
 		if (v->u.road.state < RVSB_IN_ROAD_STOP && !IsReversingRoadTrackdir((Trackdir)v->u.road.state) && v->u.road.frame == 0) {
-			if (IsRoadStop(tile)) {
+			if (IsRoadStop(tile) && IsRoadVehFront(v)) {
 				/* Attempt to allocate a parking bay in a road stop */
 				RoadStop *rs = GetRoadStopByTile(tile, GetRoadStopType(tile));
 
--- a/src/train_cmd.cpp
+++ b/src/train_cmd.cpp
@@ -576,7 +576,7 @@
 
 			v->group_id = DEFAULT_GROUP;
 
-			AddArticulatedParts(vl);
+			AddArticulatedParts(vl, VEH_TRAIN);
 
 			_new_vehicle_id = v->index;
 
@@ -755,7 +755,7 @@
 				vl[0]->u.rail.other_multiheaded_part = vl[1];
 				vl[1]->u.rail.other_multiheaded_part = vl[0];
 			} else {
-				AddArticulatedParts(vl);
+				AddArticulatedParts(vl, VEH_TRAIN);
 			}
 
 			TrainConsistChanged(v);
--- a/src/tunnelbridge_cmd.cpp
+++ b/src/tunnelbridge_cmd.cpp
@@ -1427,7 +1427,7 @@
 	} else if (IsBridge(tile)) { // XXX is this necessary?
 		DiagDirection dir;
 
-		if (v->type == VEH_ROAD || (v->type == VEH_TRAIN && IsFrontEngine(v))) {
+		if (v->HasFront() && v->IsPrimaryVehicle()) {
 			/* modify speed of vehicle */
 			uint16 spd = _bridge[GetBridgeType(tile)].speed;
 
--- a/src/vehicle.cpp
+++ b/src/vehicle.cpp
@@ -230,11 +230,15 @@
 
 		v->first = NULL;
 		if (v->type == VEH_TRAIN) v->u.rail.first_engine = INVALID_ENGINE;
+		if (v->type == VEH_ROAD)  v->u.road.first_engine = INVALID_ENGINE;
 	}
 
 	FOR_ALL_VEHICLES(v) {
-		if (v->type == VEH_TRAIN && (IsFrontEngine(v) || IsFreeWagon(v)))
+		if (v->type == VEH_TRAIN && (IsFrontEngine(v) || IsFreeWagon(v))) {
 			TrainConsistChanged(v);
+		} else if (v->type == VEH_ROAD && IsRoadVehFront(v)) {
+			RoadVehUpdateCache(v);
+		}
 	}
 
 	FOR_ALL_VEHICLES(v) {
@@ -498,7 +502,7 @@
 {
 	Vehicle *u;
 
-	FOR_ALL_VEHICLES(u) if (u->type == VEH_TRAIN && u->next == v) return u;
+	FOR_ALL_VEHICLES(u) if (u->type == v->type && u->next == v) return u;
 
 	return NULL;
 }
@@ -531,10 +535,14 @@
 	Vehicle* u;
 
 	assert(v != NULL);
-	assert(v->type == VEH_TRAIN);
+	assert(v->type == VEH_TRAIN || v->type == VEH_ROAD);
 
 	if (v->first != NULL) {
-		if (IsFrontEngine(v->first) || IsFreeWagon(v->first)) return v->first;
+		if (v->type == VEH_TRAIN) {
+			if (IsFrontEngine(v->first) || IsFreeWagon(v->first)) return v->first;
+		} else {
+			if (IsRoadVehFront(v->first)) return v->first;
+		}
 
 		DEBUG(misc, 0, "v->first cache faulty. We shouldn't be here, rebuilding cache!");
 	}
@@ -548,8 +556,10 @@
 	while ((u = GetPrevVehicleInChain_bruteforce(v)) != NULL) v = u;
 
 	/* Set the first pointer of all vehicles in that chain to the first wagon */
-	if (IsFrontEngine(v) || IsFreeWagon(v))
+	if ((v->type == VEH_TRAIN && (IsFrontEngine(v) || IsFreeWagon(v))) ||
+			(v->type == VEH_ROAD && IsRoadVehFront(v))) {
 		for (u = (Vehicle *)v; u != NULL; u = u->next) u->first = (Vehicle *)v;
+	}
 
 	return (Vehicle*)v;
 }
@@ -572,9 +582,8 @@
 		case VEH_TRAIN:
 			return !IsArticulatedPart(v) && // tenders and other articulated parts
 			(!IsMultiheaded(v) || IsTrainEngine(v)); // rear parts of multiheaded engines
-		case VEH_ROAD:
-		case VEH_SHIP:
-			return true;
+		case VEH_ROAD: return IsRoadVehFront(v);
+		case VEH_SHIP: return true;
 		default: return false; // Only count player buildable vehicles
 	}
 }
@@ -609,7 +618,9 @@
 	/* Now remove any artic part. This will trigger an other
 	 *  destroy vehicle, which on his turn can remove any
 	 *  other artic parts. */
-	if (v->type == VEH_TRAIN && EngineHasArticPart(v)) DeleteVehicle(v->next);
+	if ((v->type == VEH_TRAIN && EngineHasArticPart(v)) || (v->type == VEH_ROAD && RoadVehHasArticPart(v))) {
+		DeleteVehicle(v->next);
+	}
 }
 
 /**
@@ -621,7 +632,7 @@
  */
 void DeleteVehicleChain(Vehicle *v)
 {
-	assert(v->type != VEH_TRAIN);
+	assert(v->type != VEH_TRAIN && v->type != VEH_ROAD);
 
 	do {
 		Vehicle *u = v;
@@ -705,6 +716,7 @@
 			case VEH_SHIP:
 				if (v->type == VEH_TRAIN && IsTrainWagon(v)) continue;
 				if (v->type == VEH_AIRCRAFT && v->subtype != AIR_HELICOPTER) continue;
+				if (v->type == VEH_ROAD && !IsRoadVehFront(v)) continue;
 
 				v->motion_counter += (v->direction & 1) ? (v->cur_speed * 3) / 4 : v->cur_speed;
 				/* Play a running sound if the motion counter passes 256 (Do we not skip sounds?) */
@@ -1876,6 +1888,8 @@
 
 				if (w->type == VEH_TRAIN && EngineHasArticPart(w)) {
 					w = GetNextArticPart(w);
+				} else if (w->type == VEH_ROAD && RoadVehHasArticPart(w)) {
+					w = w->next;
 				} else {
 					break;
 				}
@@ -1886,7 +1900,15 @@
 					total_cost += GetRefitCost(v->engine_type);
 				}
 			}
-		} while (v->type == VEH_TRAIN && EngineHasArticPart(v) && (v = GetNextArticPart(v)) != NULL);
+
+			if (v->type == VEH_TRAIN && EngineHasArticPart(v)) {
+				v = GetNextArticPart(v);
+			} else if (v->type == VEH_ROAD && RoadVehHasArticPart(v)) {
+				v = v->next;
+			} else {
+				break;
+			}
+		} while (v != NULL);
 
 		if ((flags & DC_EXEC) && v->type == VEH_TRAIN) w = GetNextVehicle(w);
 	} while (v->type == VEH_TRAIN && (v = GetNextVehicle(v)) != NULL);
@@ -1964,7 +1986,7 @@
 
 		case VEH_ROAD:
 			FOR_ALL_VEHICLES(v) {
-				if (v->tile == tile && v->type == VEH_ROAD && IsRoadVehInDepot(v)) {
+				if (v->tile == tile && v->type == VEH_ROAD && IsRoadVehInDepot(v) && IsRoadVehFront(v)) {
 					if (*engine_count == *engine_list_length) ExtendVehicleListSize((const Vehicle***)engine_list, engine_list_length, 25);
 					(*engine_list)[(*engine_count)++] = v;
 				}
@@ -2163,7 +2185,7 @@
 
 		case VEH_ROAD:
 			InvalidateWindowClasses(WC_ROADVEH_LIST);
-			v->u.road.state = RVSB_IN_DEPOT;
+			if (!IsRoadVehFront(v)) v = GetFirstVehicleInChain(v);
 			break;
 
 		case VEH_SHIP:
--- a/src/vehicle.h
+++ b/src/vehicle.h
@@ -189,6 +189,8 @@
 	byte reverse_ctr;
 	struct RoadStop *slot;
 	byte slot_age;
+	EngineID first_engine;
+	byte cached_veh_length;
 
 	RoadType roadtype;
 	RoadTypes compatible_roadtypes;
--- a/src/vehicle_gui.h
+++ b/src/vehicle_gui.h
@@ -51,7 +51,7 @@
 int DrawVehiclePurchaseInfo(int x, int y, uint w, EngineID engine_number);
 
 void DrawTrainImage(const Vehicle *v, int x, int y, int count, int skip, VehicleID selection);
-void DrawRoadVehImage(const Vehicle *v, int x, int y, VehicleID selection);
+void DrawRoadVehImage(const Vehicle *v, int x, int y, int count, VehicleID selection);
 void DrawShipImage(const Vehicle *v, int x, int y, VehicleID selection);
 void DrawAircraftImage(const Vehicle *v, int x, int y, VehicleID selection);
 
@@ -75,7 +75,7 @@
 {
 	switch (v->type) {
 		case VEH_TRAIN:    DrawTrainImage(v, x, y, count, skip, selection); break;
-		case VEH_ROAD:     DrawRoadVehImage(v, x, y, selection);            break;
+		case VEH_ROAD:     DrawRoadVehImage(v, x, y, count, selection);     break;
 		case VEH_SHIP:     DrawShipImage(v, x, y, selection);               break;
 		case VEH_AIRCRAFT: DrawAircraftImage(v, x, y, selection);           break;
 		default: NOT_REACHED();
--- a/src/viewport.cpp
+++ b/src/viewport.cpp
@@ -23,6 +23,7 @@
 #include "waypoint.h"
 #include "variables.h"
 #include "train.h"
+#include "roadveh.h"
 
 #define VIEWPORT_DRAW_MEM (65536 * 2)
 
@@ -1764,12 +1765,18 @@
 	ShowTrainViewWindow(v);
 }
 
+static void SafeShowRoadVehViewWindow(const Vehicle *v)
+{
+	if (!IsRoadVehFront(v)) v = GetFirstVehicleInChain(v);
+	ShowRoadVehViewWindow(v);
+}
+
 static void Nop(const Vehicle *v) {}
 
 typedef void OnVehicleClickProc(const Vehicle *v);
 static OnVehicleClickProc* const _on_vehicle_click_proc[] = {
 	SafeShowTrainViewWindow,
-	ShowRoadVehViewWindow,
+	SafeShowRoadVehViewWindow,
 	ShowShipViewWindow,
 	ShowAircraftViewWindow,
 	Nop, // Special vehicles
--- a/src/water_cmd.cpp
+++ b/src/water_cmd.cpp
@@ -23,6 +23,7 @@
 #include "depot.h"
 #include "vehicle_gui.h"
 #include "train.h"
+#include "roadveh.h"
 #include "water_map.h"
 #include "newgrf.h"
 #include "newgrf_canal.h"
@@ -641,21 +642,13 @@
 	if (!(v->vehstatus & VS_CRASHED)) {
 		uint16 pass = 0;
 
-		if (v->type == VEH_ROAD) { // flood bus/truck
-			pass = 1; // driver
-			if (IsCargoInClass(v->cargo_type, CC_PASSENGERS)) pass += v->cargo_count;
-
-			v->vehstatus |= VS_CRASHED;
-			v->u.road.crashed_ctr = 2000; // max 2220, disappear pretty fast
-			RebuildVehicleLists();
-		} else if (v->type == VEH_TRAIN) {
+		if (v->type == VEH_TRAIN || v->type == VEH_ROAD) {
 			Vehicle *u;
 
 			v = GetFirstVehicleInChain(v);
 			u = v;
-			if (IsFrontEngine(v)) pass = 4; // driver
 
-			/* crash all wagons, and count passangers */
+			/* crash all wagons, and count passengers */
 			BEGIN_ENUM_WAGONS(v)
 				if (IsCargoInClass(v->cargo_type, CC_PASSENGERS)) pass += v->cargo_count;
 				v->vehstatus |= VS_CRASHED;
@@ -663,7 +656,15 @@
 			END_ENUM_WAGONS(v)
 
 			v = u;
-			v->u.rail.crash_anim_pos = 4000; // max 4440, disappear pretty fast
+
+			if (v->type == VEH_TRAIN) {
+				if (IsFrontEngine(v)) pass += 4; // driver
+				v->u.rail.crash_anim_pos = 4000; // max 4440, disappear pretty fast
+			} else {
+				if (IsRoadVehFront(v)) pass += 1; // driver
+				v->u.road.crashed_ctr = 2000; // max 2220, disappear pretty fast
+			}
+
 			RebuildVehicleLists();
 		} else {
 			return;