Skip to main content

dotzuki_renderer/
transition.rs

1// Fade palette system — the classic BGP/OBP register fades.
2// dc a,b,c,d = (a<<6)|(b<<4)|(c<<2)|d — packs 4 shades into one byte.
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct FadePalette {
6    pub bgp: u8,
7    pub obp0: u8,
8    pub obp1: u8,
9}
10
11impl FadePalette {
12    pub const fn new(bgp: u8, obp0: u8, obp1: u8) -> Self {
13        Self { bgp, obp0, obp1 }
14    }
15}
16
17// FadePal1 = all-black, FadePal4/5 = normal, FadePal8 = all-white
18pub const FADE_PAL_1: FadePalette = FadePalette::new(0xFF, 0xFF, 0xFF); // dc 3,3,3,3 / dc 3,3,3,3 / dc 3,3,3,3
19pub const FADE_PAL_2: FadePalette = FadePalette::new(0xFE, 0xFE, 0xF8); // dc 3,3,3,2 / dc 3,3,3,2 / dc 3,3,2,0
20pub const FADE_PAL_3: FadePalette = FadePalette::new(0xF9, 0xE4, 0xE4); // dc 3,3,2,1 / dc 3,2,1,0 / dc 3,2,1,0
21pub const FADE_PAL_4: FadePalette = FadePalette::new(0xE4, 0xD0, 0xE0); // dc 3,2,1,0 / dc 3,1,0,0 / dc 3,2,0,0 (normal)
22pub const FADE_PAL_5: FadePalette = FadePalette::new(0xE4, 0xD0, 0xE0); // same as FadePal4
23pub const FADE_PAL_6: FadePalette = FadePalette::new(0x90, 0x80, 0x90); // dc 2,1,0,0 / dc 2,0,0,0 / dc 2,1,0,0
24pub const FADE_PAL_7: FadePalette = FadePalette::new(0x40, 0x40, 0x40); // dc 1,0,0,0 / dc 1,0,0,0 / dc 1,0,0,0
25pub const FADE_PAL_8: FadePalette = FadePalette::new(0x00, 0x00, 0x00); // dc 0,0,0,0 / dc 0,0,0,0 / dc 0,0,0,0 (all-white)
26
27pub const FADE_PALETTES: [FadePalette; 8] = [
28    FADE_PAL_1, FADE_PAL_2, FADE_PAL_3, FADE_PAL_4, FADE_PAL_5, FADE_PAL_6, FADE_PAL_7, FADE_PAL_8,
29];
30
31pub const FADE_DELAY_FRAMES: u8 = 8;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum FadeDirection {
35    InFromBlack, // GBFadeInFromBlack: FadePal1→4 (4 steps)
36    OutToBlack,  // GBFadeOutToBlack: FadePal4→1 (4 steps)
37    OutToWhite,  // GBFadeOutToWhite: FadePal6→8 (3 steps)
38    InFromWhite, // GBFadeInFromWhite: FadePal7→5 (3 steps)
39}
40
41impl FadeDirection {
42    pub fn palette_indices(&self) -> &'static [usize] {
43        match self {
44            FadeDirection::InFromBlack => &[0, 1, 2, 3],
45            FadeDirection::OutToBlack => &[3, 2, 1, 0],
46            FadeDirection::OutToWhite => &[5, 6, 7],
47            FadeDirection::InFromWhite => &[6, 5, 4],
48        }
49    }
50
51    pub fn step_count(&self) -> usize {
52        self.palette_indices().len()
53    }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum InstantPalette {
58    Normal,   // GBPalNormal: BGP=0xE4, OBP0=0xD0
59    WhiteOut, // GBPalWhiteOut: all zeros
60}
61
62impl InstantPalette {
63    pub fn palette(&self) -> FadePalette {
64        match self {
65            InstantPalette::Normal => FadePalette::new(0xE4, 0xD0, 0xE4),
66            InstantPalette::WhiteOut => FadePalette::new(0x00, 0x00, 0x00),
67        }
68    }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum FadeState {
73    Idle,
74    Fading { step: usize, delay_remaining: u8 },
75    Done,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum FadeTickResult {
80    Fading(FadePalette),
81    Done(FadePalette),
82    Idle,
83}
84
85#[derive(Debug, Clone)]
86pub struct FadeTransition {
87    direction: Option<FadeDirection>,
88    state: FadeState,
89}
90
91impl FadeTransition {
92    pub fn new() -> Self {
93        Self {
94            direction: None,
95            state: FadeState::Idle,
96        }
97    }
98
99    pub fn start(&mut self, direction: FadeDirection) {
100        self.direction = Some(direction);
101        self.state = FadeState::Fading {
102            step: 0,
103            delay_remaining: FADE_DELAY_FRAMES,
104        };
105    }
106
107    pub fn is_active(&self) -> bool {
108        matches!(self.state, FadeState::Fading { .. })
109    }
110
111    pub fn is_done(&self) -> bool {
112        matches!(self.state, FadeState::Done)
113    }
114
115    pub fn state(&self) -> FadeState {
116        self.state
117    }
118
119    pub fn current_palette(&self) -> Option<FadePalette> {
120        let dir = self.direction.as_ref()?;
121        let indices = dir.palette_indices();
122        match self.state {
123            FadeState::Fading { step, .. } => {
124                if step < indices.len() {
125                    Some(FADE_PALETTES[indices[step]])
126                } else {
127                    Some(FADE_PALETTES[*indices.last().unwrap()])
128                }
129            }
130            FadeState::Done => Some(FADE_PALETTES[*indices.last().unwrap()]),
131            FadeState::Idle => None,
132        }
133    }
134
135    pub fn tick(&mut self) -> FadeTickResult {
136        let dir = match &self.direction {
137            Some(d) => *d,
138            None => return FadeTickResult::Idle,
139        };
140
141        match self.state {
142            FadeState::Idle => FadeTickResult::Idle,
143            FadeState::Done => {
144                self.state = FadeState::Idle;
145                self.direction = None;
146                FadeTickResult::Idle
147            }
148            FadeState::Fading {
149                step,
150                delay_remaining,
151            } => {
152                let indices = dir.palette_indices();
153                let palette = FADE_PALETTES[indices[step]];
154
155                if delay_remaining > 1 {
156                    self.state = FadeState::Fading {
157                        step,
158                        delay_remaining: delay_remaining - 1,
159                    };
160                    FadeTickResult::Fading(palette)
161                } else {
162                    let next_step = step + 1;
163                    if next_step >= indices.len() {
164                        self.state = FadeState::Done;
165                        FadeTickResult::Done(palette)
166                    } else {
167                        self.state = FadeState::Fading {
168                            step: next_step,
169                            delay_remaining: FADE_DELAY_FRAMES,
170                        };
171                        FadeTickResult::Fading(FADE_PALETTES[indices[next_step]])
172                    }
173                }
174            }
175        }
176    }
177
178    pub fn cancel(&mut self) {
179        self.state = FadeState::Idle;
180        self.direction = None;
181    }
182}
183
184impl Default for FadeTransition {
185    fn default() -> Self {
186        Self::new()
187    }
188}
189
190// LoadGBPal: palette based on wMapPalOffset (dark caves shift toward FadePal1).
191// Offset is in bytes (0,3,6,9); each 3-byte step = 1 palette index back from FadePal4.
192pub fn load_gb_pal(map_pal_offset: u8) -> FadePalette {
193    let steps_back = (map_pal_offset / 3) as usize;
194    let index = 3usize.saturating_sub(steps_back);
195    FADE_PALETTES[index]
196}
197
198// GetHealthBarColor: >=27px → 0 (green), >=10px → 1 (yellow), <10px → 2 (red)
199pub fn get_health_bar_color(hp_pixels: u8) -> u8 {
200    if hp_pixels >= 27 {
201        0
202    } else if hp_pixels >= 10 {
203        1
204    } else {
205        2
206    }
207}