406 lines
13 KiB
Rust
406 lines
13 KiB
Rust
|
|
||
|
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<WeaponSpecial>,
|
||
|
grind: usize,
|
||
|
attributes: [Option<WeaponAttribute>; 2],
|
||
|
}
|
||
|
|
||
|
|
||
|
#[derive(Debug, Deserialize)]
|
||
|
struct WeaponTableTierEntry {
|
||
|
weapon: WeaponType,
|
||
|
probability: usize,
|
||
|
}
|
||
|
|
||
|
#[derive(Debug, Deserialize)]
|
||
|
struct WeaponTableTier {
|
||
|
level: usize,
|
||
|
weapons: Vec<WeaponTableTierEntry>,
|
||
|
}
|
||
|
|
||
|
#[derive(Debug)]
|
||
|
struct WeaponTable(Vec<WeaponTableTier>);
|
||
|
|
||
|
#[derive(Debug, Deserialize)]
|
||
|
struct GrindTier {
|
||
|
level: usize,
|
||
|
min: usize,
|
||
|
max: usize,
|
||
|
}
|
||
|
|
||
|
#[derive(Debug)]
|
||
|
struct GrindTable(Vec<GrindTier>);
|
||
|
|
||
|
#[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<AttributeTier>);
|
||
|
|
||
|
#[derive(Debug, Deserialize)]
|
||
|
struct SpecialTierEntry {
|
||
|
tier: usize,
|
||
|
probability: usize,
|
||
|
}
|
||
|
|
||
|
#[derive(Debug, Deserialize)]
|
||
|
struct SpecialTier {
|
||
|
level: usize,
|
||
|
special: Vec<SpecialTierEntry>,
|
||
|
}
|
||
|
|
||
|
#[derive(Debug)]
|
||
|
struct SpecialTable(Vec<SpecialTier>);
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
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<WeaponTableTier> = 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<String, Vec<WeaponTableTier>> = 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<String, Vec<SpecialTier>> = 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<String, Vec<GrindTier>> = 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<String, Vec<GrindTier>> = 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<String, Vec<AttributeTier>> = 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<String, Vec<AttributeTier>> = toml::from_str(s.as_str()).unwrap();
|
||
|
|
||
|
AttributeTable(table.remove("attributes".into()).unwrap())
|
||
|
}
|
||
|
|
||
|
#[derive(Debug)]
|
||
|
struct WeaponShop<R: Rng + SeedableRng> {
|
||
|
difficulty: Difficulty,
|
||
|
section_id: SectionID,
|
||
|
weapon: WeaponTable,
|
||
|
special: SpecialTable,
|
||
|
grind: GrindTable,
|
||
|
alt_grind: GrindTable,
|
||
|
attr1: AttributeTable,
|
||
|
attr2: AttributeTable,
|
||
|
rng: R,
|
||
|
}
|
||
|
|
||
|
impl<R: Rng + SeedableRng> WeaponShop<R> {
|
||
|
pub fn new(difficulty: Difficulty, section_id: SectionID) -> WeaponShop<R> {
|
||
|
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<WeaponSpecial> {
|
||
|
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<WeaponAttribute> {
|
||
|
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<WeaponAttribute> {
|
||
|
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::<rand_chacha::ChaCha20Rng>::new(Difficulty::Ultimate, SectionID::Pinkal);
|
||
|
}
|
||
|
}
|