775 lines
24 KiB
Rust
775 lines
24 KiB
Rust
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<RandomizeEnemiesType> for u8 {
|
|
fn from(t: RandomizeEnemiesType) -> u8 {
|
|
use RandomizeEnemiesType::*;
|
|
|
|
match t {
|
|
Basic => 0,
|
|
Normal => 1,
|
|
Hard => 2,
|
|
Chaos => 3,
|
|
Insanity => 4,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<u8> for RandomizeEnemiesType {
|
|
type Error = InvalidEnumError<Self>;
|
|
|
|
fn try_from(byte: u8) -> Result<Self, Self::Error> {
|
|
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<RandomizeEnemyHpType> for u8 {
|
|
fn from(t: RandomizeEnemyHpType) -> u8 {
|
|
use RandomizeEnemyHpType::*;
|
|
|
|
match t {
|
|
Easy => 0,
|
|
Medium => 1,
|
|
Hard => 2,
|
|
Patty => 3,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<u8> for RandomizeEnemyHpType {
|
|
type Error = InvalidEnumError<Self>;
|
|
|
|
fn try_from(byte: u8) -> Result<Self, Self::Error> {
|
|
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<RandomizeBossesType> for u8 {
|
|
fn from(t: RandomizeBossesType) -> u8 {
|
|
use RandomizeBossesType::*;
|
|
|
|
match t {
|
|
Basic => 0,
|
|
Normal => 1,
|
|
Chaos => 2,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<u8> for RandomizeBossesType {
|
|
type Error = InvalidEnumError<Self>;
|
|
|
|
fn try_from(byte: u8) -> Result<Self, Self::Error> {
|
|
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<SwordType> for u8 {
|
|
fn from(_t: SwordType) -> u8 {
|
|
0
|
|
}
|
|
}
|
|
|
|
impl TryFrom<u8> for SwordType {
|
|
type Error = InvalidEnumError<Self>;
|
|
|
|
fn try_from(byte: u8) -> Result<Self, Self::Error> {
|
|
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<ShieldType> for u8 {
|
|
fn from(_t: ShieldType) -> u8 {
|
|
0
|
|
}
|
|
}
|
|
|
|
impl TryFrom<u8> for ShieldType {
|
|
type Error = InvalidEnumError<Self>;
|
|
|
|
fn try_from(byte: u8) -> Result<Self, Self::Error> {
|
|
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<AbsorbableType> 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<u8> for AbsorbableType {
|
|
type Error = InvalidEnumError<Self>;
|
|
|
|
fn try_from(byte: u8) -> Result<Self, Self::Error> {
|
|
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<HeartBeepSpeed> for u8 {
|
|
fn from(speed: HeartBeepSpeed) -> u8 {
|
|
use HeartBeepSpeed::*;
|
|
|
|
match speed {
|
|
Normal => 0,
|
|
Half => 1,
|
|
Quarter => 2,
|
|
Off => 3,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<u8> for HeartBeepSpeed {
|
|
type Error = InvalidEnumError<Self>;
|
|
|
|
fn try_from(byte: u8) -> Result<Self, Self::Error> {
|
|
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<BeeLevel> for u8 {
|
|
fn from(level: BeeLevel) -> u8 {
|
|
use BeeLevel::*;
|
|
|
|
match level {
|
|
Level1 => 0,
|
|
Level2 => 1,
|
|
Level3 => 2,
|
|
Level4 => 3,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<u8> for BeeLevel {
|
|
type Error = InvalidEnumError<Self>;
|
|
|
|
fn try_from(byte: u8) -> Result<Self, Self::Error> {
|
|
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<AbsorbableType, bool>,
|
|
|
|
//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<Self> {
|
|
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<u8> {
|
|
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
|
|
);
|
|
}
|
|
}
|