You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
369 lines
9.8 KiB
369 lines
9.8 KiB
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::armor::{Armor, ArmorType};
|
|
use entity::item::shield::{Shield, ShieldType};
|
|
use entity::item::unit::{Unit, UnitType};
|
|
use crate::ShopItem;
|
|
use stats::items::{ARMOR_STATS, SHIELD_STATS, UNIT_STATS};
|
|
|
|
// #[derive(Debug)]
|
|
// pub enum ArmorShopItem {
|
|
// Frame(ArmorType, usize), // slots
|
|
// Barrier(ShieldType),
|
|
// Unit(UnitType),
|
|
// }
|
|
|
|
#[derive(Debug)]
|
|
pub enum ArmorShopItem {
|
|
Frame(Armor), // slots
|
|
Barrier(Shield),
|
|
Unit(Unit),
|
|
}
|
|
|
|
const ARMOR_MULTIPLIER: f32 = 0.799_999_95;
|
|
const SHIELD_MULTIPLIER: f32 = 1.5;
|
|
const UNIT_MULTIPLIER: f32 = 1000.0;
|
|
|
|
// TODO: reduce the number of type casts?
|
|
impl ShopItem for ArmorShopItem {
|
|
fn price(&self) -> usize {
|
|
match self {
|
|
ArmorShopItem::Frame(frame) => {
|
|
ARMOR_STATS.get(&frame.armor)
|
|
.map(|frame_stats| {
|
|
let mut price = (frame_stats.dfp + frame_stats.evp + frame.dfp as i32 + frame.evp as i32) as f32;
|
|
price *= price;
|
|
price /= ARMOR_MULTIPLIER;
|
|
price += 70.0 * (frame_stats.level_req + 1) as f32;
|
|
price += 70.0 * (frame_stats.level_req + 1) as f32 * frame.slots as f32;
|
|
price as usize
|
|
})
|
|
.unwrap_or(0xFFFF)
|
|
},
|
|
ArmorShopItem::Barrier(barrier) => {
|
|
SHIELD_STATS.get(&barrier.shield)
|
|
.map(|barrier_stats| {
|
|
let mut price = (barrier_stats.dfp + barrier_stats.evp + barrier.dfp as i32 + barrier.evp as i32) as f32;
|
|
price *= price;
|
|
price /= SHIELD_MULTIPLIER;
|
|
price += 70.0 * (barrier_stats.level_req + 1) as f32;
|
|
price as usize
|
|
})
|
|
.unwrap_or(0xFFFF)
|
|
},
|
|
ArmorShopItem::Unit(unit) => {
|
|
UNIT_STATS.get(&unit.unit)
|
|
.map(|unit_stats| {
|
|
((unit_stats.stars as f32 + unit.modifier_stars() as f32) * UNIT_MULTIPLIER) as usize
|
|
})
|
|
.unwrap_or(0xFFFF)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn as_bytes(&self) -> [u8; 12] {
|
|
self.as_item().as_client_bytes()[0..12].try_into().unwrap()
|
|
}
|
|
|
|
fn as_item(&self) -> ItemDetail {
|
|
match self {
|
|
ArmorShopItem::Frame(frame) => {
|
|
ItemDetail::Armor(Armor {
|
|
armor: frame.armor,
|
|
dfp: 0,
|
|
evp: 0,
|
|
slots: frame.slots,
|
|
})
|
|
},
|
|
ArmorShopItem::Barrier(barrier) => {
|
|
ItemDetail::Shield(Shield {
|
|
shield: barrier.shield,
|
|
dfp: 0,
|
|
evp: 0,
|
|
})
|
|
},
|
|
ArmorShopItem::Unit(unit) => {
|
|
ItemDetail::Unit(Unit {
|
|
unit: unit.unit,
|
|
modifier: None,
|
|
})
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&Armor> for ArmorShopItem {
|
|
fn from(armor: &Armor) -> ArmorShopItem {
|
|
ArmorShopItem::Frame(*armor)
|
|
}
|
|
}
|
|
|
|
impl From<&Shield> for ArmorShopItem {
|
|
fn from(shield: &Shield) -> ArmorShopItem {
|
|
ArmorShopItem::Barrier(*shield)
|
|
}
|
|
}
|
|
|
|
impl From<&Unit> for ArmorShopItem {
|
|
fn from(unit: &Unit) -> ArmorShopItem {
|
|
ArmorShopItem::Unit(*unit)
|
|
}
|
|
}
|
|
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
struct FrameTierItem {
|
|
item: ArmorType,
|
|
probability: usize,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
struct FrameTier {
|
|
level: usize,
|
|
item: Vec<FrameTierItem>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct SlotRate {
|
|
slot: usize,
|
|
probability: usize,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct FrameTable {
|
|
frame: Vec<FrameTier>,
|
|
slot_rate: Vec<SlotRate>,
|
|
}
|
|
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
struct BarrierTierItem {
|
|
item: ShieldType,
|
|
probability: usize,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
struct BarrierTier {
|
|
level: usize,
|
|
item: Vec<BarrierTierItem>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct BarrierTable {
|
|
barrier: Vec<BarrierTier>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
struct UnitTierItem {
|
|
item: UnitType,
|
|
probability: usize,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
struct UnitTier {
|
|
level: usize,
|
|
item: Vec<UnitTierItem>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct UnitTable {
|
|
unit: Vec<UnitTier>,
|
|
}
|
|
|
|
|
|
|
|
fn load_frame_table() -> FrameTable {
|
|
let path = PathBuf::from("data/shops/frame.toml");
|
|
let mut f = File::open(path).unwrap();
|
|
let mut s = String::new();
|
|
f.read_to_string(&mut s).unwrap();
|
|
|
|
let table: FrameTable = toml::from_str(s.as_str()).unwrap();
|
|
table
|
|
}
|
|
|
|
fn load_barrier_table() -> BarrierTable {
|
|
let path = PathBuf::from("data/shops/barrier.toml");
|
|
let mut f = File::open(path).unwrap();
|
|
let mut s = String::new();
|
|
f.read_to_string(&mut s).unwrap();
|
|
|
|
let table: BarrierTable = toml::from_str(s.as_str()).unwrap();
|
|
table
|
|
}
|
|
|
|
fn load_unit_table() -> UnitTable {
|
|
let path = PathBuf::from("data/shops/unit.toml");
|
|
let mut f = File::open(path).unwrap();
|
|
let mut s = String::new();
|
|
f.read_to_string(&mut s).unwrap();
|
|
|
|
let table: UnitTable = toml::from_str(s.as_str()).unwrap();
|
|
table
|
|
}
|
|
|
|
|
|
fn number_of_frames_to_generate(character_level: usize) -> usize {
|
|
if character_level <= 10 {
|
|
4
|
|
}
|
|
else if character_level <= 25 {
|
|
6
|
|
}
|
|
else if character_level <= 42 {
|
|
7
|
|
}
|
|
else {
|
|
8
|
|
}
|
|
}
|
|
|
|
fn number_of_barriers_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
|
|
}
|
|
}
|
|
|
|
fn number_of_units_to_generate(character_level: usize) -> usize {
|
|
if character_level <= 10 {
|
|
0
|
|
}
|
|
else if character_level <= 25 {
|
|
3
|
|
}
|
|
else if character_level <= 42 {
|
|
5
|
|
}
|
|
else {
|
|
6
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ArmorShop<R: Rng + SeedableRng> {
|
|
frame: FrameTable,
|
|
barrier: BarrierTable,
|
|
unit: UnitTable,
|
|
rng: R,
|
|
}
|
|
|
|
impl<R: Rng + SeedableRng> Default for ArmorShop<R> {
|
|
fn default() -> ArmorShop<R> {
|
|
ArmorShop {
|
|
frame: load_frame_table(),
|
|
barrier: load_barrier_table(),
|
|
unit: load_unit_table(),
|
|
rng: R::from_entropy(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<R: Rng + SeedableRng> ArmorShop<R> {
|
|
fn generate_frame_list(&mut self, character_level: usize) -> Vec<ArmorShopItem> {
|
|
let tier = self.frame.frame.iter()
|
|
.filter(|t| t.level <= character_level)
|
|
.last()
|
|
.cloned()
|
|
.unwrap();
|
|
|
|
let frame_choice = WeightedIndex::new(tier.item.iter().map(|f| f.probability)).unwrap();
|
|
let slot_choice = WeightedIndex::new(self.frame.slot_rate.iter().map(|sr| sr.probability)).unwrap();
|
|
(0..number_of_frames_to_generate(character_level))
|
|
.map(|_| {
|
|
let frame_detail = tier.item.get(frame_choice.sample(&mut self.rng)).unwrap();
|
|
let slot = self.frame.slot_rate.get(slot_choice.sample(&mut self.rng)).unwrap();
|
|
|
|
ArmorShopItem::Frame(Armor {
|
|
armor: frame_detail.item,
|
|
dfp: 0,
|
|
evp: 0,
|
|
slots: slot.slot as u8,
|
|
})
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn generate_barrier_list(&mut self, character_level: usize) -> Vec<ArmorShopItem> {
|
|
let tier = self.barrier.barrier.iter()
|
|
.filter(|t| t.level <= character_level)
|
|
.last()
|
|
.cloned()
|
|
.unwrap();
|
|
|
|
let barrier_choice = WeightedIndex::new(tier.item.iter().map(|b| b.probability)).unwrap();
|
|
(0..number_of_barriers_to_generate(character_level))
|
|
.map(|_| {
|
|
let barrier_detail = tier.item.get(barrier_choice.sample(&mut self.rng)).unwrap();
|
|
|
|
ArmorShopItem::Barrier(Shield {
|
|
shield: barrier_detail.item,
|
|
dfp: 0,
|
|
evp: 0,
|
|
})
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn generate_unit_list(&mut self, character_level: usize) -> Vec<ArmorShopItem> {
|
|
self.unit.unit.iter()
|
|
.filter(|t| t.level <= character_level)
|
|
.last()
|
|
.cloned()
|
|
.map(|tier| {
|
|
let unit_choice = WeightedIndex::new(tier.item.iter().map(|u| u.probability)).unwrap();
|
|
(0..number_of_units_to_generate(character_level))
|
|
.map(|_| {
|
|
let unit_detail = tier.item.get(unit_choice.sample(&mut self.rng)).unwrap();
|
|
|
|
ArmorShopItem::Unit(Unit {
|
|
unit: unit_detail.item,
|
|
modifier: None,
|
|
})
|
|
})
|
|
.collect()
|
|
})
|
|
.unwrap_or_else(Vec::new)
|
|
}
|
|
|
|
pub fn generate_armor_list(&mut self, character_level: usize) -> Vec<ArmorShopItem> {
|
|
self.generate_frame_list(character_level).into_iter()
|
|
.chain(self.generate_barrier_list(character_level))
|
|
.chain(self.generate_unit_list(character_level))
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_loading_tool_shop() {
|
|
ArmorShop::<rand_chacha::ChaCha20Rng>::default();
|
|
}
|
|
|
|
#[test]
|
|
fn test_generating_some_armor() {
|
|
let mut fs = ArmorShop::<rand_chacha::ChaCha20Rng>::default();
|
|
for i in 0..200 {
|
|
fs.generate_armor_list(i);
|
|
}
|
|
}
|
|
}
|