(svn r2389) - Feature: [newgrf] Implement the mechanism for handling newgrf callbacks.
authorhackykid
Wed, 01 Jun 2005 11:34:37 +0000
changeset 1883 3b02000cfbe0
parent 1882 73937823fdb2
child 1884 ae1d6213c6dd
(svn r2389) - Feature: [newgrf] Implement the mechanism for handling newgrf callbacks.
- Feature: [newgrf] Implement the 'refit capacity' callback.
engine.c
engine.h
newgrf.c
sprite.h
train_cmd.c
--- a/engine.c	Wed Jun 01 10:30:45 2005 +0000
+++ b/engine.c	Wed Jun 01 11:34:37 2005 +0000
@@ -317,16 +317,17 @@
 	_engine_custom_sprites[engine][cargo] = *group;
 }
 
-typedef RealSpriteGroup *(*resolve_callback)(SpriteGroup *spritegroup,
-	const Vehicle *veh, void *callback); /* XXX data pointer used as function pointer */
+typedef SpriteGroup *(*resolve_callback)(SpriteGroup *spritegroup,
+	const Vehicle *veh, uint16 callback_info, void *resolve_func); /* XXX data pointer used as function pointer */
 
-static RealSpriteGroup* ResolveVehicleSpriteGroup(SpriteGroup *spritegroup,
-	const Vehicle *veh, resolve_callback callback)
+static SpriteGroup* ResolveVehicleSpriteGroup(SpriteGroup *spritegroup,
+	const Vehicle *veh, uint16 callback_info, resolve_callback resolve_func)
 {
 	//debug("spgt %d", spritegroup->type);
 	switch (spritegroup->type) {
 		case SGT_REAL:
-			return &spritegroup->g.real;
+		case SGT_CALLBACK:
+			return spritegroup;
 
 		case SGT_DETERMINISTIC: {
 			DeterministicSpriteGroup *dsg = &spritegroup->g.determ;
@@ -334,8 +335,10 @@
 			int value = -1;
 
 			//debug("[%p] Having fun resolving variable %x", veh, dsg->variable);
-
-			if ((dsg->variable >> 6) == 0) {
+			if (dsg->variable == 0x0C) {
+				/* Callback ID */
+				value = callback_info & 0xFF;
+			} else if ((dsg->variable >> 6) == 0) {
 				/* General property */
 				value = GetDeterministicSpriteValue(dsg->variable);
 			} else {
@@ -351,7 +354,7 @@
 					} else {
 						target = dsg->default_group;
 					}
-					return callback(target, NULL, callback);
+					return resolve_func(target, NULL, callback_info, resolve_func);
 				}
 
 				if (dsg->var_scope == VSG_SCOPE_PARENT) {
@@ -472,7 +475,7 @@
 
 			target = value != -1 ? EvalDeterministicSpriteGroup(dsg, value) : dsg->default_group;
 			//debug("Resolved variable %x: %d, %p", dsg->variable, value, callback);
-			return callback(target, veh, callback);
+			return resolve_func(target, veh, callback_info, resolve_func);
 		}
 
 		case SGT_RANDOMIZED: {
@@ -482,7 +485,7 @@
 				/* 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);
+				return resolve_func(&rsg->groups[0], NULL, callback_info, resolve_func);
 			}
 
 			if (rsg->var_scope == VSG_SCOPE_PARENT) {
@@ -491,7 +494,7 @@
 					veh = GetFirstVehicleInChain(veh);
 			}
 
-			return callback(EvalRandomizedSpriteGroup(rsg, veh->random_bits), veh, callback);
+			return resolve_func(EvalRandomizedSpriteGroup(rsg, veh->random_bits), veh, callback_info, resolve_func);
 		}
 
 		default:
@@ -543,14 +546,17 @@
 	}
 
 	group = GetVehicleSpriteGroup(engine, v);
-	rsg = ResolveVehicleSpriteGroup(group, v, (resolve_callback) ResolveVehicleSpriteGroup);
+	group = ResolveVehicleSpriteGroup(group, v, 0, (resolve_callback) ResolveVehicleSpriteGroup);
 
-	if (rsg->sprites_per_set == 0 && cargo != 29) { /* XXX magic number */
+	if (group->type == SGT_REAL && group->g.real.sprites_per_set == 0 && cargo != GC_DEFAULT) {
 		// This group is empty but perhaps there'll be a default one.
-		rsg = ResolveVehicleSpriteGroup(&_engine_custom_sprites[engine][29], v,
+		group = ResolveVehicleSpriteGroup(&_engine_custom_sprites[engine][GC_DEFAULT], v, 0,
 		                                (resolve_callback) ResolveVehicleSpriteGroup);
 	}
 
+	assert(group->type == SGT_REAL);
+	rsg = &group->g.real;
+
 	if (!rsg->sprites_per_set) {
 		// This group is empty. This function users should therefore
 		// look up the sprite number in _engine_original_sprites.
@@ -582,6 +588,39 @@
 	return r;
 }
 
+/**
+ * Evaluates a newgrf callback
+ * @param callback_info info about which callback to evaluate
+ *  (bit 0-7)  = CallBack id of the callback to use, see CallBackId enum
+ *  (bit 8-15) = Other info some callbacks need to have, callback specific, see CallBackId enum, not used yet
+ * @param engine Engine type of the vehicle to evaluate the callback for
+ * @param vehicle The vehicle to evaluate the callback for, NULL if it doesnt exist (yet)
+ * @return The value the callback returned, or CALLBACK_FAILED if it failed
+ */
+uint16 GetCallBackResult(uint16 callback_info, byte engine, const Vehicle *v)
+{
+	SpriteGroup *group;
+	byte cargo = GC_DEFAULT;
+
+	if (v != NULL)
+		cargo = _global_cargo_id[_opt.landscape][v->cargo_type];
+
+	group = &_engine_custom_sprites[engine][cargo];
+	group = ResolveVehicleSpriteGroup(group, v, callback_info, (resolve_callback) ResolveVehicleSpriteGroup);
+
+	if (group->type == SGT_REAL && group->g.real.sprites_per_set == 0 && 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);
+	}
+
+	if (group->type != SGT_CALLBACK)
+		return CALLBACK_FAILED;
+
+	return group->g.callback.result;
+}
+
+
 
 // Global variables are evil, yes, but we would end up with horribly overblown
 // calling convention otherwise and this should be 100% reentrant.
@@ -590,8 +629,8 @@
 
 extern int _custom_sprites_base;
 
-static RealSpriteGroup *TriggerVehicleSpriteGroup(SpriteGroup *spritegroup,
-	Vehicle *veh, resolve_callback callback)
+static SpriteGroup *TriggerVehicleSpriteGroup(SpriteGroup *spritegroup,
+	Vehicle *veh, uint16 callback_info, resolve_callback resolve_func)
 {
 	if (spritegroup->type == SGT_RANDOMIZED) {
 		_vsg_bits_to_reseed |= RandomizedSpriteGroupTriggeredBits(
@@ -601,23 +640,29 @@
 		);
 	}
 
-	return ResolveVehicleSpriteGroup(spritegroup, veh, callback);
+	return ResolveVehicleSpriteGroup(spritegroup, veh, callback_info, resolve_func);
 }
 
 static void DoTriggerVehicle(Vehicle *veh, VehicleTrigger trigger, byte base_random_bits, bool first)
 {
+	SpriteGroup *group;
 	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 */
+	group = TriggerVehicleSpriteGroup(GetVehicleSpriteGroup(veh->engine_type, veh), veh, 0,
+	                                  (resolve_callback) TriggerVehicleSpriteGroup);
+
+	if (group->type == SGT_REAL && group->g.real.sprites_per_set == 0 && veh->cargo_type != GC_DEFAULT) {
 		// 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);
+		group = TriggerVehicleSpriteGroup(&_engine_custom_sprites[veh->engine_type][GC_DEFAULT], veh, 0,
+		                                  (resolve_callback) TriggerVehicleSpriteGroup);
 	}
+
+	assert(group->type == SGT_REAL);
+	rsg = &group->g.real;
+
 	new_random_bits = Random();
 	veh->random_bits &= ~_vsg_bits_to_reseed;
 	veh->random_bits |= (first ? new_random_bits : base_random_bits) & _vsg_bits_to_reseed;
--- a/engine.h	Wed Jun 01 10:30:45 2005 +0000
+++ b/engine.h	Wed Jun 01 11:34:37 2005 +0000
@@ -122,6 +122,17 @@
 	NUM_GLOBAL_CID  =  31
 };
 
+// This enum only lists implemented callbacks
+enum CallbackID {
+	// Refit capacity, the passed vehicle needs to have its ->cargo_type set to
+	// the cargo we are refitting to, returns the new cargo capacity
+	CB_REFIT_CAP = 0x15,
+};
+
+enum {
+	CALLBACK_FAILED = 0xFFFF
+};
+
 VARDEF const uint32 _default_refitmasks[NUM_VEHICLE_TYPES];
 VARDEF const CargoID _global_cargo_id[NUM_LANDSCAPE][NUM_CARGO];
 VARDEF const uint32 _landscape_global_cargo_mask[NUM_LANDSCAPE];
@@ -133,6 +144,7 @@
 void SetCustomEngineSprites(byte engine, byte cargo, struct SpriteGroup *group);
 // loaded is in percents, overriding_engine 0xffff is none
 int GetCustomEngineSprite(byte engine, const Vehicle *v, byte direction);
+uint16 GetCallBackResult(uint16 callback_info, byte engine, const Vehicle *v);
 #define GetCustomVehicleSprite(v, direction) GetCustomEngineSprite(v->engine_type, v, direction)
 #define GetCustomVehicleIcon(et, direction) GetCustomEngineSprite(et, NULL, direction)
 
--- a/newgrf.c	Wed Jun 01 10:30:45 2005 +0000
+++ b/newgrf.c	Wed Jun 01 11:34:37 2005 +0000
@@ -1080,6 +1080,28 @@
 
 #undef FOR_EACH_OBJECT
 
+/**
+ * Creates a spritegroup representing a callback result
+ * @param value The value that was used to represent this callback result
+ * @return A spritegroup representing that callback result
+ */
+SpriteGroup NewCallBackResult(uint16 value)
+{
+	SpriteGroup group;
+
+	group.type = SGT_CALLBACK;
+
+	// Old style callback results have the highest byte 0xFF so signify it is a callback result
+	// New style ones only have the highest bit set (allows 15-bit results, instead of just 8)
+	if ((value >> 8) == 0xFF)
+		value &= 0xFF;
+	else
+		value &= ~0x8000;
+
+	group.g.callback.result = value;
+
+	return group;
+}
 
 /* Action 0x01 */
 static void NewSpriteSet(byte *buf, int len)
@@ -1183,37 +1205,43 @@
 			dg->divmod_val = grf_load_byte(&buf);
 		}
 
-		/* (groupid & 0x8000) means this is callback result; we happily
-		 * ignore that for now. */
+		/* (groupid & 0x8000) means this is callback result. */
 
 		dg->num_ranges = grf_load_byte(&buf);
 		dg->ranges = calloc(dg->num_ranges, sizeof(*dg->ranges));
 		for (i = 0; i < dg->num_ranges; i++) {
 			groupid = grf_load_word(&buf);
-			if (groupid & 0x8000 || groupid >= _cur_grffile->spritegroups_count) {
+			if (groupid & 0x8000) {
+				dg->ranges[i].group = NewCallBackResult(groupid);
+			} else if (groupid >= _cur_grffile->spritegroups_count) {
 				/* This doesn't exist for us. */
 				grf_load_word(&buf); // skip range
 				i--; dg->num_ranges--;
 				continue;
-			}
+			} else {
 			/* XXX: If multiple surreal sets attach a surreal
 			 * set this way, we are in trouble. */
-			dg->ranges[i].group = _cur_grffile->spritegroups[groupid];
+				dg->ranges[i].group = _cur_grffile->spritegroups[groupid];
+			}
+
 			dg->ranges[i].low = grf_load_byte(&buf);
 			dg->ranges[i].high = grf_load_byte(&buf);
 		}
 
 		groupid = grf_load_word(&buf);
-		if (groupid & 0x8000 || groupid >= _cur_grffile->spritegroups_count) {
+		if (groupid & 0x8000) {
+			dg->default_group = malloc(sizeof(*dg->default_group));
+			*dg->default_group = NewCallBackResult(groupid);
+		} else if (groupid >= _cur_grffile->spritegroups_count) {
 			/* This spritegroup stinks. */
 			free(dg->ranges), dg->ranges = NULL;
 			grfmsg(GMS_WARN, "NewSpriteGroup(%02x:0x%x): Default groupid %04x is cargo callback or unknown, ignoring spritegroup.", setid, numloaded, groupid);
 			return;
+		} else {
+			dg->default_group = malloc(sizeof(*dg->default_group));
+			memcpy(dg->default_group, &_cur_grffile->spritegroups[groupid], sizeof(*dg->default_group));
 		}
 
-		dg->default_group = malloc(sizeof(*dg->default_group));
-		memcpy(dg->default_group, &_cur_grffile->spritegroups[groupid], sizeof(*dg->default_group));
-
 		return;
 
 	} else if (numloaded == 0x80 || numloaded == 0x83) {
--- a/sprite.h	Wed Jun 01 10:30:45 2005 +0000
+++ b/sprite.h	Wed Jun 01 11:34:37 2005 +0000
@@ -107,10 +107,15 @@
 	SpriteGroup *groups;
 } RandomizedSpriteGroup;
 
+typedef struct CallbackResultSpriteGroup {
+	uint16 result;
+} CallbackResultSpriteGroup;
+
 typedef enum SpriteGroupType {
 	SGT_REAL,
 	SGT_DETERMINISTIC,
 	SGT_RANDOMIZED,
+	SGT_CALLBACK,
 } SpriteGroupType;
 
 struct SpriteGroup {
@@ -120,6 +125,7 @@
 		RealSpriteGroup real;
 		DeterministicSpriteGroup determ;
 		RandomizedSpriteGroup random;
+		CallbackResultSpriteGroup callback;
 	} g;
 };
 
--- a/train_cmd.c	Wed Jun 01 10:30:45 2005 +0000
+++ b/train_cmd.c	Wed Jun 01 11:34:37 2005 +0000
@@ -1318,10 +1318,6 @@
 	cost = 0;
 	num = 0;
 
-	// newgrf stuff can change graphics when refitting
-	if (!(flags & DC_EXEC))
-		InvalidateWindow(WC_VEHICLE_DEPOT, v->tile);
-
 	do {
 		/* XXX: We also refit all the attached wagons en-masse if they
 		 * can be refitted. This is how TTDPatch does it.  TODO: Have
@@ -1330,30 +1326,42 @@
 
 		if (v->cargo_cap != 0) {
 			RailVehicleInfo *rvi = RailVehInfo(v->engine_type);
-			uint16 amount = rvi->capacity;
-			CargoID old_cid = rvi->cargo_type;
-
-			/* the capacity depends on the cargo type, a rail vehicle
-			 * can carry twice as much mail/goods as normal cargo,
-			 * and four times as much passengers */
-			(old_cid == CT_PASSENGERS) ||
-			(amount <<= 1, old_cid == CT_MAIL || old_cid == CT_GOODS) ||
-			(amount <<= 1, true);
-			(new_cid == CT_PASSENGERS) ||
-			(amount >>= 1, new_cid == CT_MAIL || new_cid == CT_GOODS) ||
-			(amount >>= 1, true);
-
-			if (new_cid != v->cargo_type)
-				cost += (_price.build_railvehicle >> 8);
-			num += amount;
-			if (flags & DC_EXEC) {
-				//autorefitted train cars wants to keep the cargo
-				//it will be checked if the cargo is valid in CmdReplaceVehicle
-				if (!(SkipStoppedInDepotCheck))
-					v->cargo_count = 0;
-				v->cargo_type = new_cid;
-				v->cargo_cap = amount;
-				InvalidateWindow(WC_VEHICLE_DETAILS, v->index);
+			uint16 amount;
+			CargoID temp_cid = v->cargo_type;
+
+			/* Check the 'refit capacity' callback */
+			v->cargo_type = new_cid;
+			amount = GetCallBackResult(CB_REFIT_CAP, v->engine_type, v);
+			v->cargo_type = temp_cid;
+
+			if (amount == CALLBACK_FAILED) { // callback failed, use default
+				CargoID old_cid = rvi->cargo_type;
+				/* normally, the capacity depends on the cargo type, a rail vehicle
+				* can carry twice as much mail/goods as normal cargo,
+				* and four times as much passengers */
+				amount = rvi->capacity;
+				(old_cid == CT_PASSENGERS) ||
+				(amount <<= 1, old_cid == CT_MAIL || old_cid == CT_GOODS) ||
+				(amount <<= 1, true);
+				(new_cid == CT_PASSENGERS) ||
+				(amount >>= 1, new_cid == CT_MAIL || new_cid == CT_GOODS) ||
+				(amount >>= 1, true);
+			};
+
+			if (amount != 0) {
+				if (new_cid != v->cargo_type)
+					cost += (_price.build_railvehicle >> 8);
+				num += amount;
+				if (flags & DC_EXEC) {
+					//autorefitted train cars wants to keep the cargo
+					//it will be checked if the cargo is valid in CmdReplaceVehicle
+					if (!(SkipStoppedInDepotCheck))
+						v->cargo_count = 0;
+					v->cargo_type = new_cid;
+					v->cargo_cap = amount;
+					InvalidateWindow(WC_VEHICLE_DETAILS, v->index);
+					InvalidateWindow(WC_VEHICLE_DEPOT, v->tile);
+				}
 			}
 		}
 	// SkipStoppedInDepotCheck is called by CmdReplace and it should only apply to the single car it is called for