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::weapon::{WeaponType, WeaponSpecial, Attribute, WeaponAttribute}; const TIER1_SPECIAL: [WeaponSpecial; 8] = [WeaponSpecial::Draw, WeaponSpecial::Heart, WeaponSpecial::Ice, WeaponSpecial::Bind, WeaponSpecial::Heat, WeaponSpecial::Shock, WeaponSpecial::Dim, WeaponSpecial::Panic]; const TIER2_SPECIAL: [WeaponSpecial; 10] = [WeaponSpecial::Drain, WeaponSpecial::Mind, WeaponSpecial::Masters, WeaponSpecial::Charge, WeaponSpecial::Frost, WeaponSpecial::Hold, WeaponSpecial::Fire, WeaponSpecial::Thunder, WeaponSpecial::Shadow, WeaponSpecial::Riot]; #[derive(Debug)] pub struct ShopWeapon { weapon: WeaponType, special: Option, grind: usize, attributes: [Option; 2], } #[derive(Debug, Deserialize)] struct WeaponTableTierEntry { weapon: WeaponType, probability: usize, } #[derive(Debug, Deserialize)] struct WeaponTableTier { level: usize, weapons: Vec, } #[derive(Debug)] struct WeaponTable(Vec); #[derive(Debug, Deserialize)] struct GrindTier { level: usize, min: usize, max: usize, } #[derive(Debug)] struct GrindTable(Vec); #[derive(Debug, Deserialize)] struct AttributeTier { level: usize, percent_min: isize, percent_max: isize, none: usize, native: usize, abeast: usize, machine: usize, dark: usize, hit: usize, } #[derive(Debug)] struct AttributeTable(Vec); #[derive(Debug, Deserialize)] struct SpecialTierEntry { tier: usize, probability: usize, } #[derive(Debug, Deserialize)] struct SpecialTier { level: usize, special: Vec, } #[derive(Debug)] struct SpecialTable(Vec); /* trait WeaponTableLoader { fn load(difficulty: Difficulty, section_id: SectionID) -> WeaponTable where Self::Sized; } struct WeaponTableLoaderImpl; impl WeaponTableLoader for WeaponTableLoaderImpl { fn load(difficulty: Difficulty, section_id: SectionID) -> WeaponTable { let mut path = PathBuf::from("data/shops/"); path.push(difficulty.to_string().to_lowercase()); path.push(section_id.to_string().to_lowercase()); path.push("weapon.toml"); let mut f = File::open(path).unwrap(); let mut s = String::new(); f.read_to_string(&mut s); let table: Vec = toml::from_str(s.as_str()).unwrap(); println!("table {:?}", table); WeaponTable { } } } */ fn load_weapon_table(difficulty: Difficulty, section_id: SectionID) -> WeaponTable { let mut path = PathBuf::from("data/shops/"); path.push(difficulty.to_string().to_lowercase()); path.push(section_id.to_string().to_lowercase()); path.push("weapon.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(); WeaponTable(table.remove("weapon_tier".into()).unwrap()) } fn load_special_table() -> SpecialTable { let mut path = PathBuf::from("data/shops/special.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(); SpecialTable(table.remove("specials".into()).unwrap()) } fn load_grind_table() -> GrindTable { let mut path = PathBuf::from("data/shops/grind.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(); GrindTable(table.remove("grind".into()).unwrap()) } fn load_alt_grind_table() -> GrindTable { let mut path = PathBuf::from("data/shops/alt_grind.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(); GrindTable(table.remove("grind".into()).unwrap()) } fn load_attribute1_table() -> AttributeTable { let mut path = PathBuf::from("data/shops/attribute1.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(); AttributeTable(table.remove("attributes".into()).unwrap()) } fn load_attribute2_table() -> AttributeTable { let mut path = PathBuf::from("data/shops/attribute2.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(); AttributeTable(table.remove("attributes".into()).unwrap()) } #[derive(Debug)] struct WeaponShop { difficulty: Difficulty, section_id: SectionID, weapon: WeaponTable, special: SpecialTable, grind: GrindTable, alt_grind: GrindTable, attr1: AttributeTable, attr2: AttributeTable, rng: R, } impl WeaponShop { pub fn new(difficulty: Difficulty, section_id: SectionID) -> WeaponShop { WeaponShop { difficulty: difficulty, section_id: section_id, weapon: load_weapon_table(difficulty, section_id), special: load_special_table(), grind: load_grind_table(), alt_grind: load_alt_grind_table(), attr1: load_attribute1_table(), attr2: load_attribute1_table(), rng: R::from_entropy(), } } fn generate_type(&mut self, level: usize) -> WeaponType { let tier = self.weapon.0.iter() .filter(|t| t.level < level) .last() .unwrap(); let weapon_choice = WeightedIndex::new(tier.weapons.iter().map(|t| t.probability)).unwrap(); tier.weapons.get(weapon_choice.sample(&mut self.rng)).unwrap().weapon } fn generate_special(&mut self, level: usize) -> Option { let tier = self.special.0.iter() .filter(|t| t.level < level) .last() .unwrap(); let special_tier = WeightedIndex::new(tier.special.iter().map(|t| t.probability)).unwrap(); match special_tier.sample(&mut self.rng) { 1 => TIER1_SPECIAL.choose(&mut self.rng).cloned(), 2 => TIER2_SPECIAL.choose(&mut self.rng).cloned(), _ => None } } fn generate_grind(&mut self, level: usize) -> usize { let tier = self.grind.0.iter() .filter(|t| t.level < level) .last() .unwrap(); self.rng.gen_range(tier.min, tier.max+1) } fn generate_alt_grind(&mut self, level: usize) -> usize { let tier = self.alt_grind.0.iter() .filter(|t| t.level < level) .nth(0) .unwrap(); self.rng.gen_range(tier.min, tier.max+1) } fn generate_attribute1(&mut self, level: usize) -> Option { let tier = self.attr1.0.iter() .filter(|t| t.level < level) .last() .unwrap(); let attr_choice = WeightedIndex::new(&[tier.none, tier.native, tier.abeast, tier.machine, tier.dark, tier.hit]).unwrap(); let attr = match attr_choice.sample(&mut self.rng) { 0 => return None, 1 => Attribute::Native, 2 => Attribute::ABeast, 3 => Attribute::Machine, 4 => Attribute::Dark, 5 => Attribute::Hit, _ => panic!() }; let percent = (tier.percent_min..tier.percent_max+1) .filter(|p| p % 5 == 0) .choose(&mut self.rng)?; Some(WeaponAttribute { attr: attr, value: percent as i8, }) } fn generate_attribute2(&mut self, level: usize) -> Option { let tier = self.attr2.0.iter() .filter(|t| t.level < level) .last() .unwrap(); let attr_choice = WeightedIndex::new(&[tier.none, tier.native, tier.abeast, tier.machine, tier.dark, tier.hit]).unwrap(); let attr = match attr_choice.sample(&mut self.rng) { 0 => return None, 1 => Attribute::Native, 2 => Attribute::ABeast, 3 => Attribute::Machine, 4 => Attribute::Dark, 5 => Attribute::Hit, _ => panic!() }; let percent = (tier.percent_min..tier.percent_max+1) .filter(|p| p % 5 == 0) .choose(&mut self.rng)?; Some(WeaponAttribute { attr: attr, value: percent as i8, }) } fn is_alt_grind(&self, weapon: &WeaponType) -> bool { match (self.section_id, weapon) { (SectionID::Viridia, WeaponType::Shot) => true, (SectionID::Viridia, WeaponType::Spread) => true, (SectionID::Viridia, WeaponType::Cannon) => true, (SectionID::Viridia, WeaponType::Launcher) => true, (SectionID::Viridia, WeaponType::Arms) => true, (SectionID::Greenill, WeaponType::Rifle) => true, (SectionID::Greenill, WeaponType::Sniper) => true, (SectionID::Greenill, WeaponType::Blaster) => true, (SectionID::Greenill, WeaponType::Beam) => true, (SectionID::Greenill, WeaponType::Laser) => true, (SectionID::Skyly, WeaponType::Sword) => true, (SectionID::Skyly, WeaponType::Gigush) => true, (SectionID::Skyly, WeaponType::Breaker) => true, (SectionID::Skyly, WeaponType::Claymore) => true, (SectionID::Skyly, WeaponType::Calibur) => true, (SectionID::Bluefull, WeaponType::Partisan) => true, (SectionID::Bluefull, WeaponType::Halbert) => true, (SectionID::Bluefull, WeaponType::Glaive) => true, (SectionID::Bluefull, WeaponType::Berdys) => true, (SectionID::Bluefull, WeaponType::Gungnir) => true, (SectionID::Purplenum, WeaponType::Mechgun) => true, (SectionID::Purplenum, WeaponType::Assault) => true, (SectionID::Purplenum, WeaponType::Repeater) => true, (SectionID::Purplenum, WeaponType::Gatling) => true, (SectionID::Purplenum, WeaponType::Vulcan) => true, (SectionID::Pinkal, WeaponType::Cane) => true, (SectionID::Pinkal, WeaponType::Stick) => true, (SectionID::Pinkal, WeaponType::Mace) => true, (SectionID::Pinkal, WeaponType::Club) => true, (SectionID::Oran, WeaponType::Dagger) => true, (SectionID::Oran, WeaponType::Knife) => true, (SectionID::Oran, WeaponType::Blade) => true, (SectionID::Oran, WeaponType::Edge) => true, (SectionID::Oran, WeaponType::Ripper) => true, (SectionID::Whitill, WeaponType::Slicer) => true, (SectionID::Whitill, WeaponType::Spinner) => true, (SectionID::Whitill, WeaponType::Cutter) => true, (SectionID::Whitill, WeaponType::Sawcer) => true, (SectionID::Whitill, WeaponType::Diska) => true, _ => false, } } pub fn generate_weapon(&mut self, level: usize) -> ShopWeapon { let weapon = self.generate_type(level); let grind = if self.is_alt_grind(&weapon) { self.generate_alt_grind(level) } else { self.generate_grind(level) }; let special = self.generate_special(level); let (attr1, attr2) = { match self.generate_attribute1(level) { Some(a1) => { let a2 = loop { let attr = self.generate_attribute2(level); match attr { Some(a2) => { if a2.attr != a1.attr { break Some(a2); } }, None => break None, } }; (Some(a1), a2) }, None => { let a2 = self.generate_attribute2(level); (a2, None) } } }; ShopWeapon { weapon: weapon, grind: grind, special: special, attributes: [attr1, attr2], } } } #[cfg(test)] mod test { use super::*; #[test] fn test_loading_weapons() { WeaponShop::::new(Difficulty::Ultimate, SectionID::Pinkal); } }