(svn r654) Hopefully complete support for randomized variational spritegroups (i.e. the cars transporter in DBSetXL gets different cars each time) (pasky)

This commit is contained in:
tron 2004-11-17 08:52:47 +00:00
parent 0086bb9d06
commit 13f0b6c0cf
13 changed files with 301 additions and 23 deletions

View File

@ -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);

View File

@ -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);

152
engine.c
View File

@ -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)

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -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()
};

View File

@ -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