use std::collections::{BTreeMap}; use serde::{Serialize, Deserialize}; use rand::{Rng}; use rand::distributions::{WeightedIndex, Distribution}; use entity::item::tech::{Technique, TechniqueDisk}; use maps::room::{Difficulty, Episode}; use maps::area::MapArea; use entity::character::SectionID; use crate::{ItemDropType, load_data_file}; #[derive(Debug, Serialize, Deserialize)] struct TechniqueRateStat { rate: u32, min: i32, max: i32, } #[derive(Debug, Serialize, Deserialize)] struct TechniqueRatesRaw { area1: BTreeMap, area2: BTreeMap, area3: BTreeMap, area4: BTreeMap, area5: BTreeMap, area6: BTreeMap, area7: BTreeMap, area8: BTreeMap, area9: BTreeMap, area10: BTreeMap, } #[derive(Debug, Serialize, Deserialize)] struct TechniqueRates { area1: BTreeMap, area2: BTreeMap, area3: BTreeMap, area4: BTreeMap, area5: BTreeMap, area6: BTreeMap, area7: BTreeMap, area8: BTreeMap, area9: BTreeMap, area10: BTreeMap, } impl TechniqueRates { fn new(rates: TechniqueRatesRaw) -> TechniqueRates { TechniqueRates { area1: rates.area1.into_iter().map(|(tech, rate)| (tech.parse().unwrap(), rate)).collect(), area2: rates.area2.into_iter().map(|(tech, rate)| (tech.parse().unwrap(), rate)).collect(), area3: rates.area3.into_iter().map(|(tech, rate)| (tech.parse().unwrap(), rate)).collect(), area4: rates.area4.into_iter().map(|(tech, rate)| (tech.parse().unwrap(), rate)).collect(), area5: rates.area5.into_iter().map(|(tech, rate)| (tech.parse().unwrap(), rate)).collect(), area6: rates.area6.into_iter().map(|(tech, rate)| (tech.parse().unwrap(), rate)).collect(), area7: rates.area7.into_iter().map(|(tech, rate)| (tech.parse().unwrap(), rate)).collect(), area8: rates.area8.into_iter().map(|(tech, rate)| (tech.parse().unwrap(), rate)).collect(), area9: rates.area9.into_iter().map(|(tech, rate)| (tech.parse().unwrap(), rate)).collect(), area10: rates.area10.into_iter().map(|(tech, rate)| (tech.parse().unwrap(), rate)).collect(), } } } impl TechniqueRates { fn get_by_area<'a>(&'a self, map_area: &MapArea) -> &'a BTreeMap { match map_area.drop_area_value().unwrap() { 0 => &self.area1, 1 => &self.area2, 2 => &self.area3, 3 => &self.area4, 4 => &self.area5, 5 => &self.area6, 6 => &self.area7, 7 => &self.area8, 8 => &self.area9, _ => &self.area10, } } } pub struct TechniqueTable { rates: TechniqueRates } impl TechniqueTable { pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> TechniqueTable { let rates: TechniqueRatesRaw = load_data_file(episode, difficulty, section_id, "tech_rate.toml"); TechniqueTable { rates: TechniqueRates::new(rates), } } pub fn get_drop(&self, map_area: &MapArea, rng: &mut R) -> Option { let mut tech_rates = self.rates.get_by_area(map_area).iter(); let tech_weights = WeightedIndex::new(tech_rates.clone().map(|(_, stat)| stat.rate)).unwrap(); let (tech, stat) = tech_rates.nth(tech_weights.sample(rng)).unwrap(); let level = rng.gen_range(stat.min, stat.max+1) + 1; Some(ItemDropType::TechniqueDisk(TechniqueDisk { tech: *tech, level: level as u32 })) } } #[cfg(test)] mod test { use super::*; use rand::{SeedableRng}; #[test] fn test_tech_drops() { let mut rng = rand_chacha::ChaCha20Rng::from_seed([23;32]); let tt = TechniqueTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly); let tech_tests = vec![(MapArea::Forest1, Technique::Resta, 14), (MapArea::Caves3, Technique::Foie, 25), (MapArea::Mines2, Technique::Gibarta, 21), (MapArea::DarkFalz, Technique::Razonde, 23)]; for (area, tech, level) in tech_tests { assert!(tt.get_drop(&area, &mut rng) == Some(ItemDropType::TechniqueDisk( TechniqueDisk { tech: tech, level: level }))); } } }