Sequence events

Events by index

Events by name

Sequence events

#![allow(unused)]
fn main() {
struct EventHeader {
    event_type: u8, // could also be an enum
    start_offset: StartOffset,
    pad02: u16, // always 0
    size: u32,
    start_time: f32,
}

enum StartOffset: u8 {
    Animation = 1,
    Sequence = 2,
    Event = 3,
}
}

The event type (u8) indicates just that, the type of the event, and therefore how to interpret the data following the header. The start offset (u8) can either be animation (1), sequence (2), or event (3). The explicit padding at offset 2 is always zero (0). The size indicates the size of this event's entire data (including the header). The start time indicates the event's start time relative to the start offset/parent (probably).

The event structures and their sizes specified in this document are all without the header, for convenience. Subtract 12 bytes (the size of the header) from the size in the header to get the event sizes specified.

Index lookups

Sequence events can refer to information in their associated animation definition, for example:

  • Object3d nodes
  • Sound nodes (dynamic sounds)
  • Other nodes (just called nodes)
  • Sounds (static sounds)
  • Lights
  • Puffers

Based on the packing of some structures and the general size of the arrays in GameZ, I assume node indices are 2 bytes/16 bits, so u16 or i16. We do see negative numbers, so I assume it's i16, leaving a maximum index of 32767 - still a lot larger than the usual array sizes.

As mentioned, there are negative numbers that seem to have special meanings. For example, if the reader file says INPUT_NODE, this is translated to the index -200.

#![allow(unused)]
fn main() {
const INPUT_NODE_INDEX: i16 = -200;
}

It's unknown if this is allowed for all node indices, or only some.

Sound

Reader name: SOUND, Type: 1, Size: 16

Also called "static sound" in this project.

#![allow(unused)]
fn main() {
struct Sound {
    sound_index: i16,
    node_index: i16,
    translation: Vec3,
}
}

The sound index (i16) is used to look up the static sound in the animation definition. The node index (i16) is used to look up the parent/at node in the animation definition. The translation (Vec3) is presumably the sound's translation from the node.

Sound node

Reader name: SOUND_NODE, Type: 2, Size: 60

Also called "dynamic sound" in this project.

#![allow(unused)]
fn main() {
struct SoundNode {
    name: [u8; 32],
    unk32: u32, // always 1
    flags: SoundNodeFlags,
    active_state: u32, // always 0 or 1 (bool)
    node_index: i16,
    pad46: u16, // always 0
    translation: Vec3,
}

bitflags SoundNodeFlags: u32 {
    InheritTranslation = 1 << 1; // 0x2
}
}

The sound node's name (32 bytes) is zero-terminated and zero padded. It's unclear why dynamic sounds aren't looked up by index, maybe this event creates a new node? The next field (u32) is always one (1). The flags field (u32) seems to be a bit field and is either zero (0) or two (2). The active state (u32) is either zero (0, false) or one (1, true). The node index (i16) is used to look up the parent/at node in the animation definition. The next field (u16) is padding and will always be zero (0). The translation (Vec3) is presumably the sound's translation from the node. If inherit translation flag (1 << 1 or 0x2) is unset, then the node index and the translation will be zero (0/0.0).

Light state

Reader name: LIGHT_STATE, Type: 4, Size: 120

#![allow(unused)]
fn main() {
struct LightState {
    name: [u8; 32],
    light_index: i16,
    pad34: u16, // always 0
    flags: LightFlags,
    active_state: u32, // always 0 or 1 (bool)
    point_source: u32, // always 1
    directional: u32, // always 0 or 1 (bool)
    saturated: u32, // always 0 or 1 (bool)
    subdivide: u32, // always 0 or 1 (bool)
    static: u32, // always 0 or 1 (bool)
    node_index: i32,
    translation: Vec3,
    rotation: Vec3,
    range_near: f32,
    range_far: f32,
    color: Color,
    ambient: f32,
    diffuse: f32,
}

// Also used for light nodes in GameZ
bitflags LightFlags: u32 {
    // This flag never occurs in sequence events
    TranslationAbs = 1 << 0; // 0x001
    Translation = 1 << 1;    // 0x002
    // This flag never occurs in sequence events
    Rotation = 1 << 2;       // 0x004
    Range = 1 << 3;          // 0x008
    Color = 1 << 4;          // 0x010
    Ambient = 1 << 5;        // 0x020
    Diffuse = 1 << 6;        // 0x040
    Directional = 1 << 7;    // 0x080
    Saturated = 1 << 8;      // 0x100
    Subdivide = 1 << 9;      // 0x200
    Static = 1 << 10;        // 0x400

    Inactive = 0;
    Default = TranslationAbs
    | Translation
    | Range
    | Directional
    | Saturated
    | Subdivide;
}
}

The light node's name (32 bytes) is zero-terminated and zero padded. The light node's index (i16) is used to look up the light in the animation definition. It's unclear why the light state contains both the light node's name and index. When looked up by index, that name matches the name in this structure. The next field (u16) is padding and will always be zero (0).

The light flags (u32) are also used for light nodes in GameZ, and indicate which further fields/states are valid and should be set. The TranslationAbs flag (1 << 0, 0x001) is never set in sequence events/in anim.zbd that we have.

The active state (u32) is always zero (0, false) or one (1, true). The point source field (u32) indicates whether the light is directed (0, never occurs) or a point source (1, always). The directional field (u32) is always zero (0, false) or one (1, true). If the directional flag (1 << 7, 0x080) is unset, this is always false. The saturated field (u32) is always zero (0, false) or one (1, true). If the saturated flag (1 << 8, 0x100) is unset, this is always false. The subdivide field (u32) is always zero (0, false) or one (1, true). If the subdivide flag (1 << 9, 0x200) is unset, this is always false. The static field (u32) is always zero (0, false) or one (1, true). If the static flag (1 << 10, 0x400) is unset, this is always false.

The node index (i32) is used to look up the parent/at node in the animation definition.

It's unclear why dynamic sounds aren't looked up by index, maybe this event creates a new node? The next field (u32) is always one (1). Inherit translation (u32) seems to be a bit field and is either zero (0) or two (2). The active state (u32) is either zero (0, false) or one (1, true).

The node index (i16) is used to look up the parent/at node in the animation definition. This is sometimes set to the special input node value. The next field (u16) is padding and will always be zero (0). The translation (Vec3) is presumably the sound's translation from the node. If the translation flag (1 << 1, 0x002) is unset, then both the node index and translation will be zero (0/0.0).

The rotation or direction (Vec3) is always zero (0.0), because the rotation flag (1 << 2, 0x004) is never set in sequence events/in anim.zbd that we have.

The near range (f32) and far range (f32) likely indicate the light's range. The near range is greater than or equal to zero (0.0), and the far range is greater than or equal to the near range. If the range flag (1 << 3, 0x008) is unset, then both are zero (0.0).

The colour (Color) is the RGB value of the light, and all values between zero (0.0) and one (1.0), inclusive of both. If the colour flag (1 << 4, 0x010) is unset, all values are zero (0.0). Finally, the ambient (f32) and diffuse (f32) control two aspects of lighting used in computer graphics. Both values are between zero (0.0, inclusive) and one (1.0, inclusive). If the ambient flag (1 << 5, 0x020) or diffuse flag (1 << 6, 0x040) are unset, the respective value will be zero (0.0).

Light animation

Reader name: LIGHT_ANIMATION, Type: 5, Size: 100

Object active state

Reader name: OBJECT_ACTIVE_STATE, Type: 6, Size: 8

Object translate state

Reader name: OBJECT_TRANSLATE_STATE, Type: 7, Size: 20

Object scale state

Reader name: OBJECT_SCALE_STATE, Type: 8, Size: 16

Object rotate state

Reader name: OBJECT_ROTATE_STATE, Type: 9, Size: 20

Object motion

Reader name: OBJECT_MOTION, Type: 10, Size: 320

Object motion from to

Reader name: OBJECT_MOTION_FROM_TO, Type: 11, Size: 132

Object motion SI script

Reader name: OBJECT_MOTION_SI_SCRIPT, Type: 12, Size: Variable, at least 24

Object opacity state

Reader name: OBJECT_OPACITY_STATE, Type: 13, Size: 12

Object opacity from to

Reader name: OBJECT_OPACITY_FROM_TO, Type: 14, Size: 24

Object add child

Reader name: OBJECT_ADD_CHILD, Type: 15, Size: 4

Object cycle texture

Reader name: OBJECT_CYCLE_TEXTURE, Type: 17, Size: 8

Object connector

Reader name: OBJECT_CONNECTOR, Type: 18, Size: 76

Call object connector

Reader name: CALL_OBJECT_CONNECTOR, Type: 19, Size: 68

Call sequence

Reader name: CALL_SEQUENCE, Type: 22, Size: 36

Stop sequence

Reader name: STOP_SEQUENCE, Type: 23, Size: 36

Call animation

Reader name: CALL_ANIMATION, Type: 24, Size: 68

Stop animation

Reader name: STOP_ANIMATION, Type: 25, Size: 36

Reset animation

Reader name: RESET_ANIMATION, Type: 26, Size: 36

Invalidate animation

Reader name: INVALIDATE_ANIMATION, Type: 27, Size: 36

Fog state

Reader name: FOG_STATE, Type: 28, Size: 68

Loop

Reader name: LOOP, Type: 30, Size: 8

If

Reader name: IF, Type: 31, Size: 12

Else

Reader name: ELSE, Type: 32, Size: 0

Elseif

Reader name: ELSEIF, Type: 33, Size: 12

Endif

Reader name: ENDIF, Type: 34, Size: 0

Callback

Reader name: CALLBACK, Type: 35, Size: 4

FBFX color from to

Reader name: FBFX_COLOR_FROM_TO, Type: 36, Size: 52

Presumably, FBFX stands for "frame buffer effect".

Detonate weapon

Reader name: DETONATE_WEAPON, Type: 41, Size: 24

Puffer state

Reader name: PUFFER_STATE, Type: 42, Size: 580

#![allow(unused)]
fn main() {
struct PufferState {
    name: [u8; 32],
    puffer_index: i16,
    pad34: u16, // always 0
    flags: PufferStateFlags,
    active_state: i32,
    node_index: u32,
    translation: Vec3,
    local_velocity: Vec3,
    world_velocity: Vec3,
    min_random_velocity: Vec3,
    max_random_velocity: Vec3,
    world_acceleration: Vec3,
    interval_type: u32,
    interval_value: f32,
    size_range: Vec2,
    lifetime_range: Vec2,
    start_age_range: Vec2,
    deviation_distance: f32,
    unk156: f32, // always 0.0
    unk160: f32, // always 0.0
    fade_range: Vec2,
    friction: f32,
    unk176: u32, // always 0
    unk180: u32, // always 0
    unk184: u32, // always 0
    unk188: u32, // always 0
    tex192: [u8; 36],
    tex228: [u8; 36],
    tex264: [u8; 36],
    tex300: [u8; 36],
    tex336: [u8; 36],
    tex372: [u8; 36],
    unk408: [u8; 120], // always 0
    unk528: u32,
    unk532: u32, // always 0
    unk536: f32,
    unk540: f32,
    growth_factor: f32,
    unk548: [u8; 32], // always 0
}

bitflags PufferStateFlags: u32 {
    // this might not be right?
    Translate = 1 << 0;          // 0x00001
    GrowthFactor = 1 << 1;       // 0x00002
    // this might not be right?
    State = 1 << 2;              // 0x00004
    LocalVelocity = 1 << 3;      // 0x00008
    WorldVelocity = 1 << 4;      // 0x00010
    MinRandomVelocity = 1 << 5;  // 0x00020
    MaxRandomVelocity = 1 << 6;  // 0x00040
    IntervalType = 1 << 7;       // 0x00080
    // this might not be right?
    IntervalValue = 1 << 8;      // 0x00100
    SizeRange = 1 << 9;          // 0x00200
    LifetimeRange = 1 << 10;     // 0x00400
    DeviationDistance = 1 << 11; // 0x00800
    FadeRange = 1 << 12;         // 0x01000
    Active = 1 << 13;            // 0x02000
    CycleTexture = 1 << 14;      // 0x04000
    StartAgeRange = 1 << 15;     // 0x08000
    WorldAcceleration = 1 << 16; // 0x10000
    Friction = 1 << 17;          // 0x20000

    Inactive = 0;
}
}

The puffer's name (32 bytes) is zero-terminated and zero padded. The puffer's index (i16) is used to look up the puffer in the animation definition. It's unclear why the puffer state contains both the puffer's name and index. When looked up by index, that name matches the name in this structure. The next field (u16) is padding and will always be zero (0).

The puffer state's flags (u32) indicate which further fields/states are valid and should be set. If the state flag (1 << 3, 0x00008) is unset, then no other flags are set in the sequence events/in anim.zbd that we have. This seems to indicate whether the puffer is disabled/inactive. At least, that's the best guess. However, there's also an active flag and an active state, which seems to be slightly different.

The active or lifetime state (i32) seems to allow for a range of values. If the active flag is set, then the active state will be greater than or equal to one (1), and less than or equal to five (5). If the active flag (1 << 13, 0x02000) is unset, then the active state is always negative one (-1).

TODO