Changeset - r1883:9315515934d3
[Not reviewed]
master
0 5 0
hackykid - 19 years ago 2005-06-01 11:34:37
hackykid@openttd.org
(svn r2389) - Feature: [newgrf] Implement the mechanism for handling newgrf callbacks.
- Feature: [newgrf] Implement the 'refit capacity' callback.
5 files changed with 158 insertions and 59 deletions:
0 comments (0 inline, 0 general)
engine.c
Show inline comments
 
@@ -314,31 +314,34 @@ void SetCustomEngineSprites(byte engine,
 
	/* FIXME: If we are replacing an override, release original SpriteGroup
 
	 * to prevent leaks. But first we need to refcount the SpriteGroup.
 
	 * --pasky */
 
	_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;
 
			SpriteGroup *target;
 
			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 {
 
				/* Vehicle-specific property. */
 

	
 
				if (veh == NULL) {
 
@@ -348,13 +351,13 @@ static RealSpriteGroup* ResolveVehicleSp
 
					 * (NOT the default one). */
 
					if (dsg->num_ranges > 0) {
 
						target = &dsg->ranges[0].group;
 
					} 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) {
 
					/* First engine in the vehicle chain */
 
					if (veh->type == VEH_Train)
 
						veh = GetFirstVehicleInChain(veh);
 
@@ -469,32 +472,32 @@ static RealSpriteGroup* ResolveVehicleSp
 
					}
 
				}
 
			}
 

	
 
			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: {
 
			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);
 
				return resolve_func(&rsg->groups[0], NULL, callback_info, resolve_func);
 
			}
 

	
 
			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);
 
			return resolve_func(EvalRandomizedSpriteGroup(rsg, veh->random_bits), veh, callback_info, resolve_func);
 
		}
 

	
 
		default:
 
			error("I don't know how to handle such a spritegroup %d!", spritegroup->type);
 
			return NULL;
 
	}
 
@@ -540,20 +543,23 @@ int GetCustomEngineSprite(byte engine, c
 
		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);
 
	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.
 
		return 0;
 
	}
 

	
 
@@ -579,48 +585,87 @@ int GetCustomEngineSprite(byte engine, c
 
	}
 

	
 
	r = (in_motion ? rsg->loaded[spriteset] : rsg->loading[spriteset]) + direction;
 
	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.
 
static byte _vsg_random_triggers;
 
static byte _vsg_bits_to_reseed;
 

	
 
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(
 
			&spritegroup->g.random,
 
			_vsg_random_triggers,
 
			&veh->waiting_triggers
 
		);
 
	}
 

	
 
	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;
 

	
 
	switch (trigger) {
 
		case VEHICLE_TRIGGER_NEW_CARGO:
engine.h
Show inline comments
 
@@ -119,23 +119,35 @@ enum GlobalCargo {
 
	GC_DEFAULT      =  29,
 
	GC_PURCHASE     =  30,
 
	GC_INVALID      = 255,
 
	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];
 
VARDEF const CargoID _local_cargo_id_ctype[NUM_GLOBAL_CID];
 

	
 
VARDEF uint32 _engine_refit_masks[256];
 
VARDEF byte _engine_original_sprites[256];
 
void SetWagonOverrideSprites(byte engine, struct SpriteGroup *group, byte *train_id, int trains);
 
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)
 

	
 
typedef enum VehicleTrigger {
 
	VEHICLE_TRIGGER_NEW_CARGO = 1,
 
	// Externally triggered only for the first vehicle in chain
newgrf.c
Show inline comments
 
@@ -1077,12 +1077,34 @@ ignoring:
 
		}
 
	}
 
}
 

	
 
#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)
 
{
 
	/* <01> <feature> <num-sets> <num-ent>
 
	 *
 
@@ -1180,43 +1202,49 @@ static void NewSpriteGroup(byte *buf, in
 
		dg->shift_num &= 0x3F;
 
		if (dg->operation != DSG_OP_NONE) {
 
			dg->add_val = grf_load_byte(&buf);
 
			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) {
 
		RandomizedSpriteGroup *rg;
 
		int i;
 

	
sprite.h
Show inline comments
 
@@ -104,25 +104,31 @@ typedef struct RandomizedSpriteGroup {
 
	byte num_groups; // must be power of 2
 

	
 
	// Take the group with appropriate index:
 
	SpriteGroup *groups;
 
} RandomizedSpriteGroup;
 

	
 
typedef struct CallbackResultSpriteGroup {
 
	uint16 result;
 
} CallbackResultSpriteGroup;
 

	
 
typedef enum SpriteGroupType {
 
	SGT_REAL,
 
	SGT_DETERMINISTIC,
 
	SGT_RANDOMIZED,
 
	SGT_CALLBACK,
 
} SpriteGroupType;
 

	
 
struct SpriteGroup {
 
	SpriteGroupType type;
 

	
 
	union {
 
		RealSpriteGroup real;
 
		DeterministicSpriteGroup determ;
 
		RandomizedSpriteGroup random;
 
		CallbackResultSpriteGroup callback;
 
	} g;
 
};
 

	
 
struct DeterministicSpriteGroupRange {
 
	SpriteGroup group;
 
	byte low;
train_cmd.c
Show inline comments
 
@@ -1315,48 +1315,56 @@ int32 CmdRefitRailVehicle(int x, int y, 
 

	
 
	SET_EXPENSES_TYPE(EXPENSES_TRAIN_RUN);
 

	
 
	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
 
		 * some nice [Refit] button near each wagon. --pasky */
 
		if (!CanRefitTo(v, new_cid)) continue;
 

	
 
		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
 
	} while ( (v=v->next) != NULL || SkipStoppedInDepotCheck );
 

	
 
	_returned_refit_amount = num;
0 comments (0 inline, 0 general)