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}