1pub mod collision;
2pub mod oam;
3pub mod update;
4
5#[cfg(test)]
6mod tests;
7
8pub const NUM_SPRITESTATEDATA_STRUCTS: usize = 16;
13pub const OAM_COUNT: usize = 40;
14pub const OAM_Y_OFS: u8 = 16;
15pub const OAM_X_OFS: u8 = 8;
16pub const OAM_BG_PRIORITY: u8 = 0x80;
17pub const OAM_XFLIP: u8 = 1 << 5;
18pub const FACING_END: u8 = 1 << 0;
19pub const UNDER_GRASS: u8 = 1 << 1;
20pub const GRASS_PRIORITY: u8 = 0x80;
21pub const IMAGE_INDEX_OFFSCREEN: u8 = 0xFF;
22pub const MOVEMENT_WALK: u8 = 0xFE;
23pub const MOVEMENT_STAY: u8 = 0xFF;
24
25pub const FACING_DOWN: u8 = 0x00;
27pub const FACING_UP: u8 = 0x04;
28pub const FACING_LEFT: u8 = 0x08;
29pub const FACING_RIGHT: u8 = 0x0C;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36#[repr(u8)]
37pub enum MovementStatus {
38 Uninitialized = 0,
39 Ready = 1,
40 Delayed = 2,
41 Moving = 3,
42}
43
44impl MovementStatus {
45 pub fn from_byte(b: u8) -> Self {
46 match b & 0x7F {
47 0 => Self::Uninitialized,
48 1 => Self::Ready,
49 2 => Self::Delayed,
50 3 => Self::Moving,
51 _ => Self::Uninitialized,
52 }
53 }
54
55 pub fn face_player_bit(b: u8) -> bool {
56 b & (1 << 7) != 0
57 }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub struct OamTemplate {
66 pub y_offset: u8,
67 pub x_offset: u8,
68 pub attributes: u8,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub struct SpriteTilePattern {
73 pub tiles: [u8; 4],
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub struct SpriteFacingEntry {
78 pub tile_pattern: &'static SpriteTilePattern,
79 pub oam_template: &'static [OamTemplate; 4],
80}
81
82pub const STANDING_DOWN: SpriteTilePattern = SpriteTilePattern {
84 tiles: [0x00, 0x01, 0x02, 0x03],
85};
86pub const WALKING_DOWN: SpriteTilePattern = SpriteTilePattern {
87 tiles: [0x80, 0x81, 0x82, 0x83],
88};
89pub const STANDING_UP: SpriteTilePattern = SpriteTilePattern {
90 tiles: [0x04, 0x05, 0x06, 0x07],
91};
92pub const WALKING_UP: SpriteTilePattern = SpriteTilePattern {
93 tiles: [0x84, 0x85, 0x86, 0x87],
94};
95pub const STANDING_LEFT: SpriteTilePattern = SpriteTilePattern {
96 tiles: [0x08, 0x09, 0x0A, 0x0B],
97};
98pub const WALKING_LEFT: SpriteTilePattern = SpriteTilePattern {
99 tiles: [0x88, 0x89, 0x8A, 0x8B],
100};
101
102pub const NORMAL_OAM: [OamTemplate; 4] = [
104 OamTemplate {
105 y_offset: 0,
106 x_offset: 0,
107 attributes: 0x00,
108 },
109 OamTemplate {
110 y_offset: 0,
111 x_offset: 8,
112 attributes: 0x00,
113 },
114 OamTemplate {
115 y_offset: 8,
116 x_offset: 0,
117 attributes: UNDER_GRASS,
118 },
119 OamTemplate {
120 y_offset: 8,
121 x_offset: 8,
122 attributes: UNDER_GRASS | FACING_END,
123 },
124];
125
126pub const FLIPPED_OAM: [OamTemplate; 4] = [
127 OamTemplate {
128 y_offset: 0,
129 x_offset: 8,
130 attributes: OAM_XFLIP,
131 },
132 OamTemplate {
133 y_offset: 0,
134 x_offset: 0,
135 attributes: OAM_XFLIP,
136 },
137 OamTemplate {
138 y_offset: 8,
139 x_offset: 8,
140 attributes: OAM_XFLIP | UNDER_GRASS,
141 },
142 OamTemplate {
143 y_offset: 8,
144 x_offset: 0,
145 attributes: OAM_XFLIP | UNDER_GRASS | FACING_END,
146 },
147];
148
149pub const SPRITE_FACING_TABLE: [SpriteFacingEntry; 32] = [
151 SpriteFacingEntry {
154 tile_pattern: &STANDING_DOWN,
155 oam_template: &NORMAL_OAM,
156 },
157 SpriteFacingEntry {
158 tile_pattern: &WALKING_DOWN,
159 oam_template: &NORMAL_OAM,
160 },
161 SpriteFacingEntry {
162 tile_pattern: &STANDING_DOWN,
163 oam_template: &NORMAL_OAM,
164 },
165 SpriteFacingEntry {
166 tile_pattern: &WALKING_DOWN,
167 oam_template: &FLIPPED_OAM,
168 },
169 SpriteFacingEntry {
171 tile_pattern: &STANDING_UP,
172 oam_template: &NORMAL_OAM,
173 },
174 SpriteFacingEntry {
175 tile_pattern: &WALKING_UP,
176 oam_template: &NORMAL_OAM,
177 },
178 SpriteFacingEntry {
179 tile_pattern: &STANDING_UP,
180 oam_template: &NORMAL_OAM,
181 },
182 SpriteFacingEntry {
183 tile_pattern: &WALKING_UP,
184 oam_template: &FLIPPED_OAM,
185 },
186 SpriteFacingEntry {
188 tile_pattern: &STANDING_LEFT,
189 oam_template: &NORMAL_OAM,
190 },
191 SpriteFacingEntry {
192 tile_pattern: &WALKING_LEFT,
193 oam_template: &NORMAL_OAM,
194 },
195 SpriteFacingEntry {
196 tile_pattern: &STANDING_LEFT,
197 oam_template: &NORMAL_OAM,
198 },
199 SpriteFacingEntry {
200 tile_pattern: &WALKING_LEFT,
201 oam_template: &NORMAL_OAM,
202 },
203 SpriteFacingEntry {
205 tile_pattern: &STANDING_LEFT,
206 oam_template: &FLIPPED_OAM,
207 },
208 SpriteFacingEntry {
209 tile_pattern: &WALKING_LEFT,
210 oam_template: &FLIPPED_OAM,
211 },
212 SpriteFacingEntry {
213 tile_pattern: &STANDING_LEFT,
214 oam_template: &FLIPPED_OAM,
215 },
216 SpriteFacingEntry {
217 tile_pattern: &WALKING_LEFT,
218 oam_template: &FLIPPED_OAM,
219 },
220 SpriteFacingEntry {
222 tile_pattern: &STANDING_DOWN,
223 oam_template: &NORMAL_OAM,
224 },
225 SpriteFacingEntry {
226 tile_pattern: &STANDING_DOWN,
227 oam_template: &NORMAL_OAM,
228 },
229 SpriteFacingEntry {
230 tile_pattern: &STANDING_DOWN,
231 oam_template: &NORMAL_OAM,
232 },
233 SpriteFacingEntry {
234 tile_pattern: &STANDING_DOWN,
235 oam_template: &NORMAL_OAM,
236 },
237 SpriteFacingEntry {
238 tile_pattern: &STANDING_DOWN,
239 oam_template: &NORMAL_OAM,
240 },
241 SpriteFacingEntry {
242 tile_pattern: &STANDING_DOWN,
243 oam_template: &NORMAL_OAM,
244 },
245 SpriteFacingEntry {
246 tile_pattern: &STANDING_DOWN,
247 oam_template: &NORMAL_OAM,
248 },
249 SpriteFacingEntry {
250 tile_pattern: &STANDING_DOWN,
251 oam_template: &NORMAL_OAM,
252 },
253 SpriteFacingEntry {
254 tile_pattern: &STANDING_DOWN,
255 oam_template: &NORMAL_OAM,
256 },
257 SpriteFacingEntry {
258 tile_pattern: &STANDING_DOWN,
259 oam_template: &NORMAL_OAM,
260 },
261 SpriteFacingEntry {
262 tile_pattern: &STANDING_DOWN,
263 oam_template: &NORMAL_OAM,
264 },
265 SpriteFacingEntry {
266 tile_pattern: &STANDING_DOWN,
267 oam_template: &NORMAL_OAM,
268 },
269 SpriteFacingEntry {
270 tile_pattern: &STANDING_DOWN,
271 oam_template: &NORMAL_OAM,
272 },
273 SpriteFacingEntry {
274 tile_pattern: &STANDING_DOWN,
275 oam_template: &NORMAL_OAM,
276 },
277 SpriteFacingEntry {
278 tile_pattern: &STANDING_DOWN,
279 oam_template: &NORMAL_OAM,
280 },
281 SpriteFacingEntry {
282 tile_pattern: &STANDING_DOWN,
283 oam_template: &NORMAL_OAM,
284 },
285];
286
287pub const TILES_PER_SPRITE: usize = 12;
288pub const FOUR_TILE_SPRITE_A: u8 = 0x0A;
289pub const FOUR_TILE_SPRITE_B: u8 = 0x0B;
290pub const FOUR_TILE_SPRITE_B_OFFSET: u8 = 0x0A * 12 + 4;
291
292pub fn facing_table_index(image_index: u8) -> usize {
293 let is_unchanging = image_index >= 0xA0;
294 if is_unchanging {
295 let base = (image_index & 0x0F) as usize;
296 base + 16
297 } else {
298 (image_index & 0x0F) as usize
299 }
300}
301
302pub fn sprite_tile_base_offset(image_index: u8) -> u8 {
303 let sprite_num = (image_index >> 4) & 0x0F;
304 if sprite_num == FOUR_TILE_SPRITE_B {
305 FOUR_TILE_SPRITE_B_OFFSET
306 } else {
307 sprite_num.wrapping_mul(TILES_PER_SPRITE as u8)
308 }
309}
310
311#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
316pub struct OamEntry {
317 pub y: u8,
318 pub x: u8,
319 pub tile_id: u8,
320 pub attributes: u8,
321}
322
323#[derive(Debug, Clone)]
324pub struct ShadowOam {
325 pub entries: [OamEntry; OAM_COUNT],
326}
327
328impl Default for ShadowOam {
329 fn default() -> Self {
330 Self {
331 entries: [OamEntry::default(); OAM_COUNT],
332 }
333 }
334}
335
336impl ShadowOam {
337 pub fn clear(&mut self) {
338 for entry in &mut self.entries {
339 *entry = OamEntry::default();
340 }
341 }
342
343 pub fn hide_all(&mut self, screen_height: u8) {
344 for entry in &mut self.entries {
345 entry.y = OAM_Y_OFS + screen_height;
346 }
347 }
348}
349
350#[derive(Debug, Clone, Copy, Default)]
351pub struct SpriteStateData1 {
352 pub picture_id: u8,
353 pub movement_status: u8,
354 pub image_index: u8,
355 pub y_step_vector: i8,
356 pub y_pixels: u8,
357 pub x_step_vector: i8,
358 pub x_pixels: u8,
359 pub intra_anim_frame_counter: u8,
360 pub anim_frame_counter: u8,
361 pub facing_direction: u8,
362 pub y_adjusted: u8,
363 pub x_adjusted: u8,
364 pub collision_data: u8,
365 pub field_0d: u8,
366 pub collision_sprite_lo: u8,
367 pub collision_sprite_hi: u8,
368}
369
370impl SpriteStateData1 {
371 pub fn is_active(&self) -> bool {
372 self.picture_id != 0
373 }
374
375 pub fn is_visible(&self) -> bool {
376 self.image_index != IMAGE_INDEX_OFFSCREEN
377 }
378
379 pub fn movement_status(&self) -> MovementStatus {
380 MovementStatus::from_byte(self.movement_status)
381 }
382
383 pub fn faces_player(&self) -> bool {
384 MovementStatus::face_player_bit(self.movement_status)
385 }
386
387 pub fn clear_collision(&mut self) {
388 self.collision_data = 0;
389 self.collision_sprite_lo = 0;
390 self.collision_sprite_hi = 0;
391 }
392}
393
394#[derive(Debug, Clone, Copy, Default)]
395pub struct SpriteStateData2 {
396 pub walk_anim_counter: u8,
397 pub field_01: u8,
398 pub y_displacement: u8,
399 pub x_displacement: u8,
400 pub map_y: u8,
401 pub map_x: u8,
402 pub movement_byte1: u8,
403 pub grass_priority: u8,
404 pub movement_delay: u8,
405 pub orig_facing_direction: u8,
406 pub field_0a: u8,
407 pub field_0b: u8,
408 pub field_0c: u8,
409 pub picture_id: u8,
410 pub image_base_offset: u8,
411 pub field_0f: u8,
412}
413
414impl SpriteStateData2 {
415 pub fn init_displacement(&mut self) {
416 self.y_displacement = 8;
417 self.x_displacement = 8;
418 }
419
420 pub fn has_grass_priority(&self) -> bool {
421 self.grass_priority & GRASS_PRIORITY != 0
422 }
423}
424
425pub struct SpriteTable {
426 pub data1: [SpriteStateData1; NUM_SPRITESTATEDATA_STRUCTS],
427 pub data2: [SpriteStateData2; NUM_SPRITESTATEDATA_STRUCTS],
428 pub shadow_oam: ShadowOam,
429 pub oam_count: usize,
430 pub ledge_or_fishing: bool,
431}
432
433impl Default for SpriteTable {
434 fn default() -> Self {
435 Self {
436 data1: [SpriteStateData1::default(); NUM_SPRITESTATEDATA_STRUCTS],
437 data2: [SpriteStateData2::default(); NUM_SPRITESTATEDATA_STRUCTS],
438 shadow_oam: ShadowOam::default(),
439 oam_count: 0,
440 ledge_or_fishing: false,
441 }
442 }
443}
444
445impl SpriteTable {
446 pub fn clear_all(&mut self) {
447 self.data1 = [SpriteStateData1::default(); NUM_SPRITESTATEDATA_STRUCTS];
448 self.data2 = [SpriteStateData2::default(); NUM_SPRITESTATEDATA_STRUCTS];
449 self.shadow_oam.clear();
450 self.oam_count = 0;
451 }
452
453 pub fn player_data1(&self) -> &SpriteStateData1 {
454 &self.data1[0]
455 }
456
457 pub fn player_data1_mut(&mut self) -> &mut SpriteStateData1 {
458 &mut self.data1[0]
459 }
460
461 pub fn player_data2(&self) -> &SpriteStateData2 {
462 &self.data2[0]
463 }
464
465 pub fn player_data2_mut(&mut self) -> &mut SpriteStateData2 {
466 &mut self.data2[0]
467 }
468
469 pub fn active_sprite_count(&self) -> usize {
470 self.data1.iter().filter(|s| s.is_active()).count()
471 }
472}