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.9 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 crate::entity::item::ItemDetail;
use crate::entity::item::armor::{Armor, ArmorType};
use crate::entity::item::shield::{Shield, ShieldType};
use crate::entity::item::unit::{Unit, UnitType};
use crate::ship::shops::ShopItem;
use crate::ship::item_stats::{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 as u8,
})
},
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).into_iter())
.chain(self.generate_unit_list(character_level).into_iter())
.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);
}
}
}