304 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			304 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use std::collections::HashMap;
 | |
| use std::fs::File;
 | |
| use std::io::Read;
 | |
| use std::path::PathBuf;
 | |
| use std::convert::TryInto;
 | |
| use serde::Deserialize;
 | |
| use rand::{Rng, SeedableRng};
 | |
| use rand::distributions::{WeightedIndex, Distribution};
 | |
| use entity::item::ItemDetail;
 | |
| use entity::item::tool::{Tool, ToolType};
 | |
| use entity::item::tech::{Technique, TechniqueDisk};
 | |
| use crate::ShopItem;
 | |
| use stats::items::{TOOL_STATS, TECH_STATS};
 | |
| 
 | |
| 
 | |
| #[derive(Debug, PartialEq, Eq)]
 | |
| pub enum ToolShopItem {
 | |
|     Tool(ToolType),
 | |
|     Tech(TechniqueDisk),
 | |
| }
 | |
| 
 | |
| impl Ord for ToolShopItem {
 | |
|     fn cmp(&self, other: &ToolShopItem) -> std::cmp::Ordering {
 | |
|         let a = match self {
 | |
|             ToolShopItem::Tool(t) => Tool{tool : *t}.as_individual_bytes(),
 | |
|             ToolShopItem::Tech(t) => t.as_bytes(),
 | |
|         };
 | |
|         let b = match other {
 | |
|             ToolShopItem::Tool(t) => Tool{tool : *t}.as_individual_bytes(),
 | |
|             ToolShopItem::Tech(t) => t.as_bytes(),
 | |
|         };
 | |
| 
 | |
|         a.cmp(&b)
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl PartialOrd for ToolShopItem {
 | |
|     fn partial_cmp(&self, other: &ToolShopItem) -> Option<std::cmp::Ordering> {
 | |
|         Some(self.cmp(other))
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl ShopItem for ToolShopItem {
 | |
|     fn price(&self) -> usize {
 | |
|         match self {
 | |
|             ToolShopItem::Tool(tool) => {
 | |
|                 TOOL_STATS.get(tool)
 | |
|                     .map(|tool_stats| {
 | |
|                         tool_stats.price
 | |
|                     })
 | |
|                     .unwrap_or(0xFFFF)
 | |
|             },
 | |
|             ToolShopItem::Tech(tech) => {
 | |
|                 TECH_STATS.get(&tech.tech)
 | |
|                     .map(|tech_stats| {
 | |
|                         tech_stats.price * tech.level as usize
 | |
|                     })
 | |
|                     .unwrap_or(0xFFFF)
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn as_bytes(&self) -> [u8; 12] {
 | |
|         match self {
 | |
|             ToolShopItem::Tool(tool) => {
 | |
|                 Tool {
 | |
|                     tool: *tool
 | |
|                 }.as_individual_bytes()[0..12].try_into().unwrap()
 | |
|             },
 | |
|             ToolShopItem::Tech(tech) => {
 | |
|                 tech.as_bytes()[0..12].try_into().unwrap()
 | |
|             },
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn as_item(&self) -> ItemDetail {
 | |
|         match self {
 | |
|             ToolShopItem::Tool(tool) => {
 | |
|                 ItemDetail::Tool(Tool {
 | |
|                     tool: *tool
 | |
|                 })
 | |
|             },
 | |
|             ToolShopItem::Tech(tech) => {
 | |
|                 ItemDetail::TechniqueDisk(*tech)
 | |
|             },
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl From<&Tool> for ToolShopItem {
 | |
|     fn from(tool: &Tool) -> ToolShopItem {
 | |
|         ToolShopItem::Tool(tool.tool)
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl From<&TechniqueDisk> for ToolShopItem {
 | |
|     fn from(techdisk: &TechniqueDisk) -> ToolShopItem {
 | |
|         ToolShopItem::Tech(*techdisk)
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| #[derive(Debug, Deserialize)]
 | |
| struct ToolTable(Vec<ToolType>);
 | |
| 
 | |
| #[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<String, TechEntry>,
 | |
| }
 | |
| 
 | |
| #[derive(Debug, Clone)]
 | |
| struct TechTier {
 | |
|     level: usize,
 | |
|     techs: HashMap<Technique, TechEntry>,
 | |
| }
 | |
| 
 | |
| #[derive(Debug)]
 | |
| struct TechTable(Vec<TechTier>);
 | |
| 
 | |
| 
 | |
| 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<String, Vec<ToolType>> = toml::from_str(s.as_str()).unwrap();
 | |
| 
 | |
|     ToolTable(table.remove("tools").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<String, Vec<TechTierDeserialize>> = toml::from_str(s.as_str()).unwrap();
 | |
|     let techniques = table.remove("techniques").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)]
 | |
| pub struct ToolShop<R: Rng + SeedableRng> {
 | |
|     tools: ToolTable,
 | |
|     techs: TechTable,
 | |
|     rng: R,
 | |
| }
 | |
| 
 | |
| 
 | |
| impl<R: Rng + SeedableRng> Default for ToolShop<R> {
 | |
|     fn default() -> ToolShop<R> {
 | |
|         ToolShop {
 | |
|             tools: load_tool_table(),
 | |
|             techs: load_tech_table(),
 | |
|             rng: R::from_entropy(),
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl<R: Rng + SeedableRng> ToolShop<R> {
 | |
|     fn generate_tech_types(&mut self, character_level: usize) -> Vec<Technique> {
 | |
|         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::<Vec<_>>();
 | |
| 
 | |
|         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::<Vec<_>>();
 | |
| 
 | |
|             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().any(|t| t == tech_detail.0) {
 | |
|                     techs.push(*tech_detail.0);
 | |
|                 }
 | |
|             }
 | |
|             techs
 | |
|         }
 | |
|         
 | |
|     }
 | |
| 
 | |
|     fn generate_techs(&mut self, character_level: usize) -> Vec<ToolShopItem> {
 | |
|         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),
 | |
|                 };
 | |
| 
 | |
|                 ToolShopItem::Tech(TechniqueDisk {
 | |
|                     tech,
 | |
|                     level: level as u32,
 | |
|                 })
 | |
|             })
 | |
|             .collect()
 | |
|     }
 | |
| 
 | |
|     pub fn generate_tool_list(&mut self, character_level: usize) -> Vec<ToolShopItem> {
 | |
|         let mut tools = Vec::new().into_iter()
 | |
|             .chain(self.tools.0.clone().into_iter().map(ToolShopItem::Tool))
 | |
|             .chain(self.generate_techs(character_level))
 | |
|             .collect::<Vec<_>>();
 | |
|         tools.sort();
 | |
|         tools
 | |
|     }
 | |
|     
 | |
| }
 | |
| 
 | |
| #[cfg(test)]
 | |
| mod test {
 | |
|     use super::*;
 | |
| 
 | |
|     #[test]
 | |
|     fn test_loading_tool_shop() {
 | |
|         ToolShop::<rand_chacha::ChaCha20Rng>::default();
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn test_generating_some_tools() {
 | |
|         let mut ts = ToolShop::<rand_chacha::ChaCha20Rng>::default();
 | |
|         for i in 0..200 {
 | |
|             ts.generate_tool_list(i);
 | |
|         }
 | |
|     }
 | |
| }
 |