Skip to main content

dotzuki_renderer/battle_anim/
types.rs

1// ─── Constants ───────────────────────────────────────────────────────
2
3/// Base tile ID added to frame block tile indices (original: $31).
4pub const ANIM_BASE_TILE_ID: u8 = 0x31;
5
6/// Number of subanimations (NUM_SUBANIMS = 86).
7pub const NUM_SUBANIMS: usize = 86;
8
9/// Number of frame blocks (NUM_FRAMEBLOCKS = 122, 0x00..0x79).
10pub const NUM_FRAMEBLOCKS: usize = 122;
11
12/// Number of base coordinates (NUM_BASECOORDS = 177, 0x00..0xB0).
13pub const NUM_BASECOORDS: usize = 177;
14
15/// First special-effect ID in the command stream.
16/// IDs < this are subanimation commands; IDs >= this are special effects.
17pub const FIRST_SE_ID: u8 = 0xD8;
18
19/// Animation terminator byte.
20pub const ANIM_END: u8 = 0xFF;
21
22/// Flip constants matching OAM flags.
23pub const OAM_XFLIP: u8 = 0x20;
24pub const OAM_YFLIP: u8 = 0x40;
25
26// ─── Subanimation transform types ────────────────────────────────────
27
28/// How a subanimation's frame block tiles are transformed when rendered.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30#[repr(u8)]
31pub enum SubAnimTransform {
32    /// No transformation — use base coords + offsets directly.
33    Normal = 0,
34    /// Flip both horizontally and vertically (rotate 180°).
35    HvFlip = 1,
36    /// Flip horizontally and translate 40px downward (enemy perspective).
37    HFlip = 2,
38    /// Flip base coordinates (mirror around screen center).
39    CoordFlip = 3,
40    /// Play frames in reverse order.
41    Reverse = 4,
42    /// Enemy-side variant: choose transform based on wPlayerMonIsAttacker.
43    Enemy = 5,
44}
45
46impl SubAnimTransform {
47    pub fn from_u8(v: u8) -> Self {
48        match v {
49            0 => Self::Normal,
50            1 => Self::HvFlip,
51            2 => Self::HFlip,
52            3 => Self::CoordFlip,
53            4 => Self::Reverse,
54            5 => Self::Enemy,
55            _ => Self::Normal,
56        }
57    }
58}
59
60// ─── Frame block modes ───────────────────────────────────────────────
61
62/// Controls delay and OAM buffer behavior after rendering a frame block.
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64#[repr(u8)]
65pub enum FrameBlockMode {
66    /// Normal: delay, then clean OAM and reset dest address.
67    Mode00 = 0,
68    /// Unused in practice — same as Mode00.
69    Mode01 = 1,
70    /// No delay, keep OAM, advance dest address.
71    Mode02 = 2,
72    /// Delay, keep OAM, advance dest address.
73    Mode03 = 3,
74    /// Delay, keep OAM, do NOT advance dest address.
75    Mode04 = 4,
76}
77
78impl FrameBlockMode {
79    pub fn from_u8(v: u8) -> Self {
80        match v {
81            0 => Self::Mode00,
82            1 => Self::Mode01,
83            2 => Self::Mode02,
84            3 => Self::Mode03,
85            4 => Self::Mode04,
86            _ => Self::Mode00,
87        }
88    }
89
90    /// Whether this mode delays (waits) after drawing.
91    pub fn has_delay(&self) -> bool {
92        matches!(
93            self,
94            Self::Mode00 | Self::Mode01 | Self::Mode03 | Self::Mode04
95        )
96    }
97
98    /// Whether this mode cleans OAM after drawing.
99    pub fn cleans_oam(&self) -> bool {
100        matches!(self, Self::Mode00 | Self::Mode01)
101    }
102
103    /// Whether this mode advances the OAM destination pointer.
104    pub fn advances_dest(&self) -> bool {
105        matches!(self, Self::Mode02 | Self::Mode03)
106    }
107}
108
109// ─── Animation type (screen-level effects) ───────────────────────────
110
111/// Screen-level animation types from `AnimationTypePointerTable`.
112/// These control per-subanimation-frame screen effects.
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114#[repr(u8)]
115pub enum AnimationType {
116    None = 0,
117    ShakeScreenVertically = 1,
118    ShakeScreenHorizontallyHeavy = 2,
119    ShakeScreenHorizontallySlow = 3,
120    BlinkEnemyMonSprite = 4,
121    ShakeScreenHorizontallyLight = 5,
122    ShakeScreenHorizontallySlow2 = 6,
123}
124
125impl AnimationType {
126    pub fn from_u8(v: u8) -> Self {
127        match v {
128            1 => Self::ShakeScreenVertically,
129            2 => Self::ShakeScreenHorizontallyHeavy,
130            3 => Self::ShakeScreenHorizontallySlow,
131            4 => Self::BlinkEnemyMonSprite,
132            5 => Self::ShakeScreenHorizontallyLight,
133            6 => Self::ShakeScreenHorizontallySlow2,
134            _ => Self::None,
135        }
136    }
137}
138
139// ─── Special effects ─────────────────────────────────────────────────
140
141/// Special effects that can appear in move animation command streams.
142/// Values 0xD8..0xFE.
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144#[repr(u8)]
145pub enum SpecialEffect {
146    WavyScreen = 0xD8,
147    SubstituteMon = 0xD9,
148    ShakeBackAndForth = 0xDA,
149    SlideEnemyMonOff = 0xDB,
150    ShowEnemyMonPic = 0xDC,
151    ShowMonPic = 0xDD,
152    BlinkEnemyMon = 0xDE,
153    HideEnemyMonPic = 0xDF,
154    FlashEnemyMonPic = 0xE0,
155    DelayAnimation10 = 0xE1,
156    SpiralBallsInward = 0xE2,
157    ShakeEnemyHud2 = 0xE3,
158    ShakeEnemyHud = 0xE4,
159    SlideMonHalfOff = 0xE5,
160    PetalsFalling = 0xE6,
161    LeavesFalling = 0xE7,
162    TransformMon = 0xE8,
163    SlideMonDownAndHide = 0xE9,
164    MinimizeMon = 0xEA,
165    BounceUpAndDown = 0xEB,
166    ShootManyBallsUpward = 0xEC,
167    ShootBallsUpward = 0xED,
168    SquishMonPic = 0xEE,
169    HideMonPic = 0xEF,
170    LightScreenPalette = 0xF0,
171    ResetMonPosition = 0xF1,
172    MoveMonHorizontally = 0xF2,
173    BlinkMon = 0xF3,
174    SlideMonOff = 0xF4,
175    FlashMonPic = 0xF5,
176    SlideMonDown = 0xF6,
177    SlideMonUp = 0xF7,
178    FlashScreenLong = 0xF8,
179    DarkenMonPalette = 0xF9,
180    WaterDropletsEverywhere = 0xFA,
181    ShakeScreen = 0xFB,
182    ResetScreenPalette = 0xFC,
183    DarkScreenPalette = 0xFD,
184    DarkScreenFlash = 0xFE,
185}
186
187impl SpecialEffect {
188    pub fn from_u8(v: u8) -> Option<Self> {
189        match v {
190            0xD8 => Some(Self::WavyScreen),
191            0xD9 => Some(Self::SubstituteMon),
192            0xDA => Some(Self::ShakeBackAndForth),
193            0xDB => Some(Self::SlideEnemyMonOff),
194            0xDC => Some(Self::ShowEnemyMonPic),
195            0xDD => Some(Self::ShowMonPic),
196            0xDE => Some(Self::BlinkEnemyMon),
197            0xDF => Some(Self::HideEnemyMonPic),
198            0xE0 => Some(Self::FlashEnemyMonPic),
199            0xE1 => Some(Self::DelayAnimation10),
200            0xE2 => Some(Self::SpiralBallsInward),
201            0xE3 => Some(Self::ShakeEnemyHud2),
202            0xE4 => Some(Self::ShakeEnemyHud),
203            0xE5 => Some(Self::SlideMonHalfOff),
204            0xE6 => Some(Self::PetalsFalling),
205            0xE7 => Some(Self::LeavesFalling),
206            0xE8 => Some(Self::TransformMon),
207            0xE9 => Some(Self::SlideMonDownAndHide),
208            0xEA => Some(Self::MinimizeMon),
209            0xEB => Some(Self::BounceUpAndDown),
210            0xEC => Some(Self::ShootManyBallsUpward),
211            0xED => Some(Self::ShootBallsUpward),
212            0xEE => Some(Self::SquishMonPic),
213            0xEF => Some(Self::HideMonPic),
214            0xF0 => Some(Self::LightScreenPalette),
215            0xF1 => Some(Self::ResetMonPosition),
216            0xF2 => Some(Self::MoveMonHorizontally),
217            0xF3 => Some(Self::BlinkMon),
218            0xF4 => Some(Self::SlideMonOff),
219            0xF5 => Some(Self::FlashMonPic),
220            0xF6 => Some(Self::SlideMonDown),
221            0xF7 => Some(Self::SlideMonUp),
222            0xF8 => Some(Self::FlashScreenLong),
223            0xF9 => Some(Self::DarkenMonPalette),
224            0xFA => Some(Self::WaterDropletsEverywhere),
225            0xFB => Some(Self::ShakeScreen),
226            0xFC => Some(Self::ResetScreenPalette),
227            0xFD => Some(Self::DarkScreenPalette),
228            0xFE => Some(Self::DarkScreenFlash),
229            _ => None,
230        }
231    }
232}
233
234// ─── Data structures ─────────────────────────────────────────────────
235
236/// A single OAM tile entry within a frame block.
237/// Encodes position offset, tile index, and flip/attribute flags.
238#[derive(Debug, Clone, Copy, PartialEq, Eq)]
239pub struct FrameBlockTile {
240    /// X offset in pixels (dbsprite byte: x_tile*8 + x_px).
241    pub x_px: u8,
242    /// Y offset in pixels (dbsprite byte: y_tile*8 + y_px).
243    pub y_px: u8,
244    /// Tile ID (before adding ANIM_BASE_TILE_ID).
245    pub tile_id: u8,
246    /// OAM attribute flags (OAM_XFLIP, OAM_YFLIP, etc.).
247    pub flags: u8,
248}
249
250/// A frame block — a group of OAM tiles composing one animation "sprite".
251/// Frame blocks are indexed 0x00..0x79 (NUM_FRAMEBLOCKS).
252#[derive(Debug, Clone)]
253pub struct FrameBlock {
254    pub tiles: Vec<FrameBlockTile>,
255}
256
257impl FrameBlock {
258    pub const fn empty() -> Self {
259        Self { tiles: Vec::new() }
260    }
261}
262
263/// A single frame within a subanimation.
264#[derive(Debug, Clone, Copy, PartialEq, Eq)]
265pub struct SubAnimFrame {
266    /// Frame block index (0..NUM_FRAMEBLOCKS).
267    pub frame_block_id: u8,
268    /// Base coordinate index (0..NUM_BASECOORDS).
269    pub base_coord_id: u8,
270    /// Frame block rendering mode.
271    pub mode: FrameBlockMode,
272}
273
274/// A subanimation definition — specifies transform type and a sequence of frames.
275#[derive(Debug, Clone)]
276pub struct SubAnimation {
277    pub transform: SubAnimTransform,
278    pub frames: Vec<SubAnimFrame>,
279}
280
281/// A single command in a move's animation sequence.
282#[derive(Debug, Clone, Copy, PartialEq, Eq)]
283pub enum AnimCommand {
284    /// Play a subanimation with the given parameters.
285    /// `sound_id` is the move sound index (0 = no sound, stored as sound-1 in ROM).
286    /// `subanim_id` is the subanimation table index.
287    /// `tileset` selects animation tileset 0 or 1.
288    /// `delay` is the per-frame delay in vblank frames.
289    SubAnim {
290        sound_id: u8,
291        subanim_id: u8,
292        tileset: u8,
293        delay: u8,
294    },
295    /// Execute a special effect (screen flash, mon slide, etc.).
296    /// `sound_id` is the move sound index (0 = no sound).
297    Effect { sound_id: u8, effect: SpecialEffect },
298}
299
300/// A complete move animation — a sequence of commands.
301#[derive(Debug, Clone)]
302pub struct MoveAnimation {
303    pub commands: Vec<AnimCommand>,
304}
305
306impl MoveAnimation {
307    pub const fn empty() -> Self {
308        Self {
309            commands: Vec::new(),
310        }
311    }
312}
313
314// ─── Per-animation-id frame hooks ────────────────────────────────────
315
316/// Per-frame-block hook kinds. In the original engine a special effect is
317/// applied after every frame block; the hook is selected by the animation id
318/// and inspects the frame counter (which counts down from the subanimation's
319/// frame count to 1).
320#[derive(Debug, Clone, Copy, PartialEq, Eq)]
321pub enum FrameHook {
322    /// Flash the screen after every frame block.
323    FlashScreen,
324    /// Flash when counter % 4 == 0 (hyper beam).
325    FlashScreenEveryFour,
326    /// Flash when counter % 8 == 0 (thunderbolt).
327    FlashScreenEveryEight,
328    /// Flash at counters 13, 9, 5 and 1.
329    BlizzardFlash,
330    /// Flash every 4 frame blocks; hide the attacking mon at counter 1.
331    Explode,
332    /// Shake the screen horizontally and vertically at counters 8..=11;
333    /// flash at counter 1.
334    RockSlide,
335    /// Duplicate the first 4 OAM entries (second music note). Handled
336    /// internally by the player; nothing emitted.
337    Growl,
338    /// Unreachable — Tail Whip's animation has no subanimations, so the
339    /// hook never fires.
340    TailWhipUnused,
341    /// Ball toss flow — depends on battle state the player does not have
342    /// (held item, in-battle flag).
343    BallToss,
344    /// Ball shake flow — restarts the subanimation a number of times, which
345    /// is capture logic, not playback.
346    BallShake,
347    /// Plays a poof sound at counter 5; sound playback is a frontend
348    /// concern, nothing visual is emitted.
349    BallPoof,
350    /// Trade sequence only, never used in battle.
351    TradeHideMonster,
352    /// Trade sequence only, never used in battle.
353    TradeShakeBall,
354    /// Trade sequence only, never used in battle.
355    TradeJumpBall,
356}
357
358// ─── Shared move/animation ids ───────────────────────────────────────
359
360// Moves do double duty as animation identifiers; animation ids are 1-based.
361
362/// AMNESIA move/animation id ($85).
363pub const AMNESIA: u8 = 0x85;
364/// REST move/animation id ($9C).
365pub const REST: u8 = 0x9C;
366/// CONF_ANIM (confused monster) animation id ($BF).
367pub const CONF_ANIM: u8 = 0xBF;
368/// SLP_ANIM (sleeping monster) animation id ($BD).
369pub const SLP_ANIM: u8 = 0xBD;
370
371// ─── Animation tick result ───────────────────────────────────────────
372
373/// Result of a single animation tick.
374#[derive(Debug, Clone, PartialEq, Eq)]
375pub enum AnimTickResult {
376    /// Animation is still playing — OAM entries ready to render.
377    /// `sound`: move id whose sound this command plays (None = NO_MOVE);
378    /// only set on the command's first frame. `hook`: per-frame effect
379    /// from `AnimationIdSpecialEffects`, if one fired for this frame.
380    Playing {
381        sound: Option<u8>,
382        hook: Option<AnimEffect>,
383    },
384    /// Waiting for a delay (number of frames remaining).
385    /// `sound`: move id whose sound this command plays (None = NO_MOVE);
386    /// only set on the command's first frame. `hook`: per-frame effect
387    /// from `AnimationIdSpecialEffects`, if any.
388    WaitDelay {
389        frames: u8,
390        sound: Option<u8>,
391        hook: Option<AnimEffect>,
392    },
393    /// A special effect needs to be applied by the caller.
394    /// `sound`: move id whose sound this command plays (None = NO_MOVE).
395    Effect {
396        sound: Option<u8>,
397        effect: SpecialEffect,
398    },
399    /// The entire move animation is complete.
400    Done,
401}
402
403/// High-level effect actions the caller should perform.
404/// These map special effects to concrete rendering operations.
405#[derive(Debug, Clone, PartialEq, Eq)]
406pub enum AnimEffect {
407    /// No action needed (effect handled internally or is a no-op).
408    None,
409    /// Flash/invert the screen palette for N frames.
410    FlashScreen { frames: u8 },
411    /// Shake the screen horizontally by N pixels for M frames.
412    ShakeScreenH { pixels: i8, frames: u8 },
413    /// Shake the screen vertically by N pixels for M frames.
414    ShakeScreenV { pixels: i8, frames: u8 },
415    /// Shake the screen horizontally and vertically (DoRockSlideSpecialEffects).
416    ShakeScreenHV { pixels: i8, frames: u8 },
417    /// Hide the player's mon sprite.
418    HidePlayerMon,
419    /// Show the player's mon sprite.
420    ShowPlayerMon,
421    /// Hide the enemy's mon sprite.
422    HideEnemyMon,
423    /// Show the enemy's mon sprite.
424    ShowEnemyMon,
425    /// Slide the enemy mon off screen.
426    SlideEnemyMonOff,
427    /// Slide the player mon partially off.
428    SlidePlayerMonHalfOff,
429    /// Slide the player mon fully off the screen (AnimationSlideMonOff:
430    /// slides 8 tiles, i.e. the whole 7-tile-wide pic, wSlideMonDelay = 3).
431    SlidePlayerMonOff,
432    /// Slide the player mon down and hide.
433    SlidePlayerMonDown,
434    /// Slide the mon down and hide with the two-step 7×5/7×3 row-shrink
435    /// (`AnimationSlideMonDownAndHide`, Acid Armor) — distinct from
436    /// `AnimationSlideMonDown`'s plain one-row-at-a-time slide.
437    SlidePlayerMonDownAndHide,
438    /// Slide the player mon up to original position.
439    SlidePlayerMonUp,
440    /// Reset the player mon to original position.
441    ResetPlayerMonPosition,
442    /// Move the player mon horizontally.
443    MovePlayerMonH,
444    /// Blink the enemy mon sprite.
445    BlinkEnemyMon { times: u8 },
446    /// Blink the player mon sprite.
447    BlinkPlayerMon { times: u8 },
448    /// Flash the enemy mon picture.
449    FlashEnemyMonPic,
450    /// Flash the player mon picture.
451    FlashPlayerMonPic,
452    /// Set screen to dark palette.
453    DarkScreenPalette,
454    /// Set screen to light palette.
455    LightScreenPalette,
456    /// Reset screen palette to normal.
457    ResetScreenPalette,
458    /// Darken the active mon's palette.
459    DarkenMonPalette,
460    /// Wavy screen distortion effect.
461    WavyScreen,
462    /// Substitute doll animation.
463    SubstituteMon,
464    /// Transform mon into another.
465    TransformMon,
466    /// Minimize mon (shrink).
467    MinimizeMon,
468    /// Bounce up and down.
469    BounceUpAndDown,
470    /// Squish the mon picture.
471    SquishMonPic,
472    /// Spiral balls inward.
473    SpiralBallsInward,
474    /// Shoot balls upward.
475    ShootBallsUpward { many: bool },
476    /// Petals falling.
477    PetalsFalling,
478    /// Leaves falling.
479    LeavesFalling,
480    /// Water droplets everywhere.
481    WaterDroplets,
482    /// Shake back and forth.
483    ShakeBackAndForth,
484    /// Shake enemy HUD.
485    ShakeEnemyHud { variant: u8 },
486    /// Delay for 10 frames.
487    Delay10,
488}