changeset 8162:e77c1af20217 draft

(svn r11724) -Fix: make it impossible (for users) to circumvent the length checking of the NewGRF 'allow wagon attach' callback by moving several wagons at a time.
author rubidium <rubidium@openttd.org>
date Sun, 30 Dec 2007 21:35:48 +0000
parents 5bc22121422c
children 48e06b96549c
files src/train_cmd.cpp
diffstat 1 files changed, 82 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/src/train_cmd.cpp
+++ b/src/train_cmd.cpp
@@ -1027,16 +1027,90 @@
 		if (flags & DC_EXEC) src->unitnumber = unit_num;
 	}
 
-	if (dst_head != NULL) {
-		/* Check NewGRF Callback 0x1D */
-		uint16 callback = GetVehicleCallbackParent(CBID_TRAIN_ALLOW_WAGON_ATTACH, 0, 0, dst_head->engine_type, src, dst_head);
-		if (callback != CALLBACK_FAILED) {
-			if (callback == 0xFD) return_cmd_error(STR_INCOMPATIBLE_RAIL_TYPES);
-			if (callback < 0xFD) {
-				StringID error = GetGRFStringID(GetEngineGRFID(dst_head->engine_type), 0xD000 + callback);
-				return_cmd_error(error);
+	/*
+	 * Check whether the vehicles in the source chain are in the destination
+	 * chain. This can easily be done by checking whether the first vehicle
+	 * of the source chain is in the destination chain as the Next/Previous
+	 * pointers always make a doubly linked list of it where the assumption
+	 * v->Next()->Previous() == v holds (assuming v->Next() != NULL).
+	 */
+	bool src_in_dst = false;
+	for (Vehicle *v = dst_head; !src_in_dst && v != NULL; v = v->Next()) src_in_dst = v == src;
+
+	/*
+	 * If the source chain is in the destination chain then the user is
+	 * only reordering the vehicles, thus not attaching a new vehicle.
+	 * Therefor the 'allow wagon attach' callback does not need to be
+	 * called. If it would be called strange things would happen because
+	 * one 'attaches' an already 'attached' vehicle causing more trouble
+	 * than it actually solves (infinite loops and such).
+	 */
+	if (dst_head != NULL && !src_in_dst) {
+		/*
+		 * When performing the 'allow wagon attach' callback, we have to check
+		 * that for each and every wagon, not only the first one. This means
+		 * that we have to test one wagon, attach it to the train and then test
+		 * the next wagon till we have reached the end. We have to restore it
+		 * to the state it was before we 'tried' attaching the train when the
+		 * attaching fails or succeeds because we are not 'only' doing this
+		 * in the DC_EXEC state.
+		 */
+		Vehicle *dst_tail = dst_head;
+		while (dst_tail->Next() != NULL) dst_tail = dst_tail->Next();
+
+		Vehicle *orig_tail = dst_tail;
+		Vehicle *next_to_attach = src;
+		Vehicle *src_previous = src->Previous();
+
+		while (next_to_attach != NULL) {
+			uint16 callback = GetVehicleCallbackParent(CBID_TRAIN_ALLOW_WAGON_ATTACH, 0, 0, dst_head->engine_type, next_to_attach, dst_head);
+			if (callback != CALLBACK_FAILED) {
+				StringID error = STR_NULL;
+
+				if (callback == 0xFD) error = STR_INCOMPATIBLE_RAIL_TYPES;
+				if (callback < 0xFD) error = GetGRFStringID(GetEngineGRFID(dst_head->engine_type), 0xD000 + callback);
+
+				if (error != STR_NULL) {
+					/*
+					 * The attaching is not allowed. In this case 'next_to_attach'
+					 * can contain some vehicles of the 'source' and the destination
+					 * train can have some too. We 'just' add the to-be added wagons
+					 * to the chain and then split it where it was previously
+					 * separated, i.e. the tail of the original destination train.
+					 * Furthermore the 'previous' link of the original source vehicle needs
+					 * to be restored, otherwise the train goes missing in the depot.
+					 */
+					dst_tail->SetNext(next_to_attach);
+					orig_tail->SetNext(NULL);
+					if (src_previous != NULL) src_previous->SetNext(src);
+
+					return_cmd_error(error);
+				}
 			}
+
+			/*
+			 * Adding a next wagon to the chain so we can test the other wagons.
+			 * First 'take' the first wagon from 'next_to_attach' and move it
+			 * to the next wagon. Then add that to the tail of the destination
+			 * train and update the tail with the new vehicle.
+			 */
+			Vehicle *to_add = next_to_attach;
+			next_to_attach = next_to_attach->Next();
+
+			to_add->SetNext(NULL);
+			dst_tail->SetNext(to_add);
+			dst_tail = dst_tail->Next();
 		}
+
+		/*
+		 * When we reach this the attaching is allowed. It also means that the
+		 * chain of vehicles to attach is empty, so we do not need to merge that.
+		 * This means only the splitting needs to be done.
+		 * Furthermore the 'previous' link of the original source vehicle needs
+		 * to be restored, otherwise the train goes missing in the depot.
+		 */
+		orig_tail->SetNext(NULL);
+		if (src_previous != NULL) src_previous->SetNext(src);
 	}
 
 	/* do it? */