src/roadveh_cmd.cpp
branchNewGRF_ports
changeset 6720 35756db7e577
parent 6719 4cc327ad39d5
child 6725 23339968083f
--- a/src/roadveh_cmd.cpp	Sat Jun 02 19:59:29 2007 +0000
+++ b/src/roadveh_cmd.cpp	Sat Jul 14 19:42:58 2007 +0000
@@ -14,6 +14,7 @@
 #include "map.h"
 #include "tile.h"
 #include "vehicle.h"
+#include "timetable.h"
 #include "engine.h"
 #include "command.h"
 #include "station.h"
@@ -27,6 +28,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"
@@ -83,19 +85,19 @@
 	TRACKDIR_X_NE, TRACKDIR_Y_SE, TRACKDIR_X_SW, TRACKDIR_Y_NW
 };
 
-int GetRoadVehImage(const Vehicle* v, Direction direction)
+int RoadVehicle::GetImage(Direction direction) const
 {
-	int img = v->spritenum;
+	int img = this->spritenum;
 	int image;
 
 	if (is_custom_sprite(img)) {
-		image = GetCustomVehicleSprite(v, direction);
+		image = GetCustomVehicleSprite(this, (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;
+		img = orig_road_vehicle_info[this->engine_type - ROAD_ENGINES_INDEX].image_index;
 	}
 
 	image = direction + _roadveh_images[img];
-	if (v->cargo_count >= v->cargo_cap / 2) image += _roadveh_full_adder[img];
+	if (this->cargo.Count() >= this->cargo_cap / 2U) image += _roadveh_full_adder[img];
 	return image;
 }
 
@@ -115,9 +117,38 @@
 	DrawSprite(6 + _roadveh_images[spritenum], pal, x, y);
 }
 
-static int32 EstimateRoadVehCost(EngineID engine_type)
+static CommandCost EstimateRoadVehCost(EngineID engine_type)
 {
-	return ((_price.roadveh_base >> 3) * GetEngineProperty(engine_type, 0x11, RoadVehInfo(engine_type)->base_cost)) >> 5;
+	return CommandCost(((_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_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.
@@ -126,9 +157,9 @@
  * @param p1 bus/truck type being built (engine)
  * @param p2 bit 0 when set, the unitnumber will be 0, otherwise it will be a free number
  */
-int32 CmdBuildRoadVeh(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
+CommandCost CmdBuildRoadVeh(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
 {
-	int32 cost;
+	CommandCost cost;
 	Vehicle *v;
 	UnitID unit_num;
 	Engine *e;
@@ -147,8 +178,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 +202,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;
@@ -180,7 +220,7 @@
 		v->cargo_subtype = 0;
 		v->cargo_cap = rvi->capacity;
 //		v->cargo_count = 0;
-		v->value = cost;
+		v->value = cost.GetCost();
 //		v->day_counter = 0;
 //		v->next_order_param = v->next_order = 0;
 //		v->load_unload_time_rem = 0;
@@ -193,9 +233,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,24 +249,32 @@
 		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);
 		RebuildVehicleLists();
 		InvalidateWindow(WC_COMPANY, v->owner);
 		if (IsLocalPlayer())
-			InvalidateAutoreplaceWindow(VEH_ROAD); // updates the replace Road window
+			InvalidateAutoreplaceWindow(VEH_ROAD, v->group_id); // updates the replace Road window
 
 		GetPlayer(_current_player)->num_engines[p1]++;
 	}
 
-	return cost;
+	return CommandCost(cost);
 }
 
 /** Start/Stop a road vehicle.
@@ -238,7 +283,7 @@
  * @param p1 road vehicle ID to start/stop
  * @param p2 unused
  */
-int32 CmdStartStopRoadVeh(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
+CommandCost CmdStartStopRoadVeh(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
 {
 	Vehicle *v;
 	uint16 callback;
@@ -268,7 +313,7 @@
 		InvalidateWindow(WC_VEHICLE_DEPOT, v->tile);
 	}
 
-	return 0;
+	return CommandCost();
 }
 
 void ClearSlot(Vehicle *v)
@@ -285,13 +330,25 @@
 	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
  * @param p1 vehicle ID to be sold
  * @param p2 unused
  */
-int32 CmdSellRoadVeh(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
+CommandCost CmdSellRoadVeh(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
 {
 	Vehicle *v;
 
@@ -303,7 +360,7 @@
 
 	SET_EXPENSES_TYPE(EXPENSES_NEW_VEHICLES);
 
-	if (!IsRoadVehInDepotStopped(v)) {
+	if (!CheckRoadVehInDepotStopped(v)) {
 		return_cmd_error(STR_9013_MUST_BE_STOPPED_INSIDE);
 	}
 
@@ -317,7 +374,7 @@
 		DeleteVehicle(v);
 	}
 
-	return -(int32)v->value;
+	return CommandCost(-v->value);
 }
 
 struct RoadFindDepotData {
@@ -359,7 +416,7 @@
 		/* See where we are now */
 		Trackdir trackdir = GetVehicleTrackdir(v);
 
-		ftd = NPFRouteToDepotBreadthFirst(v->tile, trackdir, TRANSPORT_ROAD, v->u.road.compatible_roadtypes, v->owner, INVALID_RAILTYPE);
+		ftd = NPFRouteToDepotBreadthFirstTwoWay(v->tile, trackdir, v->tile, ReverseTrackdir(trackdir), TRANSPORT_ROAD, v->u.road.compatible_roadtypes, v->owner, INVALID_RAILTYPE, 0);
 		if (ftd.best_bird_dist == 0) {
 			return GetDepotByTile(ftd.node.tile); /* Target found */
 		} else {
@@ -391,7 +448,7 @@
  * - p2 bit 0-3 - DEPOT_ flags (see vehicle.h)
  * - p2 bit 8-10 - VLW flag (for mass goto depot)
  */
-int32 CmdSendRoadVehToDepot(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
+CommandCost CmdSendRoadVehToDepot(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
 {
 	Vehicle *v;
 	const Depot *dep;
@@ -422,7 +479,7 @@
 				TOGGLEBIT(v->current_order.flags, OFB_HALT_IN_DEPOT);
 				InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR);
 			}
-			return 0;
+			return CommandCost();
 		}
 
 		if (p2 & DEPOT_DONT_CANCEL) return CMD_ERROR; // Requested no cancelation of depot orders
@@ -436,7 +493,7 @@
 			v->current_order.flags = 0;
 			InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR);
 		}
-		return 0;
+		return CommandCost();
 	}
 
 	dep = FindClosestRoadDepot(v);
@@ -455,7 +512,7 @@
 		InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR);
 	}
 
-	return 0;
+	return CommandCost();
 }
 
 /** Turn a roadvehicle around.
@@ -464,7 +521,7 @@
  * @param p1 vehicle ID to turn
  * @param p2 unused
  */
-int32 CmdTurnRoadVeh(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
+CommandCost CmdTurnRoadVeh(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
 {
 	Vehicle *v;
 
@@ -492,13 +549,13 @@
 
 	if (flags & DC_EXEC) v->u.road.reverse_ctr = 180;
 
-	return 0;
+	return CommandCost();
 }
 
 
 void RoadVehicle::MarkDirty()
 {
-	this->cur_image = GetRoadVehImage(this, this->direction);
+	this->cur_image = this->GetImage(this->direction);
 	MarkAllViewportsDirty(this->left_coord, this->top_coord, this->right_coord + 1, this->bottom_coord + 1);
 }
 
@@ -536,8 +593,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 +635,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 = v->GetImage(v->direction);
+		SetRoadVehPosition(v, v->x_pos, v->y_pos);
+	} while ((v = v->next) != NULL);
 }
 
 static void RoadVehIsCrashed(Vehicle *v)
@@ -590,8 +653,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 +672,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 +703,18 @@
 
 static void RoadVehCheckTrainCrash(Vehicle *v)
 {
-	TileIndex tile;
-
-	if (v->u.road.state == RVSB_WORMHOLE) return;
+	for (Vehicle *u = v; u != NULL; u = u->next) {
+		if (u->u.road.state == RVSB_WORMHOLE) continue;
 
-	tile = v->tile;
+		TileIndex tile = u->tile;
 
-	if (!IsLevelCrossingTile(tile)) return;
+		if (!IsLevelCrossingTile(tile)) continue;
 
-	if (VehicleFromPos(tile, v, EnumCheckRoadVehCrashTrain) != NULL)
-		RoadVehCrash(v);
+		if (VehicleFromPosXY(v->x_pos, v->y_pos, u, EnumCheckRoadVehCrashTrain) != NULL) {
+			RoadVehCrash(v);
+			return;
+		}
+	}
 }
 
 static void HandleBrokenRoadVeh(Vehicle *v)
@@ -689,6 +758,7 @@
 			if (!(v->current_order.flags & OF_PART_OF_ORDERS)) return;
 			if (v->current_order.flags & OF_SERVICE_IF_NEEDED &&
 					!VehicleNeedsService(v)) {
+				UpdateVehicleTimetable(v, true);
 				v->cur_order_index++;
 			}
 			break;
@@ -798,11 +868,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)) &&
@@ -821,7 +891,7 @@
 	rvf.y = y;
 	rvf.dir = dir;
 	rvf.veh = v;
-	u = (Vehicle*)VehicleFromPos(TileVirtXY(x, y), &rvf, EnumCheckRoadVehClose);
+	u = (Vehicle*)VehicleFromPosXY(x, y, &rvf, EnumCheckRoadVehClose);
 
 	/* This code protects a roadvehicle from being blocked for ever
 	 * If more than 1480 / 74 days a road vehicle is blocked, it will
@@ -972,6 +1042,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 */
@@ -1014,15 +1087,8 @@
 
 static int PickRandomBit(uint bits)
 {
-	uint num = 0;
-	uint b = bits;
 	uint i;
-
-	do {
-		if (b & 1) num++;
-	} while (b >>= 1);
-
-	num = RandomRange(num);
+	uint num = RandomRange(CountBitsSet(bits));
 
 	for (i = 0; !(bits & 1) || (int)--num >= 0; bits >>= 1, i++) {}
 	return i;
@@ -1267,7 +1333,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 = v->GetImage(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 +1429,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 +1439,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,22 +1454,22 @@
 		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)) {
 			/* Vehicle has just entered a bridge or tunnel */
-			v->cur_image = GetRoadVehImage(v, v->direction);
+			v->cur_image = v->GetImage(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 +1481,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:
@@ -1412,14 +1511,14 @@
 					case TRACKDIR_RVREV_SW: needed = ROAD_NE; break;
 					case TRACKDIR_RVREV_NW: needed = ROAD_SE; break;
 				}
-				if (!IsTileType(tile, MP_STREET) || GetRoadTileType(tile) != ROAD_TILE_NORMAL || (needed & GetRoadBits(tile, ROADTYPE_TRAM)) == ROAD_NONE) {
+				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 +1531,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 +1549,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));
@@ -1475,10 +1574,10 @@
 			v->cur_speed -= v->cur_speed >> 2;
 		}
 
-		v->cur_image = GetRoadVehImage(v, newdir);
+		v->cur_image = v->GetImage(newdir);
 		v->UpdateDeltaXY(v->direction);
 		RoadZPosAffectSpeed(v, SetRoadVehPosition(v, x, y));
-		return;
+		return true;
 	}
 
 	if (rd.x & RDE_TURNED) {
@@ -1490,7 +1589,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 +1598,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;
@@ -1515,10 +1614,21 @@
 			v->cur_speed -= v->cur_speed >> 2;
 		}
 
-		v->cur_image = GetRoadVehImage(v, newdir);
+		v->cur_image = v->GetImage(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 +1637,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 +1646,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;
 		}
 	}
 
@@ -1546,13 +1656,13 @@
 		v->cur_speed -= (v->cur_speed >> 2);
 		if (old_dir != v->u.road.state) {
 			/* The vehicle is in a road stop */
-			v->cur_image = GetRoadVehImage(v, new_dir);
+			v->cur_image = v->GetImage(new_dir);
 			v->UpdateDeltaXY(v->direction);
 			SetRoadVehPosition(v, v->x_pos, v->y_pos);
 			/* 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 +1671,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 +1706,7 @@
 
 						v->u.road.frame++;
 						RoadZPosAffectSpeed(v, SetRoadVehPosition(v, x, y));
-						return;
+						return true;
 					}
 				}
 			}
@@ -1608,7 +1718,7 @@
 			RoadVehArrivesAt(v, st);
 			v->BeginLoading();
 
-			return;
+			return false;
 		}
 
 		/* Vehicle is ready to leave a bay in a road stop */
@@ -1616,7 +1726,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,28 +1769,71 @@
 	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
 	 * in a depot or entered a tunnel/bridge */
 	if (!HASBIT(r, VETS_ENTERED_WORMHOLE)) v->u.road.frame++;
 
-	v->cur_image = GetRoadVehImage(v, v->direction);
+	v->cur_image = v->GetImage(v->direction);
 	v->UpdateDeltaXY(v->direction);
 	RoadZPosAffectSpeed(v, SetRoadVehPosition(v, x, y));
+	return true;
+}
+
+static void RoadVehController(Vehicle *v)
+{
+	/* decrease counters */
+	v->tick_counter++;
+	v->current_order_time++;
+	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)
 {
 	if (_age_cargo_skip_counter != 0) return;
-	if (v->cargo_days != 255) v->cargo_days++;
+	v->cargo.AgeCargo();
 }
 
-void RoadVeh_Tick(Vehicle *v)
+void RoadVehicle::Tick()
 {
-	AgeRoadVehCargo(v);
-	RoadVehController(v);
+	AgeRoadVehCargo(this);
+
+	if (IsRoadVehFront(this)) RoadVehController(this);
 }
 
 static void CheckIfRoadVehNeedsService(Vehicle *v)
@@ -1736,7 +1889,9 @@
 
 void OnNewDay_RoadVeh(Vehicle *v)
 {
-	int32 cost;
+	CommandCost cost;
+
+	if (!IsRoadVehFront(v)) return;
 
 	if ((++v->day_counter & 7) == 0) DecreaseVehicleValue(v);
 	if (v->u.road.blocked_ctr == 0) CheckVehicleBreakdown(v);
@@ -1816,10 +1971,10 @@
 
 	cost = RoadVehInfo(v->engine_type)->running_cost * _price.roadveh_running / 364;
 
-	v->profit_this_year -= cost >> 8;
+	v->profit_this_year -= cost.GetCost() >> 8;
 
 	SET_EXPENSES_TYPE(EXPENSES_ROADVEH_RUN);
-	SubtractMoneyFromPlayerFract(v->owner, cost);
+	SubtractMoneyFromPlayerFract(v->owner, CommandCost(cost));
 
 	InvalidateWindow(WC_VEHICLE_DETAILS, v->index);
 	InvalidateWindowClasses(WC_ROADVEH_LIST);
@@ -1849,10 +2004,10 @@
  * - p2 = (bit 16) - refit only this vehicle (ignored)
  * @return cost of refit or error
  */
-int32 CmdRefitRoadVeh(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
+CommandCost CmdRefitRoadVeh(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
 {
 	Vehicle *v;
-	int32 cost;
+	CommandCost cost;
 	CargoID new_cid = GB(p2, 0, 8);
 	byte new_subtype = GB(p2, 8, 8);
 	uint16 capacity = CALLBACK_FAILED;
@@ -1862,7 +2017,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;
 
@@ -1908,14 +2063,13 @@
 	}
 	_returned_refit_capacity = capacity;
 
-	cost = 0;
 	if (IsHumanPlayer(v->owner) && new_cid != v->cargo_type) {
 		cost = GetRefitCost(v->engine_type);
 	}
 
 	if (flags & DC_EXEC) {
 		v->cargo_cap = capacity;
-		v->cargo_count = (v->cargo_type == new_cid) ? min(capacity, v->cargo_count) : 0;
+		v->cargo.Truncate((v->cargo_type == new_cid) ? capacity : 0);
 		v->cargo_type = new_cid;
 		v->cargo_subtype = new_subtype;
 		InvalidateWindow(WC_VEHICLE_DETAILS, v->index);