newgrf_engine.c
changeset 3686 16627c28741d
parent 3684 4bb59d048c6e
child 3693 c154dbd20439
--- a/newgrf_engine.c	Fri Apr 28 19:56:18 2006 +0000
+++ b/newgrf_engine.c	Fri Apr 28 20:04:57 2006 +0000
@@ -2,16 +2,16 @@
 
 #include "stdafx.h"
 #include "openttd.h"
+#include "variables.h"
 #include "debug.h"
 #include "functions.h"
-#include "string.h"
-#include "strings.h"
 #include "engine.h"
+#include "train.h"
+#include "player.h"
 #include "newgrf_callbacks.h"
 #include "newgrf_engine.h"
-#include "sprite.h"
-#include "variables.h"
-#include "train.h"
+#include "newgrf_station.h"
+#include "newgrf_spritegroup.h"
 
 // TODO: We don't support cargo-specific wagon overrides. Pretty exotic... ;-) --pasky
 
@@ -147,10 +147,171 @@
 	return 2;
 }
 
-static int VehicleSpecificProperty(const Vehicle *v, byte var) {
+
+/* Vehicle Resolver Functions */
+static inline const Vehicle *GRV(const ResolverObject *object)
+{
+	return object->scope == VSG_SCOPE_SELF ? object->vehicle.self : object->vehicle.parent;
+}
+
+
+static uint32 VehicleGetRandomBits(const ResolverObject *object)
+{
+	return GRV(object) == NULL ? 0 : GRV(object)->random_bits;
+}
+
+
+static uint32 VehicleGetTriggers(const ResolverObject *object)
+{
+	return GRV(object) == NULL ? 0 : GRV(object)->waiting_triggers;
+}
+
+
+static void VehicleSetTriggers(const ResolverObject *object, int triggers)
+{
+	/* Evil cast to get around const-ness. This used to be achieved by an
+	 * innocent looking function pointer cast... Currently I cannot see a
+	 * way of avoiding this without removing consts deep within gui code.
+	 */
+	Vehicle *v = (Vehicle*)GRV(object);
+
+	/* This function must only be called when processing triggers -- any
+	 * other time is an error. */
+	assert(object->trigger != 0);
+
+	if (v != NULL) v->waiting_triggers = triggers;
+}
+
+
+static uint32 VehicleGetVariable(const ResolverObject *object, byte variable, byte parameter)
+{
+	const Vehicle *v = GRV(object);
+
+	if (v == NULL) {
+		/* Vehicle does not exist, so we're in a purchase list */
+		switch (variable) {
+			case 0x43: return _current_player; /* Owner information */
+			case 0x46: return 0;               /* Motion counter */
+			case 0xC4: return _cur_year;       /* Build year */
+			case 0xDA: return INVALID_VEHICLE; /* Next vehicle */
+			default:   return -1;
+		}
+	}
+
+	/* Calculated vehicle parameters */
+	switch (variable) {
+		case 0x40: /* Get length of consist */
+		case 0x41: /* Get length of same consecutive wagons */
+			if (v->type != VEH_Train) return 1;
+
+			{
+				const Vehicle* u;
+				byte chain_before = 0;
+				byte chain_after  = 0;
+
+				for (u = GetFirstVehicleInChain(v); u != v; u = u->next) {
+					chain_before++;
+					if (variable == 0x41 && u->engine_type != v->engine_type) chain_before = 0;
+				}
+
+				while (u->next != NULL && (variable == 0x40 || u->next->engine_type == v->engine_type)) {
+					chain_after++;
+					u = u->next;
+				}
+
+				return chain_before | chain_after << 8 | (chain_before + chain_after) << 16;
+			}
+
+		case 0x43: /* Player information */
+			return v->owner;
+
+		case 0x46: /* Motion counter */
+			return 0;
+	}
+
+	/* General vehicle properties */
+	switch (variable - 0x80) {
+		case 0x00: return v->type;
+		case 0x01: return MapOldSubType(v);
+		case 0x04: return v->index;
+		case 0x05: return v->index & 0xFF;
+		case 0x0A: return PackOrder(&v->current_order);
+		case 0x0B: return PackOrder(&v->current_order) & 0xFF;
+		case 0x0C: return v->num_orders;
+		case 0x0D: return v->cur_order_index;
+		case 0x10: return v->load_unload_time_rem;
+		case 0x11: return v->load_unload_time_rem & 0xFF;
+		case 0x12: return v->date_of_last_service;
+		case 0x13: return v->date_of_last_service & 0xFF;
+		case 0x14: return v->service_interval;
+		case 0x15: return v->service_interval & 0xFF;
+		case 0x16: return v->last_station_visited;
+		case 0x17: return v->tick_counter;
+		case 0x18: return v->max_speed;
+		case 0x19: return v->max_speed & 0xFF;
+		case 0x1A: return v->x_pos;
+		case 0x1B: return v->x_pos & 0xFF;
+		case 0x1C: return v->y_pos;
+		case 0x1D: return v->y_pos & 0xFF;
+		case 0x1E: return v->z_pos;
+		case 0x1F: return v->direction;
+		case 0x28: return v->cur_image;
+		case 0x29: return v->cur_image & 0xFF;
+		case 0x32: return v->vehstatus;
+		case 0x33: return v->vehstatus;
+		case 0x34: return v->cur_speed;
+		case 0x35: return v->cur_speed & 0xFF;
+		case 0x36: return v->subspeed;
+		case 0x37: return v->acceleration;
+		case 0x39: return v->cargo_type;
+		case 0x3A: return v->cargo_cap;
+		case 0x3B: return v->cargo_cap & 0xFF;
+		case 0x3C: return v->cargo_count;
+		case 0x3D: return v->cargo_count & 0xFF;
+		case 0x3E: return v->cargo_source;
+		case 0x3F: return v->cargo_days;
+		case 0x40: return v->age;
+		case 0x41: return v->age & 0xFF;
+		case 0x42: return v->max_age;
+		case 0x43: return v->max_age & 0xFF;
+		case 0x44: return v->build_year;
+		case 0x45: return v->unitnumber;
+		case 0x46: return v->engine_type;
+		case 0x47: return v->engine_type & 0xFF;
+		case 0x48: return v->spritenum;
+		case 0x49: return v->day_counter;
+		case 0x4A: return v->breakdowns_since_last_service;
+		case 0x4B: return v->breakdown_ctr;
+		case 0x4C: return v->breakdown_delay;
+		case 0x4D: return v->breakdown_chance;
+		case 0x4E: return v->reliability;
+		case 0x4F: return v->reliability & 0xFF;
+		case 0x50: return v->reliability_spd_dec;
+		case 0x51: return v->reliability_spd_dec & 0xFF;
+		case 0x52: return v->profit_this_year;
+		case 0x53: return v->profit_this_year & 0xFFFFFF;
+		case 0x54: return v->profit_this_year & 0xFFFF;
+		case 0x55: return v->profit_this_year & 0xFF;
+		case 0x56: return v->profit_last_year;
+		case 0x57: return v->profit_last_year & 0xFF;
+		case 0x58: return v->profit_last_year;
+		case 0x59: return v->profit_last_year & 0xFF;
+		case 0x5A: return v->next == NULL ? INVALID_VEHICLE : v->next->index;
+		case 0x5C: return v->value;
+		case 0x5D: return v->value & 0xFFFFFF;
+		case 0x5E: return v->value & 0xFFFF;
+		case 0x5F: return v->value & 0xFF;
+		case 0x60: return v->string_id;
+		case 0x61: return v->string_id & 0xFF;
+		case 0x72: return 0; // XXX Refit cycle
+		case 0x7A: return v->random_bits;
+		case 0x7B: return v->waiting_triggers;
+	}
+
+	/* Vehicle specific properties */
 	switch (v->type) {
 		case VEH_Train:
-			switch (var) {
+			switch (variable - 0x80) {
 				case 0x62: return v->u.rail.track;
 				case 0x66: return v->u.rail.railtype;
 				case 0x73: return v->u.rail.cached_veh_length;
@@ -164,7 +325,7 @@
 			break;
 
 		case VEH_Road:
-			switch (var) {
+			switch (variable - 0x80) {
 				case 0x62: return v->u.road.state;
 				case 0x64: return v->u.road.blocked_ctr;
 				case 0x65: return v->u.road.blocked_ctr & 0xFF;
@@ -176,7 +337,7 @@
 			break;
 
 		case VEH_Aircraft:
-			switch (var) {
+			switch (variable - 0x80) {
 				// case 0x62: XXX Need to convert from ottd to ttdp state
 				case 0x63: return v->u.air.targetairport;
 				// case 0x66: XXX
@@ -184,211 +345,71 @@
 			break;
 	}
 
-	DEBUG(grf, 1)("Unhandled vehicle property 0x%02X (var 0x%02X), type 0x%02X", var, var + 0x80, v->type);
+	DEBUG(grf, 1)("Unhandled vehicle property 0x%X, type 0x%X", variable, v->type);
 
 	return -1;
 }
 
-typedef SpriteGroup *(*resolve_callback)(const SpriteGroup *spritegroup,
-	const Vehicle *veh, uint16 callback_info, void *resolve_func); /* XXX data pointer used as function pointer */
-
-static const SpriteGroup* ResolveVehicleSpriteGroup(const SpriteGroup *spritegroup,
-	const Vehicle *veh, uint16 callback_info, resolve_callback resolve_func)
-{
-	if (spritegroup == NULL)
-		return NULL;
-
-	//debug("spgt %d", spritegroup->type);
-	switch (spritegroup->type) {
-		case SGT_REAL:
-		case SGT_CALLBACK:
-			return spritegroup;
-
-		case SGT_DETERMINISTIC: {
-			const DeterministicSpriteGroup *dsg = &spritegroup->g.determ;
-			const SpriteGroup *target;
-			int value = -1;
-			// XXX Temporary support
-			byte variable = dsg->adjusts[0].variable;
-
-			//debug("[%p] Having fun resolving variable %x", veh, variable);
-			if (variable == 0x0C) {
-				/* Callback ID */
-				value = callback_info & 0xFF;
-			} else if (variable == 0x10) {
-				value = (callback_info >> 8) & 0xFF;
-			} else if ((variable >> 6) == 0) {
-				/* General property */
-				value = GetDeterministicSpriteValue(variable);
-			} else {
-				/* Vehicle-specific property. */
-
-				if (veh == NULL) {
-					/* We are in a purchase list of something,
-					 * and we are checking for something undefined.
-					 * That means we should get the first target
-					 * (NOT the default one). */
-					if (dsg->num_ranges > 0) {
-						target = dsg->ranges[0].group;
-					} else {
-						target = dsg->default_group;
-					}
-					return resolve_func(target, NULL, callback_info, resolve_func);
-				}
-
-				if (dsg->var_scope == VSG_SCOPE_PARENT) {
-					/* First engine in the vehicle chain */
-					if (veh->type == VEH_Train)
-						veh = GetFirstVehicleInChain(veh);
-				}
-
-				if (variable == 0x40 || variable == 0x41) {
-					if (veh->type == VEH_Train) {
-						const Vehicle *u = GetFirstVehicleInChain(veh);
-						byte chain_before = 0, chain_after = 0;
-
-						while (u != veh) {
-							chain_before++;
-							if (variable == 0x41 && u->engine_type != veh->engine_type)
-								chain_before = 0;
-							u = u->next;
-						}
-						while (u->next != NULL && (variable == 0x40 || u->next->engine_type == veh->engine_type)) {
-							chain_after++;
-							u = u->next;
-						};
-
-						value = chain_before | chain_after << 8
-						        | (chain_before + chain_after) << 16;
-					} else {
-						value = 1; /* 1 vehicle in the chain */
-					}
 
-				} else {
-					// TTDPatch runs on little-endian arch;
-					// Variable is 0x80 + offset in TTD's vehicle structure
-					switch (variable - 0x80) {
-#define veh_prop(id_, value_) case (id_): value = (value_); break
-						veh_prop(0x00, veh->type);
-						veh_prop(0x01, MapOldSubType(veh));
-						veh_prop(0x04, veh->index);
-						veh_prop(0x05, veh->index & 0xFF);
-						/* XXX? Is THIS right? */
-						veh_prop(0x0A, PackOrder(&veh->current_order));
-						veh_prop(0x0B, PackOrder(&veh->current_order) & 0xff);
-						veh_prop(0x0C, veh->num_orders);
-						veh_prop(0x0D, veh->cur_order_index);
-						veh_prop(0x10, veh->load_unload_time_rem);
-						veh_prop(0x11, veh->load_unload_time_rem & 0xFF);
-						veh_prop(0x12, veh->date_of_last_service);
-						veh_prop(0x13, veh->date_of_last_service & 0xFF);
-						veh_prop(0x14, veh->service_interval);
-						veh_prop(0x15, veh->service_interval & 0xFF);
-						veh_prop(0x16, veh->last_station_visited);
-						veh_prop(0x17, veh->tick_counter);
-						veh_prop(0x18, veh->max_speed);
-						veh_prop(0x19, veh->max_speed & 0xFF);
-						veh_prop(0x1A, veh->x_pos);
-						veh_prop(0x1B, veh->x_pos & 0xFF);
-						veh_prop(0x1C, veh->y_pos);
-						veh_prop(0x1D, veh->y_pos & 0xFF);
-						veh_prop(0x1E, veh->z_pos);
-						veh_prop(0x1F, veh->direction);
-						veh_prop(0x28, veh->cur_image);
-						veh_prop(0x29, veh->cur_image & 0xFF);
-						veh_prop(0x32, veh->vehstatus);
-						veh_prop(0x33, veh->vehstatus);
-						veh_prop(0x34, veh->cur_speed);
-						veh_prop(0x35, veh->cur_speed & 0xFF);
-						veh_prop(0x36, veh->subspeed);
-						veh_prop(0x37, veh->acceleration);
-						veh_prop(0x39, veh->cargo_type);
-						veh_prop(0x3A, veh->cargo_cap);
-						veh_prop(0x3B, veh->cargo_cap & 0xFF);
-						veh_prop(0x3C, veh->cargo_count);
-						veh_prop(0x3D, veh->cargo_count & 0xFF);
-						veh_prop(0x3E, veh->cargo_source); // Probably useless; so what
-						veh_prop(0x3F, veh->cargo_days);
-						veh_prop(0x40, veh->age);
-						veh_prop(0x41, veh->age & 0xFF);
-						veh_prop(0x42, veh->max_age);
-						veh_prop(0x43, veh->max_age & 0xFF);
-						veh_prop(0x44, veh->build_year);
-						veh_prop(0x45, veh->unitnumber);
-						veh_prop(0x46, veh->engine_type);
-						veh_prop(0x47, veh->engine_type & 0xFF);
-						veh_prop(0x48, veh->spritenum);
-						veh_prop(0x49, veh->day_counter);
-						veh_prop(0x4A, veh->breakdowns_since_last_service);
-						veh_prop(0x4B, veh->breakdown_ctr);
-						veh_prop(0x4C, veh->breakdown_delay);
-						veh_prop(0x4D, veh->breakdown_chance);
-						veh_prop(0x4E, veh->reliability);
-						veh_prop(0x4F, veh->reliability & 0xFF);
-						veh_prop(0x50, veh->reliability_spd_dec);
-						veh_prop(0x51, veh->reliability_spd_dec & 0xFF);
-						veh_prop(0x52, veh->profit_this_year);
-						veh_prop(0x53, veh->profit_this_year & 0xFFFFFF);
-						veh_prop(0x54, veh->profit_this_year & 0xFFFF);
-						veh_prop(0x55, veh->profit_this_year & 0xFF);
-						veh_prop(0x56, veh->profit_last_year);
-						veh_prop(0x57, veh->profit_last_year & 0xFF);
-						veh_prop(0x58, veh->profit_last_year);
-						veh_prop(0x59, veh->profit_last_year & 0xFF);
-						veh_prop(0x5A, veh->next == NULL ? INVALID_VEHICLE : veh->next->index);
-						veh_prop(0x5C, veh->value);
-						veh_prop(0x5D, veh->value & 0xFFFFFF);
-						veh_prop(0x5E, veh->value & 0xFFFF);
-						veh_prop(0x5F, veh->value & 0xFF);
-						veh_prop(0x60, veh->string_id);
-						veh_prop(0x61, veh->string_id & 0xFF);
+static uint32 VehicleResolveReal(const ResolverObject *object, uint num_loaded, uint num_loading, bool *in_motion)
+{
+	const Vehicle *v = object->vehicle.self;
+	uint totalsets;
+	uint set;
 
-						veh_prop(0x72, 0); // XXX Refit cycle currently unsupported
-						veh_prop(0x7A, veh->random_bits);
-						veh_prop(0x7B, veh->waiting_triggers);
-#undef veh_prop
-
-						// Handle vehicle specific properties.
-						default: value = VehicleSpecificProperty(veh, variable - 0x80); break;
-					}
-				}
-			}
-
-			target = value != -1 ? EvalDeterministicSpriteGroup(dsg, value) : dsg->default_group;
-			//debug("Resolved variable %x: %d, %p", dsg->variable, value, callback);
-			return resolve_func(target, veh, callback_info, resolve_func);
-		}
-
-		case SGT_RANDOMIZED: {
-			const RandomizedSpriteGroup *rsg = &spritegroup->g.random;
+	if (v == NULL) {
+		*in_motion = false;
+		return 0;
+	}
 
-			if (veh == NULL) {
-				/* Purchase list of something. Show the first one. */
-				assert(rsg->num_groups > 0);
-				//debug("going for %p: %d", rsg->groups[0], rsg->groups[0].type);
-				return resolve_func(rsg->groups[0], NULL, callback_info, resolve_func);
-			}
+	if (v->type == VEH_Train) {
+		*in_motion = GetFirstVehicleInChain(v)->current_order.type != OT_LOADING;
+	} else {
+		*in_motion = v->current_order.type != OT_LOADING;
+	}
 
-			if (rsg->var_scope == VSG_SCOPE_PARENT) {
-				/* First engine in the vehicle chain */
-				if (veh->type == VEH_Train)
-					veh = GetFirstVehicleInChain(veh);
-			}
+	totalsets = in_motion ? num_loaded : num_loading;
 
-			return resolve_func(EvalRandomizedSpriteGroup(rsg, veh->random_bits), veh, callback_info, resolve_func);
-		}
+	if (v->cargo_count == v->cargo_cap || totalsets == 1) {
+		set = totalsets - 1;
+	} else if (v->cargo_count == 0 || totalsets == 2) {
+		set = 0;
+	} else {
+		set = v->cargo_count * (totalsets - 2) / max(1, v->cargo_cap) + 1;
+	}
 
-		default:
-			error("I don't know how to handle such a spritegroup %d!", spritegroup->type);
-			return NULL;
-	}
+	return set;
 }
 
-static const SpriteGroup *GetVehicleSpriteGroup(EngineID engine, const Vehicle *v)
+
+static inline void NewVehicleResolver(ResolverObject *res, const Vehicle *v)
+{
+	res->GetRandomBits = &VehicleGetRandomBits;
+	res->GetTriggers   = &VehicleGetTriggers;
+	res->SetTriggers   = &VehicleSetTriggers;
+	res->GetVariable   = &VehicleGetVariable;
+	res->ResolveReal   = &VehicleResolveReal;
+
+	res->vehicle.self   = v;
+	res->vehicle.parent = (v != NULL && v->type == VEH_Train) ? GetFirstVehicleInChain(v) : NULL;
+
+	res->callback        = 0;
+	res->callback_param1 = 0;
+	res->callback_param2 = 0;
+	res->last_value      = 0;
+	res->trigger         = 0;
+	res->reseed          = 0;
+}
+
+
+SpriteID GetCustomEngineSprite(EngineID engine, const Vehicle *v, Direction direction)
 {
 	const SpriteGroup *group;
+	ResolverObject object;
 	CargoID cargo = GC_PURCHASE;
 
+	NewVehicleResolver(&object, v);
+
 	if (v != NULL) {
 		cargo = _global_cargo_id[_opt.landscape][v->cargo_type];
 		assert(cargo != GC_INVALID);
@@ -402,76 +423,19 @@
 		if (overset != NULL) group = overset;
 	}
 
-	return group;
-}
-
-SpriteID GetCustomEngineSprite(EngineID engine, const Vehicle* v, Direction direction)
-{
-	const SpriteGroup *group;
-	const RealSpriteGroup *rsg;
-	CargoID cargo = GC_PURCHASE;
-	byte loaded = 0;
-	bool in_motion = 0;
-	int totalsets, spriteset;
+	group = Resolve(group, &object);
 
-	if (v != NULL) {
-		int capacity = v->cargo_cap;
-
-		cargo = _global_cargo_id[_opt.landscape][v->cargo_type];
-		assert(cargo != GC_INVALID);
-
-		if (capacity == 0) capacity = 1;
-		loaded = (v->cargo_count * 100) / capacity;
-
-		if (v->type == VEH_Train) {
-			in_motion = GetFirstVehicleInChain(v)->current_order.type != OT_LOADING;
-		} else {
-			in_motion = v->current_order.type != OT_LOADING;
-		}
+	if ((group == NULL || group->type != SGT_RESULT) && cargo != GC_DEFAULT) {
+		// This group is empty but perhaps there'll be a default one.
+		group = Resolve(engine_custom_sprites[engine][GC_DEFAULT], &object);
 	}
 
-	group = GetVehicleSpriteGroup(engine, v);
-	group = ResolveVehicleSpriteGroup(group, v, 0, (resolve_callback) ResolveVehicleSpriteGroup);
-
-	if (group == NULL && cargo != GC_DEFAULT) {
-		// This group is empty but perhaps there'll be a default one.
-		group = ResolveVehicleSpriteGroup(engine_custom_sprites[engine][GC_DEFAULT], v, 0,
-		                                (resolve_callback) ResolveVehicleSpriteGroup);
-	}
-
-	if (group == NULL)
-		return 0;
-
-	assert(group->type == SGT_REAL);
-	rsg = &group->g.real;
-
-	// This group is empty. This function users should therefore
-	// look up the sprite number in _engine_original_sprites.
-	if (rsg->num_loaded == 0 || rsg->num_loading == 0) return 0;
-
-	totalsets = in_motion ? rsg->num_loaded : rsg->num_loading;
-
-	// My aim here is to make it possible to visually determine absolutely
-	// empty and totally full vehicles. --pasky
-	if (loaded == 100 || totalsets == 1) { // full
-		spriteset = totalsets - 1;
-	} else if (loaded == 0 || totalsets == 2) { // empty
-		spriteset = 0;
-	} else { // something inbetween
-		spriteset = loaded * (totalsets - 2) / 100 + 1;
-		// correct possible rounding errors
-		if (!spriteset)
-			spriteset = 1;
-		else if (spriteset == totalsets - 1)
-			spriteset--;
-	}
-
-	group = in_motion ? rsg->loaded[spriteset] : rsg->loading[spriteset];
-	if (group->type != SGT_RESULT) return 0;
+	if (group == NULL || group->type != SGT_RESULT) return 0;
 
 	return group->g.result.sprite + (direction % group->g.result.num_sprites);
 }
 
+
 /**
  * Check if a wagon is currently using a wagon override
  * @param v The wagon to check
@@ -495,8 +459,14 @@
 uint16 GetVehicleCallback(byte callback, uint32 param1, uint32 param2, EngineID engine, const Vehicle *v)
 {
 	const SpriteGroup *group;
+	ResolverObject object;
 	CargoID cargo;
-	uint16 callback_info = callback | (param1 << 8); // XXX Temporary conversion between new and old format.
+
+	NewVehicleResolver(&object, v);
+
+	object.callback        = callback;
+	object.callback_param1 = param1;
+	object.callback_param2 = param2;
 
 	cargo = (v == NULL) ? GC_PURCHASE : _global_cargo_id[_opt.landscape][v->cargo_type];
 
@@ -508,12 +478,11 @@
 		if (overset != NULL) group = overset;
 	}
 
-	group = ResolveVehicleSpriteGroup(group, v, callback_info, (resolve_callback) ResolveVehicleSpriteGroup);
+	group = Resolve(group, &object);
 
 	if ((group == NULL || group->type != SGT_CALLBACK) && cargo != GC_DEFAULT) {
 		// This group is empty but perhaps there'll be a default one.
-		group = ResolveVehicleSpriteGroup(engine_custom_sprites[engine][GC_DEFAULT], v, callback_info,
-		                                (resolve_callback) ResolveVehicleSpriteGroup);
+		group = Resolve(engine_custom_sprites[engine][GC_DEFAULT], &object);
 	}
 
 	if (group == NULL || group->type != SGT_CALLBACK)
@@ -523,55 +492,40 @@
 }
 
 
-
-// Global variables are evil, yes, but we would end up with horribly overblown
-// calling convention otherwise and this should be 100% reentrant.
-static byte _vsg_random_triggers;
-static byte _vsg_bits_to_reseed;
-
-static const SpriteGroup *TriggerVehicleSpriteGroup(const SpriteGroup *spritegroup,
-	Vehicle *veh, uint16 callback_info, resolve_callback resolve_func)
-{
-	if (spritegroup == NULL)
-		return NULL;
-
-	if (spritegroup->type == SGT_RANDOMIZED) {
-		_vsg_bits_to_reseed |= RandomizedSpriteGroupTriggeredBits(
-			&spritegroup->g.random,
-			_vsg_random_triggers,
-			&veh->waiting_triggers
-		);
-	}
-
-	return ResolveVehicleSpriteGroup(spritegroup, veh, callback_info, resolve_func);
-}
-
 static void DoTriggerVehicle(Vehicle *v, VehicleTrigger trigger, byte base_random_bits, bool first)
 {
 	const SpriteGroup *group;
-	const RealSpriteGroup *rsg;
+	ResolverObject object;
+	CargoID cargo;
 	byte new_random_bits;
 
-	_vsg_random_triggers = trigger;
-	_vsg_bits_to_reseed = 0;
-	group = TriggerVehicleSpriteGroup(GetVehicleSpriteGroup(v->engine_type, v), v, 0,
-	                                  (resolve_callback) TriggerVehicleSpriteGroup);
+	/* We can't trigger a non-existent vehicle... */
+	assert(v != NULL);
 
-	if (group == NULL && v->cargo_type != GC_DEFAULT) {
-		// This group turned out to be empty but perhaps there'll be a default one.
-		group = TriggerVehicleSpriteGroup(engine_custom_sprites[v->engine_type][GC_DEFAULT], v, 0,
-		                                  (resolve_callback) TriggerVehicleSpriteGroup);
+	NewVehicleResolver(&object, v);
+
+	object.trigger = trigger;
+
+	cargo = _global_cargo_id[_opt.landscape][v->cargo_type];
+	group = engine_custom_sprites[v->engine_type][cargo];
+
+	if (v->type == VEH_Train) {
+		const SpriteGroup *overset = GetWagonOverrideSpriteSet(v->engine_type, v->u.rail.first_engine);
+		if (overset != NULL) group = overset;
 	}
 
-	if (group == NULL)
-		return;
+	group = Resolve(group, &object);
+	if (group == NULL && v->cargo_type != GC_DEFAULT) {
+		// This group is empty but perhaps there'll be a default one.
+		group = Resolve(engine_custom_sprites[v->engine_type][GC_DEFAULT], &object);
+	}
 
-	assert(group->type == SGT_REAL);
-	rsg = &group->g.real;
+	/* Really return? */
+	if (group == NULL) return;
 
 	new_random_bits = Random();
-	v->random_bits &= ~_vsg_bits_to_reseed;
-	v->random_bits |= (first ? new_random_bits : base_random_bits) & _vsg_bits_to_reseed;
+	v->random_bits &= ~object.reseed;
+	v->random_bits |= (first ? new_random_bits : base_random_bits) & object.reseed;
 
 	switch (trigger) {
 		case VEHICLE_TRIGGER_NEW_CARGO: