# HG changeset patch # User tron # Date 1100681567 0 # Node ID 0e3fa3da3899ba0af7a10d2ed9de97da8298cc16 # Parent b4589ccaff3ae8f4c888af0d7e71b7f3750d187b (svn r654) Hopefully complete support for randomized variational spritegroups (i.e. the cars transporter in DBSetXL gets different cars each time) (pasky) diff -r b4589ccaff3a -r 0e3fa3da3899 aircraft_cmd.c --- 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); diff -r b4589ccaff3a -r 0e3fa3da3899 economy.c --- 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); diff -r b4589ccaff3a -r 0e3fa3da3899 engine.c --- 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) diff -r b4589ccaff3a -r 0e3fa3da3899 engine.h --- 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); diff -r b4589ccaff3a -r 0e3fa3da3899 grfspecial.c --- 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; } diff -r b4589ccaff3a -r 0e3fa3da3899 roadveh_cmd.c --- 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); diff -r b4589ccaff3a -r 0e3fa3da3899 ship_cmd.c --- 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); diff -r b4589ccaff3a -r 0e3fa3da3899 sprite.c --- 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; +} diff -r b4589ccaff3a -r 0e3fa3da3899 sprite.h --- 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 diff -r b4589ccaff3a -r 0e3fa3da3899 station_cmd.c --- 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; } diff -r b4589ccaff3a -r 0e3fa3da3899 train_cmd.c --- 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); diff -r b4589ccaff3a -r 0e3fa3da3899 vehicle.c --- 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() }; diff -r b4589ccaff3a -r 0e3fa3da3899 vehicle.h --- 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