use std::collections::BTreeMap; use serde::{Serialize, Deserialize}; use rand::Rng; use rand::seq::IteratorRandom; use entity::character::SectionID; use entity::item::unit::{UnitType, Unit, UnitModifier}; use maps::room::{Difficulty, Episode}; use maps::area::MapArea; use crate::ship::drops::{ItemDropType, load_data_file}; use stats::items::{unit_stats, UnitStats}; #[derive(Debug, Serialize, Deserialize)] struct UnitLevels { area1: u32, area2: u32, area3: u32, area4: u32, area5: u32, area6: u32, area7: u32, area8: u32, area9: u32, area10: u32, } impl UnitLevels { fn level_by_area(&self, map_area: &MapArea) -> u32 { match map_area.drop_area_value().unwrap() { 0 => self.area1, 1 => self.area2, 2 => self.area3, 3 => self.area1, 4 => self.area1, 5 => self.area6, 6 => self.area7, 7 => self.area8, 8 => self.area9, 9 => self.area10, _ => panic!() } } } pub struct GenericUnitTable { unit_levels: UnitLevels, unit_stats: BTreeMap, } impl GenericUnitTable { pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> GenericUnitTable { GenericUnitTable { unit_levels: load_data_file(episode, difficulty, section_id, "unit_rate.toml"), unit_stats: unit_stats(), } } fn unit_type_and_modifier(&self, area_map: &MapArea, rng: &mut R) -> Option<(UnitType, Option)> { let level = self.unit_levels.level_by_area(area_map) as i32; if level == 0 { return None; } let units = self.unit_stats .iter() .filter(|(_, stats)| { stats.stars < 9 }) .filter_map(|(utype, stats)| { match level - stats.stars as i32 { -1 if stats.modifier != 0 => Some(vec![(*utype, Some(UnitModifier::Minus)), (*utype, Some(UnitModifier::MinusMinus))]), 0 => Some(vec![(*utype, None)]), 1 if stats.modifier != 0 => Some(vec![(*utype, Some(UnitModifier::Plus)), (*utype, Some(UnitModifier::PlusPlus))]), _ => None, } }) .flatten(); Some(units.choose(rng).unwrap()) } pub fn get_drop(&self, area_map: &MapArea, rng: &mut R) -> Option { let unit_type_modifier = self.unit_type_and_modifier(area_map, rng); unit_type_modifier.map(|(unit_type, unit_modifier)| { ItemDropType::Unit(Unit { unit: unit_type, modifier: unit_modifier, }) }) } } #[cfg(test)] mod test { use super::*; use rand::{SeedableRng}; #[test] fn test_unit_drops() { let mut rng = rand_chacha::ChaCha20Rng::from_seed([23;32]); let gut = GenericUnitTable::new(Episode::One, Difficulty::Normal, SectionID::Skyly); assert!(gut.get_drop(&MapArea::Forest1, &mut rng) == None); let gut = GenericUnitTable::new(Episode::One, Difficulty::Hard, SectionID::Skyly); let unit_tests = vec![(MapArea::Forest1, UnitType::ResistFreeze, Some(UnitModifier::PlusPlus)), (MapArea::Caves3, UnitType::GeneralTp, None), (MapArea::Mines2, UnitType::ResistEvil, Some(UnitModifier::PlusPlus)), (MapArea::DarkFalz, UnitType::DragonHp, Some(UnitModifier::Minus))]; for (area, unit, umod) in unit_tests { assert!(gut.get_drop(&area, &mut rng) == Some(ItemDropType::Unit(Unit { unit: unit, modifier: umod, }))); } } }