# HG changeset patch # User hackykid # Date 2005-06-01 11:34:37 # Node ID 9315515934d372e4623a6696834e7fa699fdcc28 # Parent b657f9bda5ce2076f9e617bd1d584dcbb4b4d80d (svn r2389) - Feature: [newgrf] Implement the mechanism for handling newgrf callbacks. - Feature: [newgrf] Implement the 'refit capacity' callback. diff --git a/engine.c b/engine.c --- a/engine.c +++ b/engine.c @@ -317,16 +317,17 @@ void SetCustomEngineSprites(byte engine, _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 @@ static RealSpriteGroup* ResolveVehicleSp 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 @@ static RealSpriteGroup* ResolveVehicleSp } 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 @@ 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: { @@ -482,7 +485,7 @@ static RealSpriteGroup* ResolveVehicleSp /* 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 @@ static RealSpriteGroup* ResolveVehicleSp 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 @@ int GetCustomEngineSprite(byte engine, c } 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 @@ int GetCustomEngineSprite(byte engine, c 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 @@ 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( @@ -601,23 +640,29 @@ static RealSpriteGroup *TriggerVehicleSp ); } - 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; diff --git a/engine.h b/engine.h --- a/engine.h +++ b/engine.h @@ -122,6 +122,17 @@ enum GlobalCargo { 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 SetWagonOverrideSprites(byte engine 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) diff --git a/newgrf.c b/newgrf.c --- a/newgrf.c +++ b/newgrf.c @@ -1080,6 +1080,28 @@ 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) @@ -1183,37 +1205,43 @@ static void NewSpriteGroup(byte *buf, in 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) { diff --git a/sprite.h b/sprite.h --- a/sprite.h +++ b/sprite.h @@ -107,10 +107,15 @@ typedef struct RandomizedSpriteGroup { 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 @@ struct SpriteGroup { RealSpriteGroup real; DeterministicSpriteGroup determ; RandomizedSpriteGroup random; + CallbackResultSpriteGroup callback; } g; }; diff --git a/train_cmd.c b/train_cmd.c --- a/train_cmd.c +++ b/train_cmd.c @@ -1318,10 +1318,6 @@ int32 CmdRefitRailVehicle(int x, int y, 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 @@ int32 CmdRefitRailVehicle(int x, int y, 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