changeset 13940:b76c3fa5c59d draft

(svn r18472) -Fix [FS#3146]: selling vehicles in the depot could create states that are not allowed by the NewGRF attach callback.
author rubidium <rubidium@openttd.org>
date Sat, 12 Dec 2009 22:59:48 +0000
parents 0a9f4129b777
children 5a2efeabdd21
files src/train_cmd.cpp
diffstat 1 files changed, 56 insertions(+), 200 deletions(-) [+]
line wrap: on
line diff
--- a/src/train_cmd.cpp
+++ b/src/train_cmd.cpp
@@ -980,35 +980,6 @@
 	return count;
 }
 
-/**
- * Unlink a rail wagon from the consist.
- * @param v Vehicle to remove.
- * @param first The first vehicle of the consist.
- * @return The first vehicle of the consist.
- */
-static Train *UnlinkWagon(Train *v, Train *first)
-{
-	/* unlinking the first vehicle of the chain? */
-	if (v == first) {
-		v = v->GetNextVehicle();
-		if (v == NULL) return NULL;
-
-		if (v->IsWagon()) v->SetFreeWagon();
-
-		/* First can be an articulated engine, meaning GetNextVehicle() isn't
-		 * v->Next(). Thus set the next vehicle of the last articulated part
-		 * and the last articulated part is just before the next vehicle (v). */
-		v->Previous()->SetNext(NULL);
-
-		return v;
-	}
-
-	Train *u;
-	for (u = first; u->GetNextVehicle() != v; u = u->GetNextVehicle()) {}
-	u->GetLastEnginePart()->SetNext(v->GetNextVehicle());
-	return first;
-}
-
 static Train *FindGoodVehiclePos(const Train *src)
 {
 	EngineID eng = src->engine_type;
@@ -1029,41 +1000,6 @@
 	return NULL;
 }
 
-/*
- * add a vehicle v behind vehicle dest
- * use this function since it sets flags as needed
- */
-static void AddWagonToConsist(Train *v, Train *dest)
-{
-	UnlinkWagon(v, v->First());
-	if (dest == NULL) return;
-
-	Train *next = dest->Next();
-	v->SetNext(NULL);
-	dest->SetNext(v);
-	v->SetNext(next);
-	v->ClearFreeWagon();
-	v->ClearFrontEngine();
-}
-
-/*
- * move around on the train so rear engines are placed correctly according to the other engines
- * always call with the front engine
- */
-static void NormaliseTrainConsist(Train *v)
-{
-	for (; v != NULL; v = v->GetNextVehicle()) {
-		if (!v->IsMultiheaded() || !v->IsEngine()) continue;
-
-		/* make sure that there are no free cars before next engine */
-		Train *u;
-		for (u = v; u->Next() != NULL && !u->Next()->IsEngine(); u = u->Next()) {}
-
-		if (u == v->other_multiheaded_part) continue;
-		AddWagonToConsist(v->other_multiheaded_part, u);
-	}
-}
-
 /** Helper type for lists/vectors of trains */
 typedef SmallVector<Train *, 16> TrainList;
 
@@ -1546,7 +1482,9 @@
 
 	Train *v = Train::GetIfValid(p1);
 	if (v == NULL || !CheckOwnership(v->owner)) return CMD_ERROR;
-	if (p2 > 1) return CMD_ERROR;
+
+	/* Sell a chain of vehicles or not? */
+	bool sell_chain = HasBit(p2, 0);
 
 	if (v->vehstatus & VS_CRASHED) return_cmd_error(STR_ERROR_CAN_T_SELL_DESTROYED_VEHICLE);
 
@@ -1560,145 +1498,63 @@
 
 	if (v->IsRearDualheaded()) return_cmd_error(STR_ERROR_REAR_ENGINE_FOLLOW_FRONT);
 
-	if (flags & DC_EXEC) {
-		if (v == first && first->IsFrontEngine()) {
-			DeleteWindowById(WC_VEHICLE_VIEW, first->index);
-			DeleteWindowById(WC_VEHICLE_ORDERS, first->index);
-			DeleteWindowById(WC_VEHICLE_REFIT, first->index);
-			DeleteWindowById(WC_VEHICLE_DETAILS, first->index);
-			DeleteWindowById(WC_VEHICLE_TIMETABLE, first->index);
-		}
-		SetWindowDirty(WC_VEHICLE_DEPOT, first->tile);
-		InvalidateWindowClassesData(WC_TRAINS_LIST, 0);
+	/* First make a backup of the order of the train. That way we can do
+	 * whatever we want with the order and later on easily revert. */
+	TrainList original;
+	MakeTrainBackup(original, first);
+
+	/* We need to keep track of the new head and the head of what we're going to sell. */
+	Train *new_head = first;
+	Train *sell_head = NULL;
+
+	/* Split the train in the wanted way. */
+	ArrangeTrains(&sell_head, NULL, &new_head, v, sell_chain);
+
+	/* We don't need to validate the second train; it's going to be sold. */
+	CommandCost ret = ValidateTrains(NULL, NULL, first, new_head);
+	if (ret.Failed()) {
+		/* Restore the train we had. */
+		RestoreTrainBackup(original);
+		return ret;
 	}
 
 	CommandCost cost(EXPENSES_NEW_VEHICLES);
-	switch (p2) {
-		case 0: { // Delete given wagon
-			bool switch_engine = false;    // update second wagon to engine?
-
-			/* 1. Delete the engine, if it is dualheaded also delete the matching
-			 * rear engine of the loco (from the point of deletion onwards) */
-			Train *rear = (v->IsMultiheaded() &&
-				v->IsEngine()) ? v->other_multiheaded_part : NULL;
-
-			if (rear != NULL) {
-				cost.AddCost(-rear->value);
-				if (flags & DC_EXEC) {
-					UnlinkWagon(rear, first);
-					delete rear;
-				}
-			}
-
-			/* 2. We are selling the front vehicle, some special action might be required
-			 * here, so take attention */
-			if (v == first) {
-				Train *new_f = first->GetNextUnit();
-
-				/* 2.2 If there are wagons present after the deleted front engine, check
-				 * if the second wagon (which will be first) is an engine. If it is one,
-				 * promote it as a new train, retaining the unitnumber, orders */
-				if (new_f != NULL && new_f->IsEngine()) {
-					if (first->IsEngine()) {
-						/* Let the new front engine take over the setup of the old engine */
-						switch_engine = true;
-
-						if (flags & DC_EXEC) {
-							/* Make sure the group counts stay correct. */
-							new_f->group_id        = first->group_id;
-							first->group_id        = DEFAULT_GROUP;
-
-							/* Copy orders (by sharing) */
-							new_f->orders.list     = first->orders.list;
-							new_f->AddToShared(first);
-							DeleteVehicleOrders(first);
-
-							/* Copy other important data from the front engine */
-							new_f->CopyVehicleConfigAndStatistics(first);
-
-							/* If we deleted a window then open a new one for the 'new' train */
-							if (IsLocalCompany() && w != NULL) ShowVehicleViewWindow(new_f);
-						}
-					} else {
-						/* We are selling a free wagon, and construct a new train at the same time.
-						 * This needs lots of extra checks (e.g. train limit), which are done by first moving
-						 * the remaining vehicles to a new row */
-						cost.AddCost(DoCommand(0, new_f->index | INVALID_VEHICLE << 16, 1, flags, CMD_MOVE_RAIL_VEHICLE));
-						if (cost.Failed()) return cost;
-					}
-				}
-			}
-
-			/* 3. Delete the requested wagon */
-			cost.AddCost(-v->value);
-			if (flags & DC_EXEC) {
-				first = UnlinkWagon(v, first);
-				delete v;
-
-				/* 4 If the second wagon was an engine, update it to front_engine
-				 * which UnlinkWagon() has changed to TS_Free_Car */
-				if (switch_engine) first->SetFrontEngine();
-
-				/* 5. If the train still exists, update its acceleration, window, etc. */
-				if (first != NULL) {
-					NormaliseTrainConsist(first);
-					TrainConsistChanged(first, false);
-					UpdateTrainGroupID(first);
-					if (first->IsFrontEngine()) SetWindowDirty(WC_VEHICLE_REFIT, first->index);
-				}
-
-			}
-		} break;
-		case 1: { // Delete wagon and all wagons after it given certain criteria
-			/* Start deleting every vehicle after the selected one
-			 * If we encounter a matching rear-engine to a front-engine
-			 * earlier in the chain (before deletion), leave it alone */
-			for (Train *tmp; v != NULL; v = tmp) {
-				tmp = v->GetNextVehicle();
-
-				if (v->IsMultiheaded()) {
-					if (v->IsEngine()) {
-						/* We got a front engine of a multiheaded set. Now we will sell the rear end too */
-						Train *rear = v->other_multiheaded_part;
-
-						if (rear != NULL) {
-							cost.AddCost(-rear->value);
-
-							/* If this is a multiheaded vehicle with nothing
-							 * between the parts, tmp will be pointing to the
-							 * rear part, which is unlinked from the train and
-							 * deleted here. However, because tmp has already
-							 * been set it needs to be updated now so that the
-							 * loop never sees the rear part. */
-							if (tmp == rear) tmp = tmp->GetNextVehicle();
-
-							if (flags & DC_EXEC) {
-								first = UnlinkWagon(rear, first);
-								delete rear;
-							}
-						}
-					} else if (v->other_multiheaded_part != NULL) {
-						/* The front to this engine is earlier in this train. Do nothing */
-						continue;
-					}
-				}
-
-				cost.AddCost(-v->value);
-				if (flags & DC_EXEC) {
-					first = UnlinkWagon(v, first);
-					delete v;
-				}
-			}
-
-			/* 3. If it is still a valid train after selling, update its acceleration and cached values */
-			if ((flags & DC_EXEC) && first != NULL) {
-				NormaliseTrainConsist(first);
-				TrainConsistChanged(first, false);
-				UpdateTrainGroupID(first);
-				SetWindowDirty(WC_VEHICLE_REFIT, first->index);
-			}
-		} break;
+	for (Train *t = sell_head; t != NULL; t = t->Next()) cost.AddCost(-t->value);
+
+	/* do it? */
+	if (flags & DC_EXEC) {
+		/* First normalise the sub types of the chain. */
+		NormaliseSubtypes(new_head);
+
+		if (v == first && v->IsEngine() && !sell_chain && new_head != NULL && new_head->IsFrontEngine()) {
+			/* We are selling the front engine. In this case we want to
+			 * 'give' the order, unitnumber and such to the new head. */
+			new_head->orders.list = first->orders.list;
+			new_head->AddToShared(first);
+			DeleteVehicleOrders(first);
+
+			/* Copy other important data from the front engine */
+			new_head->CopyVehicleConfigAndStatistics(first);
+			IncreaseGroupNumVehicle(new_head->group_id);
+
+			/* If we deleted a window then open a new one for the 'new' train */
+			if (IsLocalCompany() && w != NULL) ShowVehicleViewWindow(new_head);
+		}
+
+		/* We need to update the information about the train. */
+		NormaliseTrainHead(new_head);
+
+		/* We are undoubtedly changing something in the depot and train list. */
+		InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile);
+		InvalidateWindowClassesData(WC_TRAINS_LIST, 0);
+
+		/* Actually delete the sold 'goods' */
+		delete sell_head;
+	} else {
+		/* We don't want to execute what we're just tried. */
+		RestoreTrainBackup(original);
 	}
+
 	return cost;
 }