Index: station_cmd.c
===================================================================
--- station_cmd.c	(revision 641)
+++ station_cmd.c	(working copy)
@@ -1082,7 +1082,7 @@
 		}
 
 		default:
-		case SGT_RANDOM:
+		case SGT_RANDOMIZED:
 			error("I don't know how to handle random spritegroups yet!");
 			return NULL;
 	}
Index: engine.c
===================================================================
--- engine.c	(revision 641)
+++ engine.c	(working copy)
@@ -243,9 +243,15 @@
 	_engine_custom_sprites[engine][cargo] = *group;
 }
 
+typedef struct RealSpriteGroup *(*resolve_callback)(struct SpriteGroup *spritegroup,
+                                                    struct Vehicle *veh,
+                                                    void *callback);
+
 static struct RealSpriteGroup *
-ResolveVehicleSpriteGroup(struct SpriteGroup *spritegroup, struct Vehicle *veh)
+ResolveVehicleSpriteGroup(struct SpriteGroup *spritegroup, struct Vehicle *veh,
+			  resolve_callback callback)
 {
+	//debug("spgt %d", spritegroup->type);
 	switch (spritegroup->type) {
 		case SGT_REAL:
 			return &spritegroup->g.real;
@@ -274,7 +280,7 @@
 					} else {
 						target = dsg->default_group;
 					}
-					return ResolveVehicleSpriteGroup(target, NULL);
+					return callback(target, NULL, callback);
 				}
 
 				if (dsg->var_scope == VSG_SCOPE_PARENT) {
@@ -393,33 +399,44 @@
 			}
 
 			target = value != -1 ? EvalDeterministicSpriteGroup(dsg, value) : dsg->default_group;
-			//debug("Resolved variable %x: %d", dsg->variable, value);
-			return ResolveVehicleSpriteGroup(target, veh);
+			//debug("Resolved variable %x: %d, %p", dsg->variable, value, callback);
+			return callback(target, veh, callback);
 		}
 
+		case SGT_RANDOMIZED: {
+			struct RandomizedSpriteGroup *rsg = &spritegroup->g.random;
+
+			if (!veh) {
+				/* 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 callback(&rsg->groups[0], NULL, callback);
+			}
+
+			if (rsg->var_scope == VSG_SCOPE_PARENT) {
+				/* First engine in the vehicle chain */
+				if (veh->type == VEH_Train)
+					veh = GetFirstVehicleInChain(veh);
+			}
+
+			return callback(EvalRandomizedSpriteGroup(rsg, veh->random_bits), veh, callback);
+		}
+
 		default:
-		case SGT_RANDOM:
-			error("I don't know how to handle random spritegroups yet!");
+			error("I don't know how to handle such a spritegroup %d!", spritegroup->type);
 			return NULL;
 	}
 }
 
-int GetCustomEngineSprite(byte engine, Vehicle *v, byte direction)
+static struct SpriteGroup *GetVehicleSpriteGroup(byte engine, Vehicle *v)
 {
 	struct SpriteGroup *group;
-	struct RealSpriteGroup *rsg;
 	uint16 overriding_engine = -1;
 	byte cargo = CID_PURCHASE;
-	byte loaded = 0;
-	byte in_motion = 0;
-	int totalsets, spriteset;
-	int r;
 
 	if (v != NULL) {
 		overriding_engine = v->type == VEH_Train ? v->u.rail.first_engine : -1;
 		cargo = _global_cargo_id[_opt.landscape][v->cargo_type];
-		loaded = ((v->cargo_count + 1) * 100) / (v->cargo_cap + 1);
-		in_motion = !!v->cur_speed;
 	}
 
 	group = &_engine_custom_sprites[engine][cargo];
@@ -431,11 +448,35 @@
 		if (overset) group = overset;
 	}
 
-	rsg = ResolveVehicleSpriteGroup(group, v);
+	return group;
+}
 
+int GetCustomEngineSprite(byte engine, Vehicle *v, byte direction)
+{
+	struct SpriteGroup *group;
+	struct RealSpriteGroup *rsg;
+	byte cargo = CID_PURCHASE;
+	byte loaded = 0;
+	byte in_motion = 0;
+	int totalsets, spriteset;
+	int r;
+
+	if (v != NULL) {
+		int capacity = v->cargo_cap;
+
+		cargo = _global_cargo_id[_opt.landscape][v->cargo_type];
+		if (capacity == 0) capacity = 1;
+		loaded = (v->cargo_count * 100) / capacity;
+		in_motion = !!v->cur_speed;
+	}
+
+	group = GetVehicleSpriteGroup(engine, v);
+	rsg = ResolveVehicleSpriteGroup(group, v, (resolve_callback) ResolveVehicleSpriteGroup);
+
 	if (rsg->sprites_per_set == 0 && cargo != 29) { /* XXX magic number */
 		// This group is empty but perhaps there'll be a default one.
-		rsg = ResolveVehicleSpriteGroup(&_engine_custom_sprites[engine][29], v);
+		rsg = ResolveVehicleSpriteGroup(&_engine_custom_sprites[engine][29], v,
+		                                (resolve_callback) ResolveVehicleSpriteGroup);
 	}
 
 	if (!rsg->sprites_per_set) {
@@ -470,6 +511,85 @@
 }
 
 
+// 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;
+
+extern int _custom_sprites_base;
+
+static struct RealSpriteGroup *
+TriggerVehicleSpriteGroup(struct SpriteGroup *spritegroup, struct Vehicle *veh,
+			  resolve_callback callback)
+{
+	if (spritegroup->type == SGT_RANDOMIZED)
+		_vsg_bits_to_reseed |= RandomizedSpriteGroupTriggeredBits(&spritegroup->g.random,
+		                                                         _vsg_random_triggers,
+		                                                         &veh->waiting_triggers);
+
+	return ResolveVehicleSpriteGroup(spritegroup, veh, callback);
+}
+
+static void DoTriggerVehicle(Vehicle *veh, enum VehicleTrigger trigger, byte base_random_bits, bool first)
+{
+	struct RealSpriteGroup *rsg;
+	byte new_random_bits;
+
+	_vsg_random_triggers = trigger;
+	_vsg_bits_to_reseed = 0;
+	rsg = TriggerVehicleSpriteGroup(GetVehicleSpriteGroup(veh->engine_type, veh), veh,
+	                                (resolve_callback) TriggerVehicleSpriteGroup);
+	if (rsg->sprites_per_set == 0 && veh->cargo_type != 29) { /* XXX magic number */
+		// This group turned out to be empty but perhaps there'll be a default one.
+		rsg = TriggerVehicleSpriteGroup(&_engine_custom_sprites[veh->engine_type][29], veh,
+						(resolve_callback) TriggerVehicleSpriteGroup);
+	}
+	veh->random_bits &= ~_vsg_bits_to_reseed;
+	veh->random_bits |= (first ? (new_random_bits = Random()) : base_random_bits) & _vsg_bits_to_reseed;
+
+	switch (trigger) {
+		case VEHICLE_TRIGGER_NEW_CARGO:
+			/* All vehicles in chain get ANY_NEW_CARGO trigger now.
+			 * So we call it for the first one and they will recurse. */
+			/* Indexing part of vehicle random bits needs to be
+			 * same for all triggered vehicles in the chain (to get
+			 * all the random-cargo wagons carry the same cargo,
+			 * i.e.), so we give them all the NEW_CARGO triggered
+			 * vehicle's portion of random bits. */
+			assert(first);
+			DoTriggerVehicle(GetFirstVehicleInChain(veh), VEHICLE_TRIGGER_ANY_NEW_CARGO, new_random_bits, false);
+			break;
+		case VEHICLE_TRIGGER_DEPOT:
+			/* We now trigger the next vehicle in chain recursively.
+			 * The random bits portions may be different for each
+			 * vehicle in chain. */
+			if (veh->next)
+				DoTriggerVehicle(veh->next, trigger, 0, true);
+			break;
+		case VEHICLE_TRIGGER_EMPTY:
+			/* We now trigger the next vehicle in chain
+			 * recursively.  The random bits portions must be same
+			 * for each vehicle in chain, * so we give them all
+			 * first chained vehicle's portion of random bits. */
+			if (veh->next)
+				DoTriggerVehicle(veh->next, trigger, first ? new_random_bits : base_random_bits, false);
+			break;
+		case VEHICLE_TRIGGER_ANY_NEW_CARGO:
+			/* Now pass the trigger recursively to the next vehicle
+			 * in chain. */
+			assert(!first);
+			if (veh->next)
+				DoTriggerVehicle(veh->next, VEHICLE_TRIGGER_ANY_NEW_CARGO, base_random_bits, false);
+			break;
+	}
+}
+
+void TriggerVehicle(Vehicle *veh, enum VehicleTrigger trigger)
+{
+	DoTriggerVehicle(veh, trigger, 0, true);
+}
+
+
 static char *_engine_custom_names[256];
 
 void SetCustomEngineName(int engine, char *name)
Index: sprite.c
===================================================================
--- sprite.c	(revision 641)
+++ sprite.c	(working copy)
@@ -60,3 +60,40 @@
 			return -1;
 	}
 }
+
+struct SpriteGroup *
+EvalRandomizedSpriteGroup(struct RandomizedSpriteGroup *rsg, byte random_bits)
+{
+	byte mask;
+	byte index;
+
+	/* Noone likes mangling with bits, but you don't get around it here.
+	 * Sorry. --pasky */
+	// rsg->num_groups is always power of 2
+	mask = (rsg->num_groups - 1) << rsg->lowest_randbit;
+	index = (random_bits & mask) >> rsg->lowest_randbit;
+	assert(index < rsg->num_groups);
+	return &rsg->groups[index];
+}
+
+byte RandomizedSpriteGroupTriggeredBits(struct RandomizedSpriteGroup *rsg, byte triggers,
+                                        byte *waiting_triggers)
+{
+	byte match = rsg->triggers & (*waiting_triggers | triggers);
+	bool res;
+
+	if (rsg->cmp_mode == RSG_CMP_ANY) {
+		res = (match != 0);
+	} else { /* RSG_CMP_ALL */
+		res = (match == rsg->triggers);
+	}
+
+	if (!res) {
+		*waiting_triggers |= triggers;
+		return 0;
+	}
+
+	*waiting_triggers &= ~match;
+
+	return (rsg->num_groups - 1) << rsg->lowest_randbit;
+}
Index: engine.h
===================================================================
--- engine.h	(revision 641)
+++ engine.h	(working copy)
@@ -101,6 +101,17 @@
 #define GetCustomVehicleSprite(v, direction) GetCustomEngineSprite(v->engine_type, v, direction)
 #define GetCustomVehicleIcon(et, direction) GetCustomEngineSprite(et, NULL, direction)
 
+enum VehicleTrigger {
+	VEHICLE_TRIGGER_NEW_CARGO = 1,
+	// Externally triggered only for the first vehicle in chain
+	VEHICLE_TRIGGER_DEPOT = 2,
+	// Externally triggered only for the first vehicle in chain, only if whole chain is empty
+	VEHICLE_TRIGGER_EMPTY = 4,
+	// Not triggered externally (called for the whole chain if we got NEW_CARGO)
+	VEHICLE_TRIGGER_ANY_NEW_CARGO = 8,
+};
+void TriggerVehicle(Vehicle *veh, enum VehicleTrigger trigger);
+
 void SetCustomEngineName(int engine, char *name);
 StringID GetCustomEngineName(int engine);
 
Index: sprite.h
===================================================================
--- sprite.h	(revision 641)
+++ sprite.h	(working copy)
@@ -82,16 +82,36 @@
 	struct SpriteGroup *default_group;
 };
 
+struct RandomizedSpriteGroup {
+	// Take this object:
+	enum VarSpriteGroupScope var_scope;
+
+	// Check for these triggers:
+	enum RandomizedSpriteGroupCompareMode {
+		RSG_CMP_ANY,
+		RSG_CMP_ALL,
+	} cmp_mode;
+	byte triggers;
+
+	// Look for this in the per-object randomized bitmask:
+	byte lowest_randbit;
+	byte num_groups; // must be power of 2
+
+	// Take the group with appropriate index:
+	struct SpriteGroup *groups;
+};
+
 struct SpriteGroup {
 	enum SpriteGroupType {
 		SGT_REAL,
 		SGT_DETERMINISTIC,
-		SGT_RANDOM, /* TODO */
+		SGT_RANDOMIZED,
 	} type;
 
 	union {
 		struct RealSpriteGroup real;
 		struct DeterministicSpriteGroup determ;
+		struct RandomizedSpriteGroup random;
 	} g;
 };
 
@@ -108,4 +128,13 @@
 /* Get value of a common deterministic SpriteGroup variable. */
 int GetDeterministicSpriteValue(byte var);
 
+/* This takes randomized bitmask (probably associated with
+ * vehicle/station/whatever) and chooses corresponding SpriteGroup
+ * accordingly to the given RandomizedSpriteGroup. */
+struct SpriteGroup *EvalRandomizedSpriteGroup(struct RandomizedSpriteGroup *rsg, byte random_bits);
+/* Triggers given RandomizedSpriteGroup with given bitmask and returns and-mask
+ * of random bits to be reseeded, or zero if there were no triggers matched
+ * (then they are |ed to @waiting_triggers instead). */
+byte RandomizedSpriteGroupTriggeredBits(struct RandomizedSpriteGroup *rsg, byte triggers, byte *waiting_triggers);
+
 #endif
Index: aircraft_cmd.c
===================================================================
--- aircraft_cmd.c	(revision 641)
+++ aircraft_cmd.c	(working copy)
@@ -1148,6 +1148,8 @@
 
 	MaybeRenewVehicle(v, EstimateAircraftCost(v->engine_type));
 
+	TriggerVehicle(v, VEHICLE_TRIGGER_DEPOT);
+
 	if ((v->next_order & OT_MASK) == OT_GOTO_DEPOT) {
 		InvalidateWindow(WC_VEHICLE_VIEW, v->index);
 
Index: roadveh_cmd.c
===================================================================
--- roadveh_cmd.c	(revision 641)
+++ roadveh_cmd.c	(working copy)
@@ -1366,7 +1366,9 @@
 
 	MaybeRenewVehicle(v, EstimateRoadVehCost(v->engine_type));
 
+	TriggerVehicle(v, VEHICLE_TRIGGER_DEPOT);
 
+
 	if ((v->next_order&OT_MASK) == OT_GOTO_DEPOT) {
 		InvalidateWindow(WC_VEHICLE_VIEW, v->index);
 
Index: train_cmd.c
===================================================================
--- train_cmd.c	(revision 641)
+++ train_cmd.c	(working copy)
@@ -2544,6 +2544,8 @@
 
 	MaybeRenewVehicle(v, EstimateTrainCost(&_rail_vehicle_info[v->engine_type]));
 
+	TriggerVehicle(v, VEHICLE_TRIGGER_DEPOT);
+
 	if ((v->next_order&OT_MASK) == OT_GOTO_DEPOT) {
 		InvalidateWindow(WC_VEHICLE_VIEW, v->index);
 
Index: vehicle.c
===================================================================
--- vehicle.c	(revision 641)
+++ vehicle.c	(working copy)
@@ -164,6 +164,7 @@
 	v->next = NULL;
 	v->next_hash = 0xffff;
 	v->string_id = 0;
+	v->random_bits = RandomRange(256);
 	return v;
 }
 
@@ -1548,6 +1549,7 @@
 	SLE_VAR(Vehicle,x_offs,						SLE_INT8),
 	SLE_VAR(Vehicle,y_offs,						SLE_INT8),
 	SLE_VAR(Vehicle,engine_type,			SLE_UINT16),
+
 	SLE_VAR(Vehicle,max_speed,				SLE_UINT16),
 	SLE_VAR(Vehicle,cur_speed,				SLE_UINT16),
 	SLE_VAR(Vehicle,subspeed,					SLE_UINT8),
@@ -1590,9 +1592,14 @@
 	SLE_VAR(Vehicle,profit_last_year,	SLE_INT32),
 	SLE_VAR(Vehicle,value,						SLE_UINT32),
 
-	// reserve extra space in savegame here. (currently 16 bytes)
-	SLE_CONDARR(NullStruct,null,SLE_FILE_U64 | SLE_VAR_NULL, 2, 2, 255),
+	SLE_VAR(Vehicle,random_bits,       SLE_UINT8),
+	SLE_VAR(Vehicle,waiting_triggers,  SLE_UINT8),
 
+	// reserve extra space in savegame here. (currently 14 bytes)
+	SLE_CONDARR(NullStruct,null,SLE_FILE_U8 | SLE_VAR_NULL, 2, 2, 255), /* 2 */
+	SLE_CONDARR(NullStruct,null,SLE_FILE_U16 | SLE_VAR_NULL, 2, 2, 255), /* 4 */
+	SLE_CONDARR(NullStruct,null,SLE_FILE_U32 | SLE_VAR_NULL, 2, 2, 255), /* 8 */
+
 	SLE_END()
 };
 
Index: vehicle.h
===================================================================
--- vehicle.h	(revision 641)
+++ vehicle.h	(working copy)
@@ -107,10 +107,10 @@
 	byte z_pos;
 	byte direction;		// facing
 
-	uint16 cur_image; // sprite number for this vehicle
 	byte spritenum; // currently displayed sprite index
 	                // 0xfd == custom sprite, 0xfe == custom second head sprite
 	                // 0xff == reserved for another custom sprite
+	uint16 cur_image; // sprite number for this vehicle
 	byte sprite_width;// width of vehicle sprite
 	byte sprite_height;// height of vehicle sprite
 	byte z_height;		// z-height of vehicle sprite
@@ -118,6 +118,12 @@
 	int8 y_offs;			// y offset for vehicle sprite
 	uint16 engine_type;
 
+	// for randomized variational spritegroups
+	// bitmask used to resolve them; parts of it get reseeded when triggers
+	// of corresponding spritegroups get matched
+	byte random_bits;
+	byte waiting_triggers; // triggers to be yet matched
+
 	uint16 max_speed;	// maximum speed
 	uint16 cur_speed;	// current speed
 	byte subspeed;		// fractional speed
Index: economy.c
===================================================================
--- economy.c	(revision 641)
+++ economy.c	(working copy)
@@ -13,6 +13,7 @@
 #include "town.h"
 #include "network.h"
 #include "sound.h"
+#include "engine.h"
 
 void UpdatePlayerHouse(Player *p, uint score)
 {
@@ -1210,6 +1211,7 @@
 	int t;
 	uint count, cap;
 	byte old_player;
+	bool completely_empty = true;
 
 	assert((v->next_order&0x1F) == OT_LOADING);
 
@@ -1253,6 +1255,9 @@
 				result |= 2;
 				v->cargo_count = 0;
 			}
+
+			if (v->cargo_count != 0)
+				completely_empty = false;
 		}
 
 		/* don't pick up goods that we unloaded */
@@ -1272,6 +1277,18 @@
 		//  has capacity for it, load it on the vehicle.
 		if ((count=ge->waiting_acceptance & 0xFFF) != 0 &&
 				(cap = v->cargo_cap - v->cargo_count) != 0) {
+			if (v->cargo_count == 0)
+				TriggerVehicle(v, VEHICLE_TRIGGER_NEW_CARGO);
+
+			/* TODO: Regarding this, when we do gradual loading, we
+			 * should first unload all vehicles and then start
+			 * loading them. Since this will cause
+			 * VEHICLE_TRIGGER_EMPTY to be called at the time when
+			 * the whole vehicle chain is really totally empty, the
+			 * @completely_empty assignment can then be safely
+			 * removed; that's how TTDPatch behaves too. --pasky */
+			completely_empty = false;
+
 			if (cap > count) cap = count;
 			v->cargo_count += cap;
 			ge->waiting_acceptance -= cap;
@@ -1304,6 +1321,10 @@
 
 	v->load_unload_time_rem = unloading_time;
 
+	if (completely_empty) {
+		TriggerVehicle(v, VEHICLE_TRIGGER_EMPTY);
+	}
+
 	if (result != 0) {
 		InvalidateWindow(WC_VEHICLE_DETAILS, v->index);
 
Index: ship_cmd.c
===================================================================
--- ship_cmd.c	(revision 641)
+++ ship_cmd.c	(working copy)
@@ -392,6 +392,8 @@
 
 	MaybeRenewVehicle(v, EstimateShipCost(v->engine_type));
 
+	TriggerVehicle(v, VEHICLE_TRIGGER_DEPOT);
+
 	if ((v->next_order&OT_MASK) == OT_GOTO_DEPOT) {
 		InvalidateWindow(WC_VEHICLE_VIEW, v->index);
 
Index: grfspecial.c
===================================================================
--- grfspecial.c	(revision 641)
+++ grfspecial.c	(working copy)
@@ -1138,8 +1138,47 @@
 
 		return;
 
-	} else if (numloaded & 0x80) {
-		grfmsg(GMS_WARN, "NewSpriteGroup(0x%x): Unsupported special group.", numloaded);
+	} else if (numloaded == 0x80 || numloaded == 0x83) {
+		struct RandomizedSpriteGroup *rg;
+		int i;
+
+		/* This stuff is getting actually evaluated in
+		 * EvalRandomizedSpriteGroup(). */
+
+		buf += 4; len -= 4;
+		check_length(len, 6, "NewSpriteGroup 0x80/0x83");
+
+		if (setid >= _cur_grffile->spritegroups_count) {
+			_cur_grffile->spritegroups_count = setid + 1;
+			_cur_grffile->spritegroups = realloc(_cur_grffile->spritegroups, _cur_grffile->spritegroups_count * sizeof(struct SpriteGroup));
+		}
+
+		group = &_cur_grffile->spritegroups[setid];
+		memset(group, 0, sizeof(struct SpriteGroup));
+		group->type = SGT_RANDOMIZED;
+		rg = &group->g.random;
+
+		/* XXX: We don't free() anything, assuming that if there was
+		 * some action here before, it got associated by action 3.
+		 * We should perhaps keep some refcount? --pasky */
+
+		rg->var_scope = numloaded == 0x83 ? VSG_SCOPE_PARENT : VSG_SCOPE_SELF;
+
+		rg->triggers = grf_load_byte(&buf);
+		rg->cmp_mode = rg->triggers & 0x80;
+		rg->triggers &= 0x7F;
+
+		rg->lowest_randbit = grf_load_byte(&buf);
+		rg->num_groups = grf_load_byte(&buf);
+
+		rg->groups = calloc(rg->num_groups, sizeof(*rg->groups));
+		for (i = 0; i < rg->num_groups; i++) {
+			uint16 groupid = grf_load_word(&buf);
+			/* XXX: If multiple surreal sets attach a surreal
+			 * set this way, we are in trouble. */
+			rg->groups[i] = _cur_grffile->spritegroups[groupid];
+		}
+
 		return;
 	}
 
