#![allow(dead_code, unused_must_use)] // TODO: there is some structure duplication that occurs here: // the rare and box tables instantiate their own copies of the // generic drop tables as they need them to apply their modifiers // to their drops mod drop_table; mod rare_drop_table; mod generic_weapon; mod generic_armor; mod generic_shield; mod generic_unit; mod tool_table; mod tech_table; mod box_drop_table; use std::collections::HashMap; use std::fs::File; use std::path::PathBuf; use std::io::Read; use serde::{Serialize, Deserialize}; use rand::{Rng, SeedableRng}; use crate::ship::monster::MonsterType; use crate::ship::room::{Difficulty, Episode}; use crate::ship::map::MapArea; use crate::entity::character::SectionID; use crate::ship::drops::generic_weapon::GenericWeaponTable; use crate::ship::drops::generic_armor::GenericArmorTable; use crate::ship::drops::generic_shield::GenericShieldTable; use crate::ship::drops::generic_unit::GenericUnitTable; use crate::ship::drops::tool_table::ToolTable; use crate::ship::drops::rare_drop_table::RareDropTable; use crate::ship::drops::box_drop_table::BoxDropTable; use crate::ship::map::MapObject; use crate::entity::item::{weapon, armor, shield, unit, mag, tool, tech}; fn data_file_path(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> PathBuf { let mut path = PathBuf::from("data/drops/"); path.push(episode.to_string()); path.push(difficulty.to_string().to_lowercase()); path.push(section_id.to_string().to_lowercase()); path.push(filename); path } pub fn load_data_file(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> T { let path = data_file_path(episode, difficulty, section_id, filename); let mut f = File::open(path).unwrap(); let mut s = String::new(); f.read_to_string(&mut s); toml::from_str::(s.as_str()).unwrap() } #[derive(Debug, Serialize, Deserialize, Copy, Clone)] pub enum MonsterDropType { #[serde(rename = "weapon")] Weapon, #[serde(rename = "armor")] Armor, #[serde(rename = "shield")] Shield, #[serde(rename = "unit")] Unit, #[serde(rename = "none")] None, } #[derive(Debug, Serialize, Deserialize, Copy, Clone)] pub struct MonsterDropStats { pub dar: u32, pub drop_type: MonsterDropType, pub min_meseta: u32, pub max_meseta: u32, } #[derive(Clone, Debug, PartialEq)] pub enum ItemDropType { Weapon(weapon::Weapon), Armor(armor::Armor), Shield(shield::Shield), Unit(unit::Unit), Tool(tool::Tool), //Tools(Vec), TechniqueDisk(tech::TechniqueDisk), Mag(mag::Mag), Meseta(u32), } #[derive(Clone, Debug)] pub struct ItemDrop { pub map_area: MapArea, pub x: f32, pub y: f32, pub z: f32, pub item: ItemDropType, } pub struct DropTable { monster_stats: HashMap, rare_table: RareDropTable, weapon_table: GenericWeaponTable, armor_table: GenericArmorTable, shield_table: GenericShieldTable, unit_table: GenericUnitTable, tool_table: ToolTable, box_table: BoxDropTable, rng: R, } impl DropTable { pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable { let monster_stats: HashMap = load_data_file(episode, difficulty, section_id, "monster_dar.toml"); DropTable { monster_stats: monster_stats.into_iter().map(|(m, s)| (m.parse().unwrap(), s)).collect(), rare_table: RareDropTable::new(episode, difficulty, section_id), weapon_table: GenericWeaponTable::new(episode, difficulty, section_id), armor_table: GenericArmorTable::new(episode, difficulty, section_id), shield_table: GenericShieldTable::new(episode, difficulty, section_id), unit_table: GenericUnitTable::new(episode, difficulty, section_id), tool_table: ToolTable::new(episode, difficulty, section_id), box_table: BoxDropTable::new(episode, difficulty, section_id), rng: R::from_entropy(), } } fn generate_meseta(&mut self, monster: &MonsterDropStats) -> Option { Some(ItemDropType::Meseta(self.rng.gen_range(monster.min_meseta, monster.max_meseta + 1))) } fn generate_typed_drop(&mut self, map_area: &MapArea, monster: &MonsterDropStats) -> Option { match monster.drop_type { MonsterDropType::Weapon => self.weapon_table.get_drop(map_area, &mut self.rng), MonsterDropType::Armor => self.armor_table.get_drop(map_area, &mut self.rng), MonsterDropType::Shield => self.shield_table.get_drop(map_area, &mut self.rng), MonsterDropType::Unit => self.unit_table.get_drop(map_area, &mut self.rng), MonsterDropType::None => None, } } pub fn get_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option { let monster_stat = *self.monster_stats.get(monster)?; let drop_anything = self.rng.gen_range(0, 100); if drop_anything > monster_stat.dar { return None; } if let Some(item) = self.rare_table.get_drop(map_area, &monster, &mut self.rng) { return Some(item); } let drop_type = self.rng.gen_range(0, 3); match drop_type { 0 => { self.generate_meseta(&monster_stat) }, 1 => { self.tool_table.get_drop(map_area, &mut self.rng) }, 2 => { self.generate_typed_drop(map_area, &monster_stat) }, _ => panic!() } } pub fn get_box_drop(&mut self, map_area: &MapArea, object: &MapObject) -> Option { self.box_table.get_drop(map_area, object, &mut self.rng) } } #[cfg(test)] mod test { use super::*; use rand::seq::IteratorRandom; #[test] fn test_initializing_drop_table() { let mut rng = rand_chacha::ChaCha20Rng::from_entropy(); let episode = vec![Episode::One, Episode::Two].into_iter().choose(&mut rng).unwrap(); let difficulty = vec![Difficulty::Normal, Difficulty::Hard, Difficulty::VeryHard, Difficulty::Ultimate] .into_iter().choose(&mut rng).unwrap(); let section_id = vec![SectionID::Viridia, SectionID::Greenill, SectionID::Skyly, SectionID::Bluefull, SectionID::Purplenum, SectionID::Pinkal, SectionID::Redria, SectionID::Oran, SectionID::Yellowboze, SectionID::Whitill] .into_iter().choose(&mut rng).unwrap(); DropTable::::new(episode, difficulty, section_id); } }