From 13f0b6c0cf754184cfef8645e709f10240da5f98 Mon Sep 17 00:00:00 2001 From: tron Date: Wed, 17 Nov 2004 08:52:47 +0000 Subject: [PATCH] (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 | 2 + economy.c | 21 +++++++ engine.c | 152 +++++++++++++++++++++++++++++++++++++++++++------ engine.h | 11 ++++ grfspecial.c | 44 +++++++++++++- roadveh_cmd.c | 1 + ship_cmd.c | 2 + sprite.c | 37 ++++++++++++ sprite.h | 31 +++++++++- station_cmd.c | 2 +- train_cmd.c | 2 + vehicle.c | 11 +++- vehicle.h | 8 ++- 13 files changed, 301 insertions(+), 23 deletions(-) diff --git a/aircraft_cmd.c b/aircraft_cmd.c index fb321f62b7..f4112379d0 100644 --- a/aircraft_cmd.c +++ b/aircraft_cmd.c @@ -1148,6 +1148,8 @@ static void AircraftEnterHangar(Vehicle *v) 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 --git a/economy.c b/economy.c index b8f5e50de6..86b7082dcb 100644 --- a/economy.c +++ b/economy.c @@ -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 LoadUnloadVehicle(Vehicle *v) int t; uint count, cap; byte old_player; + bool completely_empty = true; assert((v->next_order&0x1F) == OT_LOADING); @@ -1253,6 +1255,9 @@ int LoadUnloadVehicle(Vehicle *v) 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 @@ int LoadUnloadVehicle(Vehicle *v) // 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 @@ next_vehicle:; 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 --git a/engine.c b/engine.c index fdc9eda016..23916de881 100644 --- a/engine.c +++ b/engine.c @@ -243,9 +243,15 @@ void SetCustomEngineSprites(byte engine, byte cargo, struct SpriteGroup *group) _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 @@ ResolveVehicleSpriteGroup(struct SpriteGroup *spritegroup, struct Vehicle *veh) } 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 @@ ResolveVehicleSpriteGroup(struct SpriteGroup *spritegroup, struct Vehicle *veh) } 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 @@ int GetCustomEngineSprite(byte engine, Vehicle *v, byte direction) 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 @@ int GetCustomEngineSprite(byte engine, Vehicle *v, byte direction) } +// 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 --git a/engine.h b/engine.h index 7a20d67bd6..3cb0407ec8 100644 --- a/engine.h +++ b/engine.h @@ -101,6 +101,17 @@ int GetCustomEngineSprite(byte engine, Vehicle *v, byte direction); #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 --git a/grfspecial.c b/grfspecial.c index d3d4cfec99..4809af5d12 100644 --- a/grfspecial.c +++ b/grfspecial.c @@ -1175,8 +1175,48 @@ static void NewSpriteGroup(byte *buf, int len) 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 --git a/roadveh_cmd.c b/roadveh_cmd.c index becb2605e1..5de98db27d 100644 --- a/roadveh_cmd.c +++ b/roadveh_cmd.c @@ -1366,6 +1366,7 @@ void RoadVehEnterDepot(Vehicle *v) 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 --git a/ship_cmd.c b/ship_cmd.c index 4370c61027..980f8e3263 100644 --- a/ship_cmd.c +++ b/ship_cmd.c @@ -392,6 +392,8 @@ static void ShipEnterDepot(Vehicle *v) 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 --git a/sprite.c b/sprite.c index 23a21f1b14..2bad7fcfd1 100644 --- a/sprite.c +++ b/sprite.c @@ -60,3 +60,40 @@ int GetDeterministicSpriteValue(byte var) 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 --git a/sprite.h b/sprite.h index b75b30dd4f..8b17fa9438 100644 --- a/sprite.h +++ b/sprite.h @@ -82,16 +82,36 @@ struct DeterministicSpriteGroup { 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 @@ struct SpriteGroup *EvalDeterministicSpriteGroup(struct DeterministicSpriteGroup /* 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 --git a/station_cmd.c b/station_cmd.c index a555632594..e7828b563d 100644 --- a/station_cmd.c +++ b/station_cmd.c @@ -1082,7 +1082,7 @@ ResolveStationSpriteGroup(struct SpriteGroup *spritegroup, struct Station *stat) } default: - case SGT_RANDOM: + case SGT_RANDOMIZED: error("I don't know how to handle random spritegroups yet!"); return NULL; } diff --git a/train_cmd.c b/train_cmd.c index 4b698acd45..82f837d817 100644 --- a/train_cmd.c +++ b/train_cmd.c @@ -2544,6 +2544,8 @@ void TrainEnterDepot(Vehicle *v, uint tile) 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 --git a/vehicle.c b/vehicle.c index 000953e20d..dbabd034c6 100644 --- a/vehicle.c +++ b/vehicle.c @@ -164,6 +164,7 @@ static Vehicle *InitializeVehicle(Vehicle *v) v->next = NULL; v->next_hash = 0xffff; v->string_id = 0; + v->random_bits = RandomRange(256); return v; } @@ -1548,6 +1549,7 @@ const byte _common_veh_desc[] = { 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 @@ const byte _common_veh_desc[] = { 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 --git a/vehicle.h b/vehicle.h index 3981058378..b6a9291086 100644 --- a/vehicle.h +++ b/vehicle.h @@ -107,10 +107,10 @@ struct Vehicle { 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 @@ struct Vehicle { 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