use std::collections::HashMap; use serde::{Serialize, Deserialize}; use rand::{Rng}; use rand::distributions::{WeightedIndex, Distribution}; use crate::entity::item::armor::{ArmorType, Armor}; use crate::ship::room::{Difficulty, Episode}; use crate::ship::map::MapArea; use crate::entity::character::SectionID; use crate::ship::drops::{ItemDropType, load_data_file}; use crate::ship::item_stats::{armor_stats, ArmorStats}; #[derive(Debug, Serialize, Deserialize)] struct ArmorRankRates { rank0: u32, rank1: u32, rank2: u32, rank3: u32, rank4: u32, } #[derive(Debug, Serialize, Deserialize)] struct ArmorSlotRanks { slot0: u32, slot1: u32, slot2: u32, slot3: u32, slot4: u32, } #[derive(Debug, Serialize, Deserialize)] pub struct GenericArmorTable { rank_rates: ArmorRankRates, slot_rates: ArmorSlotRanks, armor_set: u32, #[serde(skip)] armor_stats: HashMap, } impl GenericArmorTable { pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> GenericArmorTable { let mut gat: GenericArmorTable = load_data_file(episode, difficulty, section_id, "armor_rate.toml"); gat.armor_stats = armor_stats(); gat } fn armor_type(&self, area_map: &MapArea, rng: &mut R) -> ArmorType { let rank_weights = WeightedIndex::new(&[self.rank_rates.rank0, self.rank_rates.rank1, self.rank_rates.rank2, self.rank_rates.rank3, self.rank_rates.rank4]).unwrap(); let rank = rank_weights.sample(rng) as i32; let armor_level = std::cmp::max(0i32, self.armor_set as i32 - 3i32 + rank + area_map.drop_area_value().unwrap_or(0) as i32); match armor_level { 0x00 => ArmorType::Frame, 0x01 => ArmorType::Armor, 0x02 => ArmorType::PsyArmor, 0x03 => ArmorType::GigaFrame, 0x04 => ArmorType::SoulFrame, 0x05 => ArmorType::CrossArmor, 0x06 => ArmorType::SolidFrame, 0x07 => ArmorType::BraveArmor, 0x08 => ArmorType::HyperFrame, 0x09 => ArmorType::GrandArmor, 0x0A => ArmorType::ShockFrame, 0x0B => ArmorType::KingsFrame, 0x0C => ArmorType::DragonFrame, 0x0D => ArmorType::AbsorbArmor, 0x0E => ArmorType::ProtectFrame, 0x0F => ArmorType::GeneralArmor, 0x10 => ArmorType::PerfectFrame, 0x11 => ArmorType::ValiantFrame, 0x12 => ArmorType::ImperialArmor, 0x13 => ArmorType::HolinessArmor, 0x14 => ArmorType::GuardianArmor, 0x15 => ArmorType::DivinityArmor, 0x16 => ArmorType::UltimateFrame, 0x17 => ArmorType::CelestialArmor, _ => panic!() } } pub fn slots(&self, _area_map: &MapArea, rng: &mut R) -> usize { let slot_weights = WeightedIndex::new(&[self.slot_rates.slot0, self.slot_rates.slot1, self.slot_rates.slot2, self.slot_rates.slot3, self.slot_rates.slot4]).unwrap(); slot_weights.sample(rng) } pub fn dfp_modifier(&self, armor_type: &ArmorType, rng: &mut R) -> u32 { let stats = self.armor_stats.get(armor_type).unwrap(); rng.gen_range(0, stats.dfp_modifier) } pub fn evp_modifier(&self, armor_type: &ArmorType, rng: &mut R) -> u32 { let stats = self.armor_stats.get(armor_type).unwrap(); rng.gen_range(0, stats.evp_modifier) } pub fn get_drop(&self, area_map: &MapArea, rng: &mut R) -> Option { let armor_type = self.armor_type(area_map, rng); let slots = self.slots(area_map, rng); let dfp_modifier = self.dfp_modifier(&armor_type, rng); let evp_modifier = self.dfp_modifier(&armor_type, rng); Some(ItemDropType::Armor(Armor { armor: armor_type, dfp: dfp_modifier as u8, evp: evp_modifier as u8, slots: slots as u8, })) } } #[cfg(test)] mod test { use super::*; use rand::{SeedableRng}; #[test] fn test_armor_generation() { let mut rng = rand_chacha::ChaCha20Rng::from_seed([23;32]); let gat = GenericArmorTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly); assert!(gat.get_drop(&MapArea::Mines1, &mut rng) == Some(ItemDropType::Armor(Armor { armor: ArmorType::GeneralArmor, dfp: 0, evp: 0, slots: 1, }))); assert!(gat.get_drop(&MapArea::Caves3, &mut rng) == Some(ItemDropType::Armor(Armor { armor: ArmorType::AbsorbArmor, dfp: 1, evp: 1, slots: 1, }))); assert!(gat.get_drop(&MapArea::Forest2, &mut rng) == Some(ItemDropType::Armor(Armor { armor: ArmorType::HyperFrame, dfp: 0, evp: 0, slots: 0, }))); assert!(gat.get_drop(&MapArea::DarkFalz, &mut rng) == Some(ItemDropType::Armor(Armor { armor: ArmorType::ImperialArmor, dfp: 2, evp: 1, slots: 0, }))); } }