keeping track of train routes

June 5th, 2006

OpenTTD is an open source game based on the old classic Transport Tycoon Deluxe. The original game was reverse engineered and the code is all written from scratch. The idea of rewriting this venerable game is very good, as those who loved the original have a chance to implement all kinds of improvements they may have conceived while playing the commercial game, which of course did not allow for any hacking. Not to mention that with new code in c, it could be made cross platform (the old game only ran on Windows)! I found OpenTTD a very cool initiative, and after a relatively short period of time, the developers had managed to improve lots of little usability issues, while adding new and desirable features.

The game is about building a transportation network and there are four kinds of transportation available - aircraft, ships, buses/trucks, and trains. But this game is really mostly about trains and the magnificent networks of trains its gamers have concocted. One thing I found to be a little troublesome was to keep track of the trains as they grew in numbers, and keep track of their routes. If you build a junction such as the one shown in the screenshot, then how, by just looking at trains going by, are you going to keep track of which train has which route?

openttd_junction.png

There could be lots of ways to make this common problem less of a problem, but my idea was to keep track of routes by keeping track of names. If you were to have a set of trains called South Western 1, South Western 2 etc, all of which had the same route, then you could easily tell trains apart, and see where a train has gone where it shouldn't have. It would still require the user to set up the routes and name the trains, but from that point on, the game could assign the correct names, so if you cloned a train called New York - San Francisco 7, the clone would be assigned the name New York - San Francisco 8.

So to make that happen I wrote a patch against revision 5125 in svn.

*** vehicle.c	2006-06-05 14:59:24.000000000 +0200
--- vehicle.c	2006-06-05 15:00:35.000000000 +0200
***************
*** 30,35 ****
--- 30,37 ----
  #include "station_map.h"
  #include "water_map.h"
  
+ #include <ctype.h>
+ 
  #define INVALID_COORD (-0x8000)
  #define GEN_HASH(x,y) (((x & 0x1F80)>>7) + ((y & 0xFC0)))
  
***************
*** 1567,1572 ****
--- 1569,1580 ----
  				w_front = w;
  				w->service_interval = v->service_interval;
  				DoCommand(0, (v->index << 16) | w->index, p2 & 1 ? CO_SHARE : CO_COPY, flags, CMD_CLONE_ORDER);
+         
+ 				// if orders are shared, increment vehicle number
+ 				if (p2) {
+ 					IncrementVehicleNameOnClone(v, w, flags);
+ 				}
+         
  			}
  			w_rear = w;	// trains needs to know the last car in the train, so they can add more in next loop
  		}
***************
*** 1579,1584 ****
--- 1587,1672 ----
  	return total_cost;
  }
  
+ /** Increment the cloned' vehicles name
+ * @param v the original vehicle's index
+ * @param w the clone's index
+ */
+ void IncrementVehicleNameOnClone(Vehicle *v, Vehicle *w, uint32 flags)
+ {
+ 	int len = 32;
+ 	char vehicle_name[len];
+ 	char vehicle_number[len];
+ 	Vehicle *nvehicle;	// the next vehicle in array to check
+ 	char nvehicle_name[len];	// the next vehicle's name
+ 	uint16 e;
+ 	uint16 pool = GetVehiclePoolSize();
+ 	int i, j, f, n, largest;
+ 	int numbers[pool];	// the array of numbers our new number must fit into
+ 	
+ 	// Get the name of the old vehicle
+ 	if ((v->string_id & 0xF800) != 0x7800) {
+ 		vehicle_name[0] = '\0';
+ 	} else {
+ 		GetName(v->string_id & 0x7FF, vehicle_name);
+ 	}
+ 	
+ 	// find out how far into the name the digits start, save the index
+ 	i = 0;
+ 	while (!isdigit(vehicle_name[i]) && (i < len)) {
+ 		i++;
+ 	}
+ 	
+ 	// use the index to read an integer from the vehicle name
+ 	n = atoi(&vehicle_name[i]);
+ 	if (n != 0) {
+ 		
+ 		// find a vacant number to assign the new vehicle, check all existing
+ 		// vehicles which have the same name
+ 		memset(numbers, 0, pool);
+ 		e = 0;
+ 		j = 0;
+ 		for (e = 0; e < pool; e++) {
+ 			// verify vehicle owner and type to match this vehicle
+ 			nvehicle = GetVehicle(e);
+ 			if (CheckOwnership(nvehicle->owner) && (v->type == nvehicle->type)) {
+ 				
+ 				// get string_id
+ 				if ((nvehicle->string_id & 0xF800) != 0x7800) {
+ 					nvehicle_name[0] = '\0';
+ 				} else {
+ 					GetName(nvehicle->string_id & 0x7FF, nvehicle_name);
+ 				}
+ 				
+ 				// filter string_id's that are empty or non-equal
+ 				if (strncmp(nvehicle_name, vehicle_name, i) == 0) {
+ 					numbers[j] = atoi( &nvehicle_name[i]);
+ 					j++;
+ 				}
+ 				
+ 			}
+ 		}
+ 		
+ 		// resulting array 'numbers' has the numbers of vehicles we need to examine
+ 		largest = 0;
+ 		for (f = 0; f < j; f++) {
+ 			if (numbers[f] > largest) largest = numbers[f];
+ 		}
+ 		
+ 		// assign number to vehicle
+ 		n = largest+1;
+ 		vehicle_name[i] = '\0';
+ 		sprintf(vehicle_number, "%d", n);
+ 		strcat(vehicle_name, vehicle_number);
+ 	
+ 		// Set the name of the new vehicle
+ 		if ((flags & DC_EXEC) && vehicle_name[0] != '\0') {
+ 			_cmd_text = vehicle_name;
+ 			DoCommand(0, w->index, 0, DC_EXEC, CMD_NAME_VEHICLE);
+ 		}
+ 	}
+ 
+ }
+ 
  /*
   * move the cargo from one engine to another if possible
   */
*** vehicle.h	2006-06-05 14:59:24.000000000 +0200
--- vehicle.h	2006-06-05 14:59:52.000000000 +0200
***************
*** 297,302 ****
--- 297,304 ----
  void AgeVehicle(Vehicle *v);
  void VehicleEnteredDepotThisTick(Vehicle *v);
  
+ void IncrementVehicleNameOnClone(Vehicle *v, Vehicle *w, uint32 flags);
+ 
  void BeginVehicleMove(Vehicle *v);
  void EndVehicleMove(Vehicle *v);

It was a bit of a struggle, as I've never actually written an application in c. It also took me a while to figure out how things are done in OpenTTD, but sure enough, it does what I set out to do.

I've played the game with this patch applied, and I prefer it that way. However, the developers didn't think it was a good idea, neither for technical reasons nor gameplay, and it's not going to be an actual thing in the game. I think problem is a legitimate problem and that it should, and probably will, be addressed at some point.

:: random entries in this category ::