summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortron <tron@openttd.org>2004-11-17 08:52:47 +0000
committertron <tron@openttd.org>2004-11-17 08:52:47 +0000
commit13f0b6c0cf754184cfef8645e709f10240da5f98 (patch)
tree71bd164389e546b1a8a5c1c879b57c1574cb95d2
parent0086bb9d0678876e2e3e9b4002a0f44af2c45ee4 (diff)
downloadopenttd-13f0b6c0cf754184cfef8645e709f10240da5f98.tar.xz
(svn r654) Hopefully complete support for randomized variational spritegroups (i.e. the cars transporter in DBSetXL gets different cars each time) (pasky)
-rw-r--r--aircraft_cmd.c2
-rw-r--r--economy.c21
-rw-r--r--engine.c152
-rw-r--r--engine.h11
-rw-r--r--grfspecial.c44
-rw-r--r--roadveh_cmd.c1
-rw-r--r--ship_cmd.c2
-rw-r--r--sprite.c37
-rw-r--r--sprite.h31
-rw-r--r--station_cmd.c2
-rw-r--r--train_cmd.c2
-rw-r--r--vehicle.c11
-rw-r--r--vehicle.h8
13 files changed, 301 insertions, 23 deletions
diff --git a/aircraft_cmd.c b/aircraft_cmd.c
index fb321f62b..f4112379d 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 b8f5e50de..86b7082dc 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 fdc9eda01..23916de88 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 7a20d67bd..3cb0407ec 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 d3d4cfec9..4809af5d1 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 becb2605e..5de98db27 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 4370c6102..980f8e326 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 23a21f1b1..2bad7fcfd 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 b75b30dd4..8b17fa943 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 a55563259..e7828b563 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 4b698acd4..82f837d81 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 000953e20..dbabd034c 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 398105837..b6a929108 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