use std::collections::HashMap; use std::fmt; use std::marker::PhantomData; use std::path::PathBuf; use crate::bosses::BossType; use crate::InvalidEnumError; #[derive(Debug, Default)] pub struct ManualBosses { pub eastern_palace: String, pub desert_palace: String, pub tower_of_hera: String, pub agahnims_tower: String, pub palace_of_darkness: String, pub swamp_palace: String, pub skull_woods: String, pub thieves_town: String, pub ice_palace: String, pub misery_mire: String, pub turtle_rock: String, pub ganons_tower1: String, pub ganons_tower2: String, pub ganons_tower3: String, pub ganons_tower4: String, pub ganon: String, } #[derive(Debug)] pub enum RandomizeEnemiesType { Basic, Normal, Hard, Chaos, Insanity, } impl TryFrom for RandomizeEnemiesType { type Error = InvalidEnumError; fn try_from(byte: u8) -> Result { match byte { 0 => Ok(Self::Basic), 1 => Ok(Self::Normal), 2 => Ok(Self::Hard), 3 => Ok(Self::Chaos), 4 => Ok(Self::Insanity), _ => Err(InvalidEnumError(PhantomData)) } } } #[derive(Debug)] pub enum RandomizeEnemyHpType { Easy, Medium, Hard, Patty } impl TryFrom for RandomizeEnemyHpType { type Error = InvalidEnumError; fn try_from(byte: u8) -> Result { match byte { 0 => Ok(Self::Easy), 1 => Ok(Self::Medium), 2 => Ok(Self::Hard), 3 => Ok(Self::Patty), _ => Err(InvalidEnumError(PhantomData)) } } } #[derive(Debug)] pub enum RandomizeBossesType { Basic, Normal, Chaos } impl TryFrom for RandomizeBossesType { type Error = InvalidEnumError; fn try_from(byte: u8) -> Result { match byte { 0 => Ok(Self::Basic), 1 => Ok(Self::Normal), 2 => Ok(Self::Chaos), _ => Err(InvalidEnumError(PhantomData)) } } } #[derive(Debug)] pub enum SwordType { Normal } impl TryFrom for SwordType { type Error = InvalidEnumError; fn try_from(byte: u8) -> Result { match byte { 0 => Ok(Self::Normal), _ => Err(InvalidEnumError(PhantomData)) } } } #[derive(Debug)] pub enum ShieldType { Normal } impl TryFrom for ShieldType { type Error = InvalidEnumError; fn try_from(byte: u8) -> Result { match byte { 0 => Ok(Self::Normal), _ => Err(InvalidEnumError(PhantomData)) } } } #[derive(Debug, PartialEq, Eq, Hash)] pub enum AbsorbableType { Heart, GreenRupee, BlueRupee, RedRupee, Bomb1, Bomb4, Bomb8, SmallMagic, FullMagic, Arrow5, Arrow10, Fairy, Key, BigKey, } impl TryFrom for AbsorbableType { type Error = InvalidEnumError; fn try_from(byte: u8) -> Result { match byte { 0 => Ok(Self::Heart), 1 => Ok(Self::GreenRupee), 2 => Ok(Self::BlueRupee), 3 => Ok(Self::Bomb1), 4 => Ok(Self::Bomb4), 5 => Ok(Self::Bomb8), 6 => Ok(Self::SmallMagic), 7 => Ok(Self::FullMagic), 8 => Ok(Self::Arrow5), 9 => Ok(Self::Arrow10), 10 => Ok(Self::Fairy), 11 => Ok(Self::Key), 12 => Ok(Self::BigKey), _ => Err(InvalidEnumError(PhantomData)) } } } impl fmt::Display for AbsorbableType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use AbsorbableType::*; let description = match self { Heart => "Heart", GreenRupee => "Green Rupee", BlueRupee => "Blue Rupee", RedRupee => "Red Rupee", Bomb1 => "Bomb (1)", Bomb4 => "Bomb (4)", Bomb8 => "Bomb (8)", SmallMagic => "Small Magic", FullMagic => "Full Magic", Arrow5 => "Arrow (5)", Arrow10 => "Arrow (10)", Fairy => "Fairy", Key => "Key", BigKey => "Big Key", }; write!(f, "{}", description) } } #[derive(Debug)] pub enum HeartBeepSpeed { Normal, Half, Quarter, Off, } impl Default for HeartBeepSpeed { fn default() -> Self { HeartBeepSpeed::Normal } } impl TryFrom for HeartBeepSpeed { type Error = InvalidEnumError; fn try_from(byte: u8) -> Result { match byte { 0 => Ok(Self::Normal), 1 => Ok(Self::Half), 2 => Ok(Self::Quarter), 3 => Ok(Self::Off), _ => Err(InvalidEnumError(PhantomData)) } } } #[derive(Debug)] pub enum BeeLevel { Level1, Level2, Level3, Level4, } impl fmt::Display for BeeLevel { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use BeeLevel::*; let description = match self { Level1 => "Bees??", Level2 => "Bees!", Level3 => "Beeeeees!?", Level4 => "Beeeeeeeeeeeeeeeeeeeees", }; write!(f, "{}", description) } } impl TryFrom for BeeLevel { type Error = InvalidEnumError; fn try_from(byte: u8) -> Result { match byte { 0 => Ok(Self::Level1), 1 => Ok(Self::Level2), 2 => Ok(Self::Level3), 3 => Ok(Self::Level4), _ => Err(InvalidEnumError(PhantomData)) } } } #[derive(Debug)] pub struct OptionFlags { pub randomize_enemies: bool, pub randomize_enemies_type: RandomizeEnemiesType, pub randomize_bush_enemy_chance: bool, pub randomize_enemy_health_range: bool, pub randomize_enemy_health_type: RandomizeEnemyHpType, pub randomize_enemy_damage: bool, pub allow_enemy_zero_damage: bool, pub shuffle_enemy_damage_groups: bool, pub enemy_damage_chaos_mode: bool, pub easy_mode_escape: bool, pub enemies_absorbable: bool, pub absorbable_spawn_rate: u8, pub absorbable_types: HashMap, pub boss_madness: bool, pub randomize_bosses: bool, pub randomize_bosses_type: RandomizeBossesType, pub randomize_boss_health: bool, pub randomize_boss_health_min_amount: u8, pub randomize_boss_health_max_amount: u8, pub randomize_boss_damage: bool, pub randomize_boss_damage_min_amount: u8, pub randomize_boss_damage_max_amount: u8, pub randomize_boss_behavior: bool, pub randomize_dungeon_palettes: bool, pub set_blackout_mode: bool, pub randomize_overworld_palettes: bool, pub randomize_sprite_palettes: bool, pub set_advanced_sprite_palettes: bool, pub puke_mode: bool, pub negative_mode: bool, pub grayscale_mode: bool, pub generate_spoilers: bool, pub randomize_link_sprite_palette: bool, pub randomize_pots: bool, pub shuffle_music: bool, pub bootleg_magic: bool, pub debug_mode: bool, pub custom_bosses: bool, pub heart_beep_speed: HeartBeepSpeed, pub alternate_gfx: bool, pub shield_graphics: PathBuf, pub sword_graphics: PathBuf, pub bee_mizer: bool, pub bees_level: BeeLevel, pub debug_force_enemy: bool, pub debug_force_enemy_id: u8, pub debug_force_boss: bool, pub debug_force_boss_id: BossType, pub debug_open_shutter_doors: bool, pub debug_force_enemy_damage_zero: bool, pub debug_show_room_id_in_rupee_counter: bool, pub o_h_k_o: bool, pub randomize_tile_trap_pattern: bool, pub randomize_tile_trap_floor_tile: bool, pub allow_killable_thief: bool, pub randomize_sprite_on_hit: bool, pub hero_mode: bool, pub increase_brightness: bool, pub mute_music_enable_msu_1: bool, pub agahnim_bounce_balls: bool, pub use_manual_bosses: bool, pub manual_bosses: ManualBosses, } impl OptionFlags { pub fn try_from_bytes(bytes: &[u8]) -> anyhow::Result { let mut absorbable_types = HashMap::new(); absorbable_types.insert(AbsorbableType::Heart, bytes[10] != 0); absorbable_types.insert(AbsorbableType::GreenRupee, bytes[11] != 0); absorbable_types.insert(AbsorbableType::BlueRupee, bytes[12] != 0); absorbable_types.insert(AbsorbableType::Bomb1, bytes[13] != 0); absorbable_types.insert(AbsorbableType::Bomb4, bytes[14] != 0); absorbable_types.insert(AbsorbableType::Bomb8, bytes[15] != 0); absorbable_types.insert(AbsorbableType::SmallMagic, bytes[16] != 0); absorbable_types.insert(AbsorbableType::FullMagic, bytes[17] != 0); absorbable_types.insert(AbsorbableType::Arrow5, bytes[18] != 0); absorbable_types.insert(AbsorbableType::Arrow10, bytes[19] != 0); absorbable_types.insert(AbsorbableType::Fairy, bytes[20] != 0); absorbable_types.insert(AbsorbableType::Key, bytes[21] != 0); absorbable_types.insert(AbsorbableType::BigKey, bytes[22] != 0); Ok(OptionFlags { randomize_enemies: bytes[0] != 0, randomize_enemies_type: bytes[1].try_into()?, randomize_bush_enemy_chance: bytes[2] != 0, randomize_enemy_health_range: bytes[3] != 0, randomize_enemy_health_type: bytes[4].try_into()?, randomize_enemy_damage: bytes[5] != 0, allow_enemy_zero_damage: bytes[6] != 0, easy_mode_escape: bytes[7] != 0, enemies_absorbable: bytes[8] != 0, absorbable_spawn_rate: bytes[9], absorbable_types, boss_madness: bytes[23] != 0, randomize_bosses: bytes[24] != 0, randomize_bosses_type: bytes[25].try_into()?, randomize_boss_health: bytes[26] != 0, randomize_boss_health_min_amount: bytes[27], randomize_boss_health_max_amount: bytes[28], randomize_boss_damage: bytes[29] != 0, randomize_boss_damage_min_amount: bytes[30], randomize_boss_damage_max_amount: bytes[31], randomize_boss_behavior: bytes[32] != 0, randomize_dungeon_palettes: bytes[33] != 0, set_blackout_mode: bytes[34] != 0, randomize_overworld_palettes: bytes[35] != 0, randomize_sprite_palettes: bytes[36] != 0, set_advanced_sprite_palettes: bytes[37] != 0, puke_mode: bytes[38] != 0, negative_mode: bytes[39] != 0, grayscale_mode: bytes[40] != 0, generate_spoilers: bytes[41] != 0, randomize_link_sprite_palette: bytes[42] != 0, randomize_pots: bytes[43] != 0, shuffle_music: bytes[44] != 0, bootleg_magic: bytes[45] != 0, debug_mode: bytes[46] != 0, custom_bosses: bytes[47] != 0, heart_beep_speed: bytes[48].try_into()?, alternate_gfx: bytes[49] != 0, // Skip byte 50 (shield_graphics) shuffle_enemy_damage_groups: bytes[51] != 0, enemy_damage_chaos_mode: bytes[52] != 0, // Skip byte 53 (sword_graphics) bee_mizer: bytes[54] != 0, bees_level: bytes[55].try_into()?, debug_force_enemy: bytes[56] != 0, debug_force_enemy_id: bytes[57], debug_force_boss: bytes[58] != 0, debug_force_boss_id: bytes[59].try_into()?, debug_open_shutter_doors: bytes[60] != 0, debug_force_enemy_damage_zero: bytes[61] != 0, debug_show_room_id_in_rupee_counter: bytes[62] != 0, o_h_k_o: bytes[63] != 0, randomize_tile_trap_pattern: bytes[64] != 0, randomize_tile_trap_floor_tile: bytes[65] != 0, allow_killable_thief: bytes[66] != 0, randomize_sprite_on_hit: bytes[67] != 0, hero_mode: bytes[68] != 0, increase_brightness: bytes[69] != 0, mute_music_enable_msu_1: bytes[70] != 0, agahnim_bounce_balls: bytes[71] != 0, ..Default::default() }) } pub fn into_bytes(self) -> Vec { let mut bytes = Vec::with_capacity(crate::rom::ENEMIZER_INFO_FLAGS_LENGTH); bytes.push(self.randomize_enemies as u8); bytes.push(self.randomize_enemies_type as u8); bytes.push(self.randomize_bush_enemy_chance as u8); bytes.push(self.randomize_enemy_health_range as u8); bytes.push(self.randomize_enemy_health_type as u8); bytes.push(self.randomize_enemy_damage as u8); bytes.push(self.allow_enemy_zero_damage as u8); bytes.push(self.easy_mode_escape as u8); bytes.push(self.enemies_absorbable as u8); bytes.push(self.absorbable_spawn_rate); bytes.push(self.absorbable_types.get(&AbsorbableType::Heart).copied().unwrap_or(false) as u8); bytes.push(self.absorbable_types.get(&AbsorbableType::GreenRupee).copied().unwrap_or(false) as u8); bytes.push(self.absorbable_types.get(&AbsorbableType::BlueRupee).copied().unwrap_or(false) as u8); bytes.push(self.absorbable_types.get(&AbsorbableType::RedRupee).copied().unwrap_or(false) as u8); bytes.push(self.absorbable_types.get(&AbsorbableType::Bomb1).copied().unwrap_or(false) as u8); bytes.push(self.absorbable_types.get(&AbsorbableType::Bomb4).copied().unwrap_or(false) as u8); bytes.push(self.absorbable_types.get(&AbsorbableType::Bomb8).copied().unwrap_or(false) as u8); bytes.push(self.absorbable_types.get(&AbsorbableType::SmallMagic).copied().unwrap_or(false) as u8); bytes.push(self.absorbable_types.get(&AbsorbableType::FullMagic).copied().unwrap_or(false) as u8); bytes.push(self.absorbable_types.get(&AbsorbableType::Arrow5).copied().unwrap_or(false) as u8); bytes.push(self.absorbable_types.get(&AbsorbableType::Arrow10).copied().unwrap_or(false) as u8); bytes.push(self.absorbable_types.get(&AbsorbableType::Fairy).copied().unwrap_or(false) as u8); bytes.push(self.absorbable_types.get(&AbsorbableType::Key).copied().unwrap_or(false) as u8); bytes.push(self.absorbable_types.get(&AbsorbableType::BigKey).copied().unwrap_or(false) as u8); bytes.push(self.boss_madness as u8); bytes.push(self.randomize_bosses as u8); bytes.push(self.randomize_bosses_type as u8); bytes.push(self.randomize_boss_health as u8); bytes.push(self.randomize_boss_health_min_amount); bytes.push(self.randomize_boss_health_max_amount); bytes.push(self.randomize_boss_damage as u8); bytes.push(self.randomize_boss_damage_min_amount); bytes.push(self.randomize_boss_damage_max_amount); bytes.push(self.randomize_boss_behavior as u8); bytes.push(self.randomize_dungeon_palettes as u8); bytes.push(self.set_blackout_mode as u8); bytes.push(self.randomize_overworld_palettes as u8); bytes.push(self.randomize_sprite_palettes as u8); bytes.push(self.set_advanced_sprite_palettes as u8); bytes.push(self.puke_mode as u8); bytes.push(self.negative_mode as u8); bytes.push(self.grayscale_mode as u8); bytes.push(self.generate_spoilers as u8); bytes.push(self.randomize_link_sprite_palette as u8); bytes.push(self.randomize_pots as u8); bytes.push(self.shuffle_music as u8); bytes.push(self.bootleg_magic as u8); bytes.push(self.debug_mode as u8); bytes.push(self.custom_bosses as u8); bytes.push(self.heart_beep_speed as u8); bytes.push(self.alternate_gfx as u8); bytes.push(0); // self.shield_graphics bytes.push(self.shuffle_enemy_damage_groups as u8); bytes.push(self.enemy_damage_chaos_mode as u8); bytes.push(0); // self.sword_graphics bytes.push(self.bee_mizer as u8); bytes.push(self.bees_level as u8); bytes.push(self.debug_force_enemy as u8); bytes.push(self.debug_force_enemy_id as u8); bytes.push(self.debug_force_boss as u8); bytes.push(self.debug_force_boss_id as u8); bytes.push(self.debug_open_shutter_doors as u8); bytes.push(self.debug_force_enemy_damage_zero as u8); bytes.push(self.debug_show_room_id_in_rupee_counter as u8); bytes.push(self.o_h_k_o as u8); bytes.push(self.randomize_tile_trap_pattern as u8); bytes.push(self.randomize_tile_trap_floor_tile as u8); bytes.push(self.allow_killable_thief as u8); bytes.push(self.randomize_sprite_on_hit as u8); bytes.push(self.hero_mode as u8); bytes.push(self.increase_brightness as u8); bytes.push(self.mute_music_enable_msu_1 as u8); bytes.push(self.agahnim_bounce_balls as u8); bytes } } impl Default for OptionFlags { fn default() -> Self { Self { randomize_enemies: true, randomize_enemies_type: RandomizeEnemiesType::Chaos, randomize_bush_enemy_chance: true, randomize_enemy_health_range: false, randomize_enemy_health_type: RandomizeEnemyHpType::Medium, randomize_enemy_damage: false, allow_enemy_zero_damage: false, shuffle_enemy_damage_groups: false, enemy_damage_chaos_mode: false, easy_mode_escape: false, enemies_absorbable: false, absorbable_spawn_rate: 0, absorbable_types: HashMap::new(), boss_madness: false, randomize_bosses: true, randomize_bosses_type: RandomizeBossesType::Chaos, randomize_boss_health: false, randomize_boss_health_min_amount: 0, randomize_boss_health_max_amount: 0, randomize_boss_damage: false, randomize_boss_damage_min_amount: 0, randomize_boss_damage_max_amount: 0, randomize_boss_behavior: false, randomize_dungeon_palettes: true, set_blackout_mode: false, randomize_overworld_palettes: true, randomize_sprite_palettes: true, set_advanced_sprite_palettes: false, puke_mode: false, negative_mode: false, grayscale_mode: false, generate_spoilers: true, randomize_link_sprite_palette: false, randomize_pots: true, shuffle_music: false, bootleg_magic: false, debug_mode: false, custom_bosses: false, heart_beep_speed: HeartBeepSpeed::Half, alternate_gfx: false, shield_graphics: ["shield_gfx", "normal.gfx"].iter().collect(), sword_graphics: ["sword_gfx", "normal.gfx"].iter().collect(), bee_mizer: false, bees_level: BeeLevel::Level1, debug_force_enemy: false, debug_force_enemy_id: 0, debug_force_boss: false, debug_force_boss_id: BossType::Trinexx, debug_open_shutter_doors: false, debug_force_enemy_damage_zero: false, debug_show_room_id_in_rupee_counter: false, o_h_k_o: false, randomize_tile_trap_pattern: false, randomize_tile_trap_floor_tile: false, allow_killable_thief: false, randomize_sprite_on_hit: false, hero_mode: false, increase_brightness: false, mute_music_enable_msu_1: false, agahnim_bounce_balls: false, use_manual_bosses: false, manual_bosses: Default::default(), } } }