1use crate::tile_meta::CollisionType;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct MetatileCell {
20 pub tile_id: u8,
22 pub flip_x: bool,
24 pub flip_y: bool,
26 pub palette_bank: u8,
28}
29
30impl Default for MetatileCell {
31 fn default() -> Self {
32 Self {
33 tile_id: 0,
34 flip_x: false,
35 flip_y: false,
36 palette_bank: 0,
37 }
38 }
39}
40
41impl MetatileCell {
42 pub fn from_tile_id(tile_id: u8) -> Self {
44 Self {
45 tile_id,
46 ..Default::default()
47 }
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct CollisionCell {
59 pub collision: CollisionType,
60}
61
62impl CollisionCell {
63 pub fn new(collision: CollisionType) -> Self {
65 Self { collision }
66 }
67
68 pub fn passable() -> Self {
70 Self {
71 collision: CollisionType::Passable,
72 }
73 }
74
75 pub fn impassable() -> Self {
77 Self {
78 collision: CollisionType::Impassable,
79 }
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub enum TriggerType {
90 OnStep,
92 OnEnter,
94 OnInteract,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct TriggerDef {
101 pub script_name: String,
103 pub trigger_type: TriggerType,
105}
106
107#[derive(Debug, Clone)]
117pub struct MetatileDef {
118 pub size: (u8, u8),
120
121 pub tiles: Vec<MetatileCell>,
124
125 pub collision: Vec<CollisionCell>,
129
130 pub trigger: Option<TriggerDef>,
133
134 pub animation_group: Option<u8>,
137
138 pub z_offset: i8,
142}
143
144impl MetatileDef {
145 pub fn new(size: (u8, u8)) -> Self {
155 assert!(size.0 > 0, "metatile width must be > 0");
156 assert!(size.1 > 0, "metatile height must be > 0");
157
158 let count = (size.0 as usize) * (size.1 as usize);
159 MetatileDef {
160 size,
161 tiles: vec![MetatileCell::default(); count],
162 collision: vec![CollisionCell::passable(); count],
163 trigger: None,
164 animation_group: None,
165 z_offset: 0,
166 }
167 }
168
169 pub fn tile_count(&self) -> usize {
171 (self.size.0 as usize) * (self.size.1 as usize)
172 }
173
174 pub fn from_gb_block(block_data: &[u8]) -> Self {
184 assert_eq!(
185 block_data.len(),
186 16,
187 "GB block data must be exactly 16 bytes, got {}",
188 block_data.len()
189 );
190
191 let tiles: Vec<MetatileCell> = block_data
192 .iter()
193 .map(|&id| MetatileCell::from_tile_id(id))
194 .collect();
195
196 MetatileDef {
197 size: (4, 4),
198 tiles,
199 collision: vec![CollisionCell::passable(); 16],
200 trigger: None,
201 animation_group: None,
202 z_offset: 0,
203 }
204 }
205}
206
207#[derive(Debug, Clone)]
217pub struct MetatileRegistry {
218 pub defs: Vec<MetatileDef>,
219}
220
221impl MetatileRegistry {
222 pub fn new() -> Self {
224 MetatileRegistry { defs: Vec::new() }
225 }
226
227 pub fn add_def(&mut self, def: MetatileDef) -> usize {
229 let index = self.defs.len();
230 self.defs.push(def);
231 index
232 }
233
234 pub fn get(&self, index: usize) -> Option<&MetatileDef> {
237 self.defs.get(index)
238 }
239
240 pub fn from_gb_blocksets(bst_data: &[u8]) -> Self {
250 assert!(
251 bst_data.len() % 16 == 0,
252 "GB blockset data length must be a multiple of 16, got {}",
253 bst_data.len()
254 );
255
256 let mut registry = MetatileRegistry::new();
257 for chunk in bst_data.chunks_exact(16) {
258 registry.add_def(MetatileDef::from_gb_block(chunk));
259 }
260 registry
261 }
262}
263
264impl Default for MetatileRegistry {
265 fn default() -> Self {
266 Self::new()
267 }
268}
269
270#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
283 fn test_new_creates_empty_metatile() {
284 let m = MetatileDef::new((2, 3));
285 assert_eq!(m.size, (2, 3));
286 assert_eq!(m.tile_count(), 6);
287 assert_eq!(m.tiles.len(), 6);
288 assert_eq!(m.collision.len(), 6);
289 assert!(m.trigger.is_none());
290 assert!(m.animation_group.is_none());
291 assert_eq!(m.z_offset, 0);
292
293 for tile in &m.tiles {
295 assert_eq!(tile.tile_id, 0);
296 assert!(!tile.flip_x);
297 assert!(!tile.flip_y);
298 assert_eq!(tile.palette_bank, 0);
299 }
300
301 for cell in &m.collision {
303 assert_eq!(cell.collision, CollisionType::Passable);
304 }
305 }
306
307 #[test]
308 fn test_new_2x2() {
309 let m = MetatileDef::new((2, 2));
310 assert_eq!(m.size, (2, 2));
311 assert_eq!(m.tile_count(), 4);
312 }
313
314 #[test]
315 fn test_new_4x4() {
316 let m = MetatileDef::new((4, 4));
317 assert_eq!(m.size, (4, 4));
318 assert_eq!(m.tile_count(), 16);
319 }
320
321 #[test]
322 #[should_panic(expected = "metatile width must be > 0")]
323 fn test_new_zero_width_panics() {
324 MetatileDef::new((0, 4));
325 }
326
327 #[test]
328 #[should_panic(expected = "metatile height must be > 0")]
329 fn test_new_zero_height_panics() {
330 MetatileDef::new((4, 0));
331 }
332
333 #[test]
338 fn test_from_gb_block_full_16_bytes() {
339 let data: [u8; 16] = [
340 0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33, ];
345
346 let m = MetatileDef::from_gb_block(&data);
347
348 assert_eq!(m.size, (4, 4));
349 assert_eq!(m.tile_count(), 16);
350 assert_eq!(m.z_offset, 0);
351 assert!(m.trigger.is_none());
352
353 assert_eq!(m.tiles[0].tile_id, 0x00);
355 assert_eq!(m.tiles[1].tile_id, 0x01);
356 assert_eq!(m.tiles[2].tile_id, 0x02);
357 assert_eq!(m.tiles[3].tile_id, 0x03);
358 assert_eq!(m.tiles[4].tile_id, 0x10);
359 assert_eq!(m.tiles[15].tile_id, 0x33);
360
361 for tile in &m.tiles {
363 assert!(!tile.flip_x);
364 assert!(!tile.flip_y);
365 assert_eq!(tile.palette_bank, 0);
366 }
367 }
368
369 #[test]
370 fn test_from_gb_block_all_zeros() {
371 let data = [0u8; 16];
372 let m = MetatileDef::from_gb_block(&data);
373
374 assert_eq!(m.size, (4, 4));
375 for tile in &m.tiles {
376 assert_eq!(tile.tile_id, 0);
377 }
378 }
379
380 #[test]
381 fn test_from_gb_block_with_high_tile_ids() {
382 let mut data = [0u8; 16];
383 for (i, b) in data.iter_mut().enumerate() {
385 *b = 0xA0 + i as u8;
386 }
387
388 let m = MetatileDef::from_gb_block(&data);
389 assert_eq!(m.tiles[0].tile_id, 0xA0);
390 assert_eq!(m.tiles[15].tile_id, 0xAF);
391 }
392
393 #[test]
394 #[should_panic(expected = "GB block data must be exactly 16 bytes")]
395 fn test_from_gb_block_too_short_panics() {
396 MetatileDef::from_gb_block(&[0u8; 8]);
397 }
398
399 #[test]
400 #[should_panic(expected = "GB block data must be exactly 16 bytes")]
401 fn test_from_gb_block_too_long_panics() {
402 MetatileDef::from_gb_block(&[0u8; 32]);
403 }
404
405 #[test]
406 #[should_panic(expected = "GB block data must be exactly 16 bytes")]
407 fn test_from_gb_block_empty_panics() {
408 MetatileDef::from_gb_block(&[]);
409 }
410
411 #[test]
416 fn test_tile_count_various_sizes() {
417 assert_eq!(MetatileDef::new((1, 1)).tile_count(), 1);
418 assert_eq!(MetatileDef::new((2, 2)).tile_count(), 4);
419 assert_eq!(MetatileDef::new((3, 3)).tile_count(), 9);
420 assert_eq!(MetatileDef::new((4, 4)).tile_count(), 16);
421 assert_eq!(MetatileDef::new((2, 5)).tile_count(), 10);
422 }
423
424 #[test]
429 fn test_metatile_with_trigger() {
430 let mut m = MetatileDef::new((2, 2));
431 m.trigger = Some(TriggerDef {
432 script_name: "heal_party".into(),
433 trigger_type: TriggerType::OnStep,
434 });
435
436 let t = m.trigger.as_ref().unwrap();
437 assert_eq!(t.script_name, "heal_party");
438 assert_eq!(t.trigger_type, TriggerType::OnStep);
439 }
440
441 #[test]
442 fn test_metatile_with_animation_group() {
443 let mut m = MetatileDef::new((2, 2));
444 m.animation_group = Some(7);
445 assert_eq!(m.animation_group, Some(7));
446 }
447
448 #[test]
449 fn test_metatile_z_offset() {
450 let mut m = MetatileDef::new((2, 2));
451 m.z_offset = -4;
452 assert_eq!(m.z_offset, -4);
453
454 m.z_offset = 8;
455 assert_eq!(m.z_offset, 8);
456 }
457
458 #[test]
463 fn test_registry_new_is_empty() {
464 let r = MetatileRegistry::new();
465 assert!(r.defs.is_empty());
466 assert!(r.get(0).is_none());
467 }
468
469 #[test]
470 fn test_registry_add_and_get() {
471 let mut r = MetatileRegistry::new();
472
473 let m1 = MetatileDef::new((2, 2));
474 let m2 = MetatileDef::new((3, 3));
475
476 let idx1 = r.add_def(m1);
477 let idx2 = r.add_def(m2);
478
479 assert_eq!(idx1, 0);
480 assert_eq!(idx2, 1);
481
482 assert!(r.get(0).is_some());
483 assert!(r.get(1).is_some());
484 assert!(r.get(2).is_none());
485
486 assert_eq!(r.get(0).unwrap().size, (2, 2));
487 assert_eq!(r.get(1).unwrap().size, (3, 3));
488 }
489
490 #[test]
491 fn test_registry_get_out_of_bounds() {
492 let r = MetatileRegistry::new();
493 assert!(r.get(0).is_none());
494 assert!(r.get(100).is_none());
495 }
496
497 #[test]
498 fn test_registry_default() {
499 let r = MetatileRegistry::default();
500 assert!(r.defs.is_empty());
501 }
502
503 #[test]
508 fn test_from_gb_blocksets_single_block() {
509 let data: Vec<u8> = (0..16).collect(); let registry = MetatileRegistry::from_gb_blocksets(&data);
511
512 assert_eq!(registry.defs.len(), 1);
513 let m = registry.get(0).unwrap();
514 assert_eq!(m.size, (4, 4));
515 assert_eq!(m.tiles[0].tile_id, 0x00);
516 assert_eq!(m.tiles[15].tile_id, 0x0F);
517 }
518
519 #[test]
520 fn test_from_gb_blocksets_multiple_blocks() {
521 let data: Vec<u8> = (0..32).collect();
523 let registry = MetatileRegistry::from_gb_blocksets(&data);
524
525 assert_eq!(registry.defs.len(), 2);
526
527 let m0 = registry.get(0).unwrap();
528 assert_eq!(m0.tiles[0].tile_id, 0x00);
529 assert_eq!(m0.tiles[15].tile_id, 0x0F);
530
531 let m1 = registry.get(1).unwrap();
532 assert_eq!(m1.tiles[0].tile_id, 0x10);
533 assert_eq!(m1.tiles[15].tile_id, 0x1F);
534 }
535
536 #[test]
537 fn test_from_gb_blocksets_many_blocks() {
538 let data = vec![0u8; 256 * 16];
540 let registry = MetatileRegistry::from_gb_blocksets(&data);
541 assert_eq!(registry.defs.len(), 256);
542
543 for def in ®istry.defs {
544 assert_eq!(def.size, (4, 4));
545 assert_eq!(def.tile_count(), 16);
546 }
547 }
548
549 #[test]
550 fn test_from_gb_blocksets_empty() {
551 let registry = MetatileRegistry::from_gb_blocksets(&[]);
552 assert!(registry.defs.is_empty());
553 }
554
555 #[test]
556 #[should_panic(expected = "GB blockset data length must be a multiple of 16")]
557 fn test_from_gb_blocksets_unaligned_panics() {
558 MetatileRegistry::from_gb_blocksets(&[0u8; 10]);
559 }
560
561 #[test]
562 #[should_panic(expected = "GB blockset data length must be a multiple of 16")]
563 fn test_from_gb_blocksets_odd_length_panics() {
564 MetatileRegistry::from_gb_blocksets(&[0u8; 17]);
565 }
566
567 #[test]
572 fn test_collision_cell_passable() {
573 let c = CollisionCell::passable();
574 assert_eq!(c.collision, CollisionType::Passable);
575 }
576
577 #[test]
578 fn test_collision_cell_impassable() {
579 let c = CollisionCell::impassable();
580 assert_eq!(c.collision, CollisionType::Impassable);
581 }
582
583 #[test]
584 fn test_collision_cell_custom() {
585 let c = CollisionCell::new(CollisionType::Water);
586 assert_eq!(c.collision, CollisionType::Water);
587 }
588
589 #[test]
594 fn test_tilemap_entry_default() {
595 let t = MetatileCell::default();
596 assert_eq!(t.tile_id, 0);
597 assert!(!t.flip_x);
598 assert!(!t.flip_y);
599 assert_eq!(t.palette_bank, 0);
600 }
601
602 #[test]
603 fn test_tilemap_entry_from_tile_id() {
604 let t = MetatileCell::from_tile_id(42);
605 assert_eq!(t.tile_id, 42);
606 assert!(!t.flip_x);
607 assert!(!t.flip_y);
608 }
609
610 #[test]
611 fn test_tilemap_entry_with_flips() {
612 let t = MetatileCell {
613 tile_id: 0x80,
614 flip_x: true,
615 flip_y: false,
616 palette_bank: 3,
617 };
618 assert!(t.flip_x);
619 assert!(!t.flip_y);
620 assert_eq!(t.palette_bank, 3);
621 }
622
623 #[test]
628 fn test_trigger_type_equality() {
629 assert_eq!(TriggerType::OnStep, TriggerType::OnStep);
630 assert_ne!(TriggerType::OnStep, TriggerType::OnInteract);
631 }
632
633 #[test]
634 fn test_trigger_def() {
635 let td = TriggerDef {
636 script_name: "warp_to_start_town".into(),
637 trigger_type: TriggerType::OnEnter,
638 };
639 assert_eq!(td.script_name, "warp_to_start_town");
640 assert_eq!(td.trigger_type, TriggerType::OnEnter);
641 }
642}