use std::collections::HashMap; use std::fs::File; use std::io::Read; use std::path::PathBuf; use serde::Deserialize; use rand::{Rng, SeedableRng}; use rand::distributions::{WeightedIndex, Distribution}; use rand::seq::{SliceRandom, IteratorRandom}; use crate::entity::character::SectionID; use crate::ship::room::Difficulty; use crate::entity::item::tool::{Tool, ToolType}; use crate::entity::item::tech::{Technique, TechniqueDisk}; #[derive(Debug, PartialEq, Eq)] pub enum ShopTool { Tool(ToolType), Tech(TechniqueDisk), } impl Ord for ShopTool { fn cmp(&self, other: &ShopTool) -> std::cmp::Ordering { let a = match self { ShopTool::Tool(t) => Tool{tool : *t}.as_individual_bytes(), ShopTool::Tech(t) => t.as_bytes(), }; let b = match other { ShopTool::Tool(t) => Tool{tool : *t}.as_individual_bytes(), ShopTool::Tech(t) => t.as_bytes(), }; a.cmp(&b) } } impl PartialOrd for ShopTool { fn partial_cmp(&self, other: &ShopTool) -> Option { let a = match self { ShopTool::Tool(t) => Tool{tool : *t}.as_individual_bytes(), ShopTool::Tech(t) => t.as_bytes(), }; let b = match other { ShopTool::Tool(t) => Tool{tool : *t}.as_individual_bytes(), ShopTool::Tech(t) => t.as_bytes(), }; a.partial_cmp(&b) } } #[derive(Debug, Deserialize)] struct ToolTable(Vec); #[derive(Debug, Deserialize, Clone)] #[serde(untagged)] enum TechLevel { Set { set_level: usize, }, Level { level_divisor: usize, }, Range { min: usize, max: usize, } } #[derive(Debug, Deserialize, Clone)] struct TechEntry { probability: usize, #[serde(flatten)] level: TechLevel, } #[derive(Debug, Deserialize)] struct TechTierDeserialize { level: usize, techs: HashMap, } #[derive(Debug, Clone)] struct TechTier { level: usize, techs: HashMap, } #[derive(Debug)] struct TechTable(Vec); fn load_tool_table() -> ToolTable { let path = PathBuf::from("data/shops/tools.toml"); let mut f = File::open(path).unwrap(); let mut s = String::new(); f.read_to_string(&mut s).unwrap(); let mut table: HashMap> = toml::from_str(s.as_str()).unwrap(); ToolTable(table.remove("tools".into()).unwrap()) } fn load_tech_table() -> TechTable { let path = PathBuf::from("data/shops/techniques.toml"); let mut f = File::open(path).unwrap(); let mut s = String::new(); f.read_to_string(&mut s).unwrap(); let mut table: HashMap> = toml::from_str(s.as_str()).unwrap(); let techniques = table.remove("techniques".into()).unwrap(); let techniques = techniques.into_iter() .map(|tech_tier| { TechTier { level: tech_tier.level, techs: tech_tier.techs.into_iter() .map(|(tech_name, tech_entry)| { (tech_name.parse().unwrap(), tech_entry) }).collect() } }).collect(); TechTable(techniques) } fn number_of_techs_to_generate(character_level: usize) -> usize { if character_level <= 10 { 4 } else if character_level <= 25 { 5 } else if character_level <= 42 { 6 } else { 7 } } #[derive(Debug)] struct ToolShop { tools: ToolTable, techs: TechTable, rng: R, } impl ToolShop { pub fn new() -> ToolShop { ToolShop { tools: load_tool_table(), techs: load_tech_table(), rng: R::from_entropy(), } } fn generate_tech(&mut self, character_level: usize) -> ShopTool { let tier = self.techs.0.iter() .filter(|t| t.level <= character_level) .last() .unwrap(); let mut tier = tier.techs.iter() .map(|(tech, entry)| { (tech, entry) }); let tech_choice = WeightedIndex::new(tier.clone().map(|(_, e)| e.probability)).unwrap(); let tech_detail = tier.nth(tech_choice.sample(&mut self.rng)).unwrap(); let tech_level = match tech_detail.1.level { TechLevel::Set{set_level} => set_level, TechLevel::Level{level_divisor} => std::cmp::max(std::cmp::min(character_level, 99)/level_divisor, 1), TechLevel::Range{min, max} => self.rng.gen_range(min, max+1), }; ShopTool::Tech( TechniqueDisk { tech: *tech_detail.0, level: tech_level as u32, } ) } fn generate_tech_types(&mut self, character_level: usize) -> Vec { let tier = self.techs.0.iter() .filter(|t| t.level <= character_level) .last() .unwrap(); let possible_techs = tier.techs.iter() .filter(|(_, entry)| entry.probability > 0) .collect::>(); let number_of_techs = std::cmp::min(possible_techs.len(), number_of_techs_to_generate(character_level)); if number_of_techs == possible_techs.len() { possible_techs.into_iter() .map(|(tech, _entry)| { tech }) .cloned() .collect() } else { let mut techs = Vec::new(); let tier = tier.techs.iter() .map(|(tech, entry)| { (tech, entry) }) .collect::>(); let tech_choice = WeightedIndex::new(tier.iter().map(|(_, e)| e.probability)).unwrap(); while techs.len() < number_of_techs { let tech_detail = tier.get(tech_choice.sample(&mut self.rng)).unwrap(); if techs.iter().find(|t| *t == tech_detail.0).is_none() { techs.push(*tech_detail.0); } } techs } } fn generate_techs(&mut self, character_level: usize) -> Vec { let tier = self.techs.0.iter() .filter(|t| t.level <= character_level) .last() .cloned() .unwrap(); let tech_types = self.generate_tech_types(character_level); tech_types.into_iter() .map(|tech| { let tech_detail = tier.techs.get(&tech).unwrap().clone(); let level = match tech_detail.level { TechLevel::Set{set_level} => set_level, TechLevel::Level{level_divisor} => std::cmp::max(std::cmp::min(character_level, 99)/level_divisor, 1), TechLevel::Range{min, max} => self.rng.gen_range(min, max+1), }; ShopTool::Tech(TechniqueDisk { tech: tech, level: level as u32, }) }) .collect() } pub fn generate_tool_list(&mut self, character_level: usize) -> Vec { let mut tools = Vec::new().into_iter() .chain(self.tools.0.clone().into_iter().map(|t| ShopTool::Tool(t))) .chain(self.generate_techs(character_level).into_iter()) .collect::>(); tools.sort(); tools } } #[cfg(test)] mod test { use super::*; #[test] fn test_loading_tool_shop() { ToolShop::::new(); } }