diff --git a/data/shops/techniques.toml b/data/shops/techniques.toml new file mode 100644 index 0000000..4982b64 --- /dev/null +++ b/data/shops/techniques.toml @@ -0,0 +1,299 @@ +[[techniques]] +level = 0 + +[techniques.techs.Foie] +probability = 18 +level_divisor = 3 +[techniques.techs.Barta] +probability = 18 +level_divisor = 4 +[techniques.techs.Zonde] +probability = 18 +level_divisor = 6 +[techniques.techs.Resta] +probability = 18 +level_divisor = 3 +[techniques.techs.Anti] +probability = 18 +level_divisor = 35 +[techniques.techs.Shifta] +probability = 2 +set_level = 1 +[techniques.techs.Deband] +probability = 3 +set_level = 1 +[techniques.techs.Jellen] +probability = 3 +set_level = 1 +[techniques.techs.Zalure] +probability = 2 +set_level = 1 +[techniques.techs.Gifoie] +probability = 0 +set_level = 1 +[techniques.techs.Gibarta] +probability = 0 +set_level = 1 +[techniques.techs.Gizonde] +probability = 0 +set_level = 1 +[techniques.techs.Ryuker] +probability = 0 +set_level = 1 +[techniques.techs.Reverser] +probability = 0 +set_level = 1 +[techniques.techs.Rafoie] +probability = 0 +set_level = 1 +[techniques.techs.Rabarta] +probability = 0 +set_level = 1 +[techniques.techs.Razonde] +probability = 0 +set_level = 1 +[[techniques]] +level = 10 + +[techniques.techs.Foie] +probability = 7 +level_divisor = 6 +[techniques.techs.Barta] +probability = 7 +level_divisor = 7 +[techniques.techs.Zonde] +probability = 7 +level_divisor = 8 +[techniques.techs.Resta] +probability = 7 +level_divisor = 6 +[techniques.techs.Anti] +probability = 7 +level_divisor = 35 +[techniques.techs.Shifta] +probability = 6 +level_divisor = 9 +[techniques.techs.Deband] +probability = 6 +level_divisor = 7 +[techniques.techs.Jellen] +probability = 6 +level_divisor = 6 +[techniques.techs.Zalure] +probability = 7 +level_divisor = 8 +[techniques.techs.Gifoie] +probability = 8 +level_divisor = 9 +[techniques.techs.Gibarta] +probability = 8 +level_divisor = 12 +[techniques.techs.Gizonde] +probability = 8 +level_divisor = 8 +[techniques.techs.Ryuker] +probability = 5 +set_level = 1 +[techniques.techs.Reverser] +probability = 5 +set_level = 1 +[techniques.techs.Rafoie] +probability = 2 +set_level = 1 +[techniques.techs.Rabarta] +probability = 2 +set_level = 1 +[techniques.techs.Razonde] +probability = 2 +set_level = 1 +[[techniques]] +level = 25 + +[techniques.techs.Foie] +probability = 5 +min = 3 +max = 10 +[techniques.techs.Barta] +probability = 5 +min = 3 +max = 10 +[techniques.techs.Zonde] +probability = 5 +min = 3 +max = 10 +[techniques.techs.Resta] +probability = 6 +min = 3 +max = 10 +[techniques.techs.Anti] +probability = 5 +level_divisor = 35 +[techniques.techs.Shifta] +probability = 5 +level_divisor = 8 +[techniques.techs.Deband] +probability = 5 +level_divisor = 7 +[techniques.techs.Jellen] +probability = 5 +level_divisor = 5 +[techniques.techs.Zalure] +probability = 5 +level_divisor = 6 +[techniques.techs.Gifoie] +probability = 5 +level_divisor = 8 +[techniques.techs.Gibarta] +probability = 5 +level_divisor = 9 +[techniques.techs.Gizonde] +probability = 5 +level_divisor = 10 +[techniques.techs.Ryuker] +probability = 6 +set_level = 1 +[techniques.techs.Reverser] +probability = 6 +set_level = 1 +[techniques.techs.Rafoie] +probability = 9 +level_divisor = 10 +[techniques.techs.Rabarta] +probability = 9 +level_divisor = 11 +[techniques.techs.Razonde] +probability = 9 +level_divisor = 12 +[[techniques]] +level = 42 + +[techniques.techs.Foie] +probability = 6 +min = 4 +max = 12 +[techniques.techs.Barta] +probability = 6 +min = 4 +max = 12 +[techniques.techs.Zonde] +probability = 6 +min = 4 +max = 12 +[techniques.techs.Resta] +probability = 5 +min = 4 +max = 12 +[techniques.techs.Anti] +probability = 5 +level_divisor = 35 +[techniques.techs.Shifta] +probability = 6 +min = 4 +max = 12 +[techniques.techs.Deband] +probability = 6 +min = 4 +max = 12 +[techniques.techs.Jellen] +probability = 6 +min = 4 +max = 12 +[techniques.techs.Zalure] +probability = 6 +min = 4 +max = 12 +[techniques.techs.Gifoie] +probability = 6 +min = 3 +max = 10 +[techniques.techs.Gibarta] +probability = 6 +min = 3 +max = 10 +[techniques.techs.Gizonde] +probability = 6 +min = 3 +max = 10 +[techniques.techs.Ryuker] +probability = 6 +set_level = 1 +[techniques.techs.Reverser] +probability = 6 +set_level = 1 +[techniques.techs.Rafoie] +probability = 6 +level_divisor = 12 +[techniques.techs.Rabarta] +probability = 6 +level_divisor = 13 +[techniques.techs.Razonde] +probability = 6 +level_divisor = 11 +[[techniques]] +level = 60 + +[techniques.techs.Foie] +probability = 6 +min = 5 +max = 14 +[techniques.techs.Barta] +probability = 6 +min = 5 +max = 14 +[techniques.techs.Zonde] +probability = 6 +min = 5 +max = 14 +[techniques.techs.Resta] +probability = 5 +min = 5 +max = 14 +[techniques.techs.Anti] +probability = 5 +level_divisor = 35 +[techniques.techs.Shifta] +probability = 6 +min = 5 +max = 14 +[techniques.techs.Deband] +probability = 6 +min = 5 +max = 14 +[techniques.techs.Jellen] +probability = 6 +min = 5 +max = 14 +[techniques.techs.Zalure] +probability = 6 +min = 5 +max = 14 +[techniques.techs.Gifoie] +probability = 6 +min = 4 +max = 13 +[techniques.techs.Gibarta] +probability = 6 +min = 4 +max = 13 +[techniques.techs.Gizonde] +probability = 6 +min = 4 +max = 13 +[techniques.techs.Ryuker] +probability = 6 +set_level = 1 +[techniques.techs.Reverser] +probability = 6 +set_level = 1 +[techniques.techs.Rafoie] +probability = 6 +min = 3 +max = 13 +[techniques.techs.Rabarta] +probability = 6 +min = 3 +max = 13 +[techniques.techs.Razonde] +probability = 6 +min = 3 +max = 13 diff --git a/data/shops/tools.toml b/data/shops/tools.toml new file mode 100644 index 0000000..d0731bb --- /dev/null +++ b/data/shops/tools.toml @@ -0,0 +1 @@ +tools = [ "Monomate", "Dimate", "Trimate", "Monofluid", "Difluid", "Trifluid", "Antidote", "Antiparalysis", "SolAtomizer", "MoonAtomizer", "StarAtomizer", "Telepipe",] diff --git a/src/ship/shops/mod.rs b/src/ship/shops/mod.rs index e22b3b2..d24e7a1 100644 --- a/src/ship/shops/mod.rs +++ b/src/ship/shops/mod.rs @@ -1 +1,2 @@ pub mod weapon; +pub mod tool; diff --git a/src/ship/shops/tool.rs b/src/ship/shops/tool.rs new file mode 100644 index 0000000..e3a7534 --- /dev/null +++ b/src/ship/shops/tool.rs @@ -0,0 +1,268 @@ +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(); + } +}