(svn r654) Hopefully complete support for randomized variational spritegroups (i.e. the cars transporter in DBSetXL gets different cars each time) (pasky)
authortron
Wed, 17 Nov 2004 08:52:47 +0000
changeset 445 beafc0fb8f12
parent 444 6590870379ad
child 446 7a92ca9738e2
(svn r654) Hopefully complete support for randomized variational spritegroups (i.e. the cars transporter in DBSetXL gets different cars each time) (pasky)
aircraft_cmd.c
economy.c
engine.c
engine.h
grfspecial.c
roadveh_cmd.c
ship_cmd.c
sprite.c
sprite.h
station_cmd.c
train_cmd.c
vehicle.c
vehicle.h
--- a/aircraft_cmd.c	Wed Nov 17 08:11:24 2004 +0000
+++ b/aircraft_cmd.c	Wed Nov 17 08:52:47 2004 +0000
@@ -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);
 
--- a/economy.c	Wed Nov 17 08:11:24 2004 +0000
+++ b/economy.c	Wed Nov 17 08:52:47 2004 +0000
@@ -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);
 
--- a/engine.c	Wed Nov 17 08:11:24 2004 +0000
+++ b/engine.c	Wed Nov 17 08:52:47 2004 +0000
@@ -243,9 +243,15 @@
 	_engine_custom_sprites[engine][cargo] = *group;
 }
 
+typedef struct RealSpriteGroup *(*resolve_callback)(struct SpriteGroup *spritegroup,
+                                                    struct Vehicle *veh,
+                                                    void *callback); /* XXX data pointer used as function pointer */
+
 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 == 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 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;
+	bool 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 != 0);
+	}
+
+	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 != NULL)
+				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 != NULL)
+				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 != NULL)
+				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)
--- a/engine.h	Wed Nov 17 08:11:24 2004 +0000
+++ b/engine.h	Wed Nov 17 08:52:47 2004 +0000
@@ -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);
 
--- a/grfspecial.c	Wed Nov 17 08:11:24 2004 +0000
+++ b/grfspecial.c	Wed Nov 17 08:52:47 2004 +0000
@@ -1175,8 +1175,48 @@
 
 		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(*group));
+		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;
 	}
 
--- a/roadveh_cmd.c	Wed Nov 17 08:11:24 2004 +0000
+++ b/roadveh_cmd.c	Wed Nov 17 08:52:47 2004 +0000
@@ -1366,6 +1366,7 @@
 
 	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);
--- a/ship_cmd.c	Wed Nov 17 08:11:24 2004 +0000
+++ b/ship_cmd.c	Wed Nov 17 08:52:47 2004 +0000
@@ -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);
 
--- a/sprite.c	Wed Nov 17 08:11:24 2004 +0000
+++ b/sprite.c	Wed Nov 17 08:52:47 2004 +0000
@@ -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;
+}
--- a/sprite.h	Wed Nov 17 08:11:24 2004 +0000
+++ b/sprite.h	Wed Nov 17 08:52:47 2004 +0000
@@ -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
--- a/station_cmd.c	Wed Nov 17 08:11:24 2004 +0000
+++ b/station_cmd.c	Wed Nov 17 08:52:47 2004 +0000
@@ -1082,7 +1082,7 @@
 		}
 
 		default:
-		case SGT_RANDOM:
+		case SGT_RANDOMIZED:
 			error("I don't know how to handle random spritegroups yet!");
 			return NULL;
 	}
--- a/train_cmd.c	Wed Nov 17 08:11:24 2004 +0000
+++ b/train_cmd.c	Wed Nov 17 08:52:47 2004 +0000
@@ -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);
 
--- a/vehicle.c	Wed Nov 17 08:11:24 2004 +0000
+++ b/vehicle.c	Wed Nov 17 08:52:47 2004 +0000
@@ -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,8 +1592,13 @@
 	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()
 };
--- a/vehicle.h	Wed Nov 17 08:11:24 2004 +0000
+++ b/vehicle.h	Wed Nov 17 08:52:47 2004 +0000
@@ -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