301 lines
8.1 KiB
Rust
301 lines
8.1 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 crate::entity::item::ItemDetail;
|
|
use crate::entity::item::tool::{Tool, ToolType};
|
|
use crate::entity::item::tech::{Technique, TechniqueDisk};
|
|
use crate::ship::shops::ShopItem;
|
|
use crate::ship::item_stats::{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> {
|
|
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.partial_cmp(&b)
|
|
}
|
|
}
|
|
|
|
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)
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#[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).into_iter())
|
|
.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);
|
|
}
|
|
}
|
|
}
|