use std::collections::HashMap; use rand::Rng; use serde::{Serialize, Deserialize}; use entity::item::weapon::{Weapon, WeaponType}; use entity::item::armor::{Armor, ArmorType}; use entity::item::shield::{Shield, ShieldType}; use entity::item::unit::{Unit, UnitType}; use entity::item::tool::{Tool, ToolType}; use entity::item::mag::{Mag, MagType}; use entity::character::SectionID; use maps::monster::MonsterType; use maps::room::{Difficulty, Episode}; use maps::area::MapArea; use crate::{ItemDropType, load_data_file}; use crate::generic_weapon::AttributeTable; use crate::generic_armor::GenericArmorTable; use crate::generic_shield::GenericShieldTable; type ItemParseFn = Box Option>; #[derive(Debug, Copy, Clone)] pub enum RareDropItem { Weapon(WeaponType), Armor(ArmorType), Shield(ShieldType), Unit(UnitType), Tool(ToolType), Mag(MagType) } impl RareDropItem { pub fn from_string(name: String) -> RareDropItem { let parse_funcs: [ItemParseFn; 6] = [ Box::new(|i| Some(RareDropItem::Weapon(str::parse::(i).ok()?))), Box::new(|i| Some(RareDropItem::Armor(str::parse::(i).ok()?))), Box::new(|i| Some(RareDropItem::Shield(str::parse::(i).ok()?))), Box::new(|i| Some(RareDropItem::Unit(str::parse::(i).ok()?))), Box::new(|i| Some(RareDropItem::Tool(str::parse::(i).ok()?))), Box::new(|i| Some(RareDropItem::Mag(str::parse::(i).ok()?))), ]; for parse in parse_funcs.iter() { if let Some (k) = parse(&name) { return k } } panic!() } } pub struct RareDropRate { pub rate: f32, pub item: RareDropItem } #[derive(Debug, Serialize, Deserialize)] pub struct RareDropConfigEntity { pub rate: f32, pub item: String, } pub struct RareDropTable { rates: HashMap>, attribute_table: AttributeTable, armor_stats: GenericArmorTable, shield_stats: GenericShieldTable, } fn load_default_monster_rates(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> HashMap> { let cfg: HashMap> = load_data_file(episode, difficulty, section_id, "rare_rate.toml"); cfg.into_iter() .map(|(monster, drops)| { let monster = monster.parse().unwrap(); let drops = drops.into_iter().map(|drop| { RareDropRate { rate: drop.rate, item: RareDropItem::from_string(drop.item), } }).collect(); (monster, drops) }).collect() } impl RareDropTable { pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> RareDropTable { RareDropTable { rates: load_default_monster_rates(episode, difficulty, section_id), attribute_table: AttributeTable::new(episode, difficulty, section_id), armor_stats: GenericArmorTable::new(episode, difficulty, section_id), shield_stats: GenericShieldTable::new(episode, difficulty, section_id), } } pub fn builder() -> RareDropTableBuilder { RareDropTableBuilder { rates: None, attribute_table: None, armor_stats: None, shield_stats: None, } } pub fn apply_item_stats(&self, map_area: &MapArea, item: RareDropItem, rng: &mut R) -> ItemDropType { match item { RareDropItem::Weapon(weapon) => { ItemDropType::Weapon(Weapon { weapon, special: None, grind: 0, attrs: self.attribute_table.generate_rare_attributes(map_area, rng), tekked: false, }) }, RareDropItem::Armor(armor) => { ItemDropType::Armor(Armor { armor, dfp: self.armor_stats.dfp_modifier(&armor, rng) as u8, evp: self.armor_stats.evp_modifier(&armor, rng) as u8, slots: self.armor_stats.slots(map_area, rng) as u8, }) }, RareDropItem::Shield(shield) => { ItemDropType::Shield(Shield { shield, dfp: self.shield_stats.dfp_modifier(&shield, rng) as u8, evp: self.shield_stats.evp_modifier(&shield, rng) as u8, }) }, RareDropItem::Unit(unit) => { ItemDropType::Unit(Unit { unit, modifier: None, }) }, RareDropItem::Tool(tool) => { ItemDropType::Tool(Tool { tool, }) }, RareDropItem::Mag(_mag) => { ItemDropType::Mag(Mag::baby_mag(rng.gen_range(0, 18))) } } } pub fn get_drop(&self, map_area: &MapArea, monster: &MonsterType, rng: &mut R) -> Option { self.rates.get(monster) .and_then(|drop_rates| { drop_rates.iter() .filter_map(|drop_rate| { let rand: f32 = rng.gen(); if rand < drop_rate.rate { Some(self.apply_item_stats(map_area, drop_rate.item, rng)) } else { None } }).next() }) } } pub struct RareDropTableBuilder { rates: Option>>, attribute_table: Option, armor_stats: Option, shield_stats: Option, } // TODO: add the rest of these later I just need these ones right now impl RareDropTableBuilder { pub fn rates(mut self, rates: HashMap>) -> RareDropTableBuilder { self.rates = Some(rates); self } #[must_use] pub fn rate(mut self, monster_type: MonsterType, drop_rate: RareDropRate) -> RareDropTableBuilder { match &mut self.rates { Some(rates) => { rates.entry(monster_type) .or_insert(Vec::new()) .push(drop_rate); }, None => { let mut rates = HashMap::default(); rates.insert(monster_type, vec![drop_rate]); self.rates = Some(rates); } } self } pub fn build(self, episode: Episode, difficulty: Difficulty, section_id: SectionID) -> RareDropTable { RareDropTable { rates: self.rates.unwrap_or_else(|| load_default_monster_rates(episode, difficulty, section_id)), attribute_table: self.attribute_table.unwrap_or_else(|| AttributeTable::new(episode, difficulty, section_id)), armor_stats: self.armor_stats.unwrap_or_else(|| GenericArmorTable::new(episode, difficulty, section_id)), shield_stats: self.shield_stats.unwrap_or_else(|| GenericShieldTable::new(episode, difficulty, section_id)), } } }