use std::collections::HashMap; use std::fmt; use std::marker::PhantomData; use std::path::PathBuf; use serde::{Deserialize, Serialize}; use crate::bosses::BossType; use crate::InvalidEnumError; #[derive(Debug, Default, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] 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(Clone, Copy, Debug, Deserialize, Serialize)] #[serde(into = "u8", try_from = "u8")] pub enum RandomizeEnemiesType { Basic, Normal, Hard, Chaos, Insanity, } impl From for u8 { fn from(t: RandomizeEnemiesType) -> u8 { use RandomizeEnemiesType::*; match t { Basic => 0, Normal => 1, Hard => 2, Chaos => 3, Insanity => 4, } } } 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(Clone, Copy, Debug, Deserialize, Serialize)] #[serde(into = "u8", try_from = "u8")] pub enum RandomizeEnemyHpType { Easy, Medium, Hard, Patty, } impl From for u8 { fn from(t: RandomizeEnemyHpType) -> u8 { use RandomizeEnemyHpType::*; match t { Easy => 0, Medium => 1, Hard => 2, Patty => 3, } } } 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(Clone, Copy, Debug, Deserialize, Serialize)] #[serde(into = "u8", try_from = "u8")] pub enum RandomizeBossesType { Basic, Normal, Chaos, } impl From for u8 { fn from(t: RandomizeBossesType) -> u8 { use RandomizeBossesType::*; match t { Basic => 0, Normal => 1, Chaos => 2, } } } 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(Clone, Copy, Debug, Deserialize, Serialize)] #[serde(into = "u8", try_from = "u8")] pub enum SwordType { Normal, } impl From for u8 { fn from(_t: SwordType) -> u8 { 0 } } impl TryFrom for SwordType { type Error = InvalidEnumError; fn try_from(byte: u8) -> Result { match byte { 0 => Ok(Self::Normal), _ => Err(InvalidEnumError(PhantomData)), } } } #[derive(Clone, Copy, Debug, Deserialize, Serialize)] #[serde(into = "u8", try_from = "u8")] pub enum ShieldType { Normal, } impl From for u8 { fn from(_t: ShieldType) -> u8 { 0 } } impl TryFrom for ShieldType { type Error = InvalidEnumError; fn try_from(byte: u8) -> Result { match byte { 0 => Ok(Self::Normal), _ => Err(InvalidEnumError(PhantomData)), } } } #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] pub enum AbsorbableType { Heart, GreenRupee, BlueRupee, RedRupee, #[serde(alias = "Bomb_1")] Bomb1, #[serde(alias = "Bomb_4")] Bomb4, #[serde(alias = "Bomb_8")] Bomb8, SmallMagic, FullMagic, #[serde(alias = "Arrow_5")] Arrow5, #[serde(alias = "Arrow_10")] Arrow10, Fairy, Key, BigKey, } impl From for u8 { fn from(t: AbsorbableType) -> u8 { use AbsorbableType::*; match t { Heart => 0, GreenRupee => 1, BlueRupee => 2, RedRupee => 3, Bomb1 => 4, Bomb4 => 5, Bomb8 => 6, SmallMagic => 7, FullMagic => 8, Arrow5 => 9, Arrow10 => 10, Fairy => 11, Key => 12, BigKey => 13, } } } 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::RedRupee), 4 => Ok(Self::Bomb1), 5 => Ok(Self::Bomb4), 6 => Ok(Self::Bomb8), 7 => Ok(Self::SmallMagic), 8 => Ok(Self::FullMagic), 9 => Ok(Self::Arrow5), 10 => Ok(Self::Arrow10), 11 => Ok(Self::Fairy), 12 => Ok(Self::Key), 13 => 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(Clone, Copy, Debug, Deserialize, Serialize)] #[serde(into = "u8", try_from = "u8")] pub enum HeartBeepSpeed { Normal, Half, Quarter, Off, } impl Default for HeartBeepSpeed { fn default() -> Self { HeartBeepSpeed::Normal } } impl From for u8 { fn from(speed: HeartBeepSpeed) -> u8 { use HeartBeepSpeed::*; match speed { Normal => 0, Half => 1, Quarter => 2, Off => 3, } } } 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(Clone, Copy, Debug, Deserialize, Serialize)] #[serde(into = "u8", try_from = "u8")] 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 From for u8 { fn from(level: BeeLevel) -> u8 { use BeeLevel::*; match level { Level1 => 0, Level2 => 1, Level3 => 2, Level4 => 3, } } } 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, Deserialize, Serialize)] #[serde(default, rename_all = "PascalCase")] 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::RedRupee, bytes[13] != 0); absorbable_types.insert(AbsorbableType::Bomb1, bytes[14] != 0); absorbable_types.insert(AbsorbableType::Bomb4, bytes[15] != 0); absorbable_types.insert(AbsorbableType::Bomb8, bytes[16] != 0); absorbable_types.insert(AbsorbableType::SmallMagic, bytes[17] != 0); absorbable_types.insert(AbsorbableType::FullMagic, bytes[18] != 0); absorbable_types.insert(AbsorbableType::Arrow5, bytes[19] != 0); absorbable_types.insert(AbsorbableType::Arrow10, bytes[20] != 0); absorbable_types.insert(AbsorbableType::Fairy, bytes[21] != 0); absorbable_types.insert(AbsorbableType::Key, bytes[21] != 1); absorbable_types.insert(AbsorbableType::BigKey, bytes[23] != 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[24] != 0, randomize_bosses: bytes[25] != 0, randomize_bosses_type: bytes[26].try_into()?, //randomize_boss_health: bytes[27] != 0, //randomize_boss_health_min_amount: bytes[28], //randomize_boss_health_max_amount: bytes[29], //randomize_boss_damage: bytes[30] != 0, //randomize_boss_damage_min_amount: bytes[31], //randomize_boss_damage_max_amount: bytes[32], //randomize_boss_behavior: bytes[33] != 0, randomize_dungeon_palettes: bytes[34] != 0, set_blackout_mode: bytes[35] != 0, randomize_overworld_palettes: bytes[36] != 0, randomize_sprite_palettes: bytes[37] != 0, set_advanced_sprite_palettes: bytes[38] != 0, puke_mode: bytes[39] != 0, negative_mode: bytes[40] != 0, grayscale_mode: bytes[41] != 0, generate_spoilers: bytes[42] != 0, randomize_link_sprite_palette: bytes[43] != 0, randomize_pots: bytes[44] != 0, shuffle_music: bytes[45] != 0, bootleg_magic: bytes[46] != 0, debug_mode: bytes[47] != 0, //custom_bosses: bytes[48] != 0, heart_beep_speed: bytes[49].try_into()?, alternate_gfx: bytes[50] != 0, // Skip byte 51 (shield_graphics) shuffle_enemy_damage_groups: bytes[52] != 0, enemy_damage_chaos_mode: bytes[53] != 0, // Skip byte 54 (sword_graphics) bee_mizer: bytes[55] != 0, bees_level: bytes[56].try_into()?, debug_force_enemy: bytes[57] != 0, debug_force_enemy_id: bytes[58], debug_force_boss: bytes[59] != 0, debug_force_boss_id: bytes[60].try_into()?, debug_open_shutter_doors: bytes[61] != 0, debug_force_enemy_damage_zero: bytes[62] != 0, debug_show_room_id_in_rupee_counter: bytes[63] != 0, o_h_k_o: bytes[64] != 0, randomize_tile_trap_pattern: bytes[65] != 0, randomize_tile_trap_floor_tile: bytes[66] != 0, allow_killable_thief: bytes[67] != 0, randomize_sprite_on_hit: bytes[68] != 0, hero_mode: bytes[69] != 0, increase_brightness: bytes[70] != 0, mute_music_enable_msu_1: bytes[71] != 0, agahnim_bounce_balls: bytes[72] != 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(0); //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(0); //bytes.push(self.boss_madness as u8); bytes.push(self.randomize_bosses as u8); bytes.push(self.randomize_bosses_type as u8); bytes.push(0); //bytes.push(self.randomize_boss_health as u8); bytes.push(0); //bytes.push(self.randomize_boss_health_min_amount); bytes.push(0); //bytes.push(self.randomize_boss_health_max_amount); bytes.push(0); //bytes.push(self.randomize_boss_damage as u8); bytes.push(0); //bytes.push(self.randomize_boss_damage_min_amount); bytes.push(0); //bytes.push(self.randomize_boss_damage_max_amount); bytes.push(0); //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(0); //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(), } } } #[cfg(test)] mod test { use super::*; #[test] fn test_option_flags_serde() { let empty = "{}"; let actual: OptionFlags = serde_json::from_str(empty).expect("Can't deserialize empty"); let expected = serde_json::to_string(&OptionFlags::default()).expect("Can't serialize default"); assert_eq!(serde_json::to_string(&actual).expect("Can't roundtrip"), expected); } }