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.

298 lines
11 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
1 year ago
5 years ago
5 years ago
5 years ago
5 years ago
2 years ago
  1. #![allow(dead_code, unused_must_use)]
  2. // TODO: there is some structure duplication that occurs here:
  3. // the rare and box tables instantiate their own copies of the
  4. // generic drop tables as they need them to apply their modifiers
  5. // to their drops
  6. pub mod rare_drop_table;
  7. mod generic_weapon;
  8. mod generic_armor;
  9. mod generic_shield;
  10. mod generic_unit;
  11. mod tool_table;
  12. mod tech_table;
  13. mod box_drop_table;
  14. use std::collections::HashMap;
  15. use std::fs::File;
  16. use std::path::PathBuf;
  17. use std::io::Read;
  18. use serde::{Serialize, Deserialize};
  19. use rand::{Rng, SeedableRng};
  20. use maps::monster::MonsterType;
  21. use maps::room::{Difficulty, Episode};
  22. use maps::area::MapArea;
  23. use entity::character::SectionID;
  24. use crate::generic_weapon::GenericWeaponTable;
  25. use crate::generic_armor::GenericArmorTable;
  26. use crate::generic_shield::GenericShieldTable;
  27. use crate::generic_unit::GenericUnitTable;
  28. use crate::tool_table::ToolTable;
  29. use crate::rare_drop_table::RareDropTable;
  30. use crate::box_drop_table::BoxDropTable;
  31. use maps::object::MapObject;
  32. use entity::item::{ItemType, weapon, armor, shield, unit, mag, tool, tech, esweapon};
  33. fn data_file_path(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> PathBuf {
  34. let mut path = PathBuf::from("data/drops/");
  35. path.push(episode.to_string());
  36. path.push(difficulty.to_string().to_lowercase());
  37. path.push(section_id.to_string().to_lowercase());
  38. path.push(filename);
  39. path
  40. }
  41. pub fn load_data_file<T: serde::de::DeserializeOwned>(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> T {
  42. let path = data_file_path(episode, difficulty, section_id, filename);
  43. let mut f = File::open(path).unwrap();
  44. let mut s = String::new();
  45. f.read_to_string(&mut s);
  46. toml::from_str::<T>(s.as_str()).unwrap()
  47. }
  48. #[derive(Debug, Serialize, Deserialize, Copy, Clone)]
  49. pub enum MonsterDropType {
  50. #[serde(rename = "weapon")]
  51. Weapon,
  52. #[serde(rename = "armor")]
  53. Armor,
  54. #[serde(rename = "shield")]
  55. Shield,
  56. #[serde(rename = "unit")]
  57. Unit,
  58. #[serde(rename = "none")]
  59. None,
  60. }
  61. #[derive(Debug, Serialize, Deserialize, Copy, Clone)]
  62. pub struct MonsterDropStats {
  63. pub dar: u32,
  64. pub drop_type: MonsterDropType,
  65. pub min_meseta: u32,
  66. pub max_meseta: u32,
  67. }
  68. #[derive(Clone, Debug, PartialEq, Eq)]
  69. pub enum ItemDropType {
  70. Weapon(weapon::Weapon),
  71. Armor(armor::Armor),
  72. Shield(shield::Shield),
  73. Unit(unit::Unit),
  74. Tool(tool::Tool),
  75. //Tools(Vec<tool::Tool>),
  76. TechniqueDisk(tech::TechniqueDisk),
  77. Mag(mag::Mag),
  78. Meseta(u32),
  79. }
  80. impl ItemDropType {
  81. pub fn parse_item_from_bytes(data: [u8; 16]) -> Option<ItemDropType> {
  82. let item_type = weapon::WeaponType::parse_type([data[0],data[1],data[2]]).map(ItemType::Weapon)
  83. .or_else(|_| armor::ArmorType::parse_type([data[0],data[1],data[2]]).map(ItemType::Armor))
  84. .or_else(|_| shield::ShieldType::parse_type([data[0],data[1],data[2]]).map(ItemType::Shield))
  85. .or_else(|_| unit::UnitType::parse_type([data[0],data[1],data[2]]).map(ItemType::Unit))
  86. .or_else(|_| mag::MagType::parse_type([data[0],data[1],data[2]]).map(ItemType::Mag))
  87. .or_else(|_| tool::ToolType::parse_type([data[0],data[1],data[2]]).map(ItemType::Tool))
  88. .or_else(|_| esweapon::ESWeaponType::parse_type([data[0],data[1],data[2]]).map(ItemType::ESWeapon)).ok()?;
  89. match item_type {
  90. ItemType::Weapon(_w) => Some(ItemDropType::Weapon(weapon::Weapon::from_bytes(data).ok()?)),
  91. ItemType::Armor(_a) => Some(ItemDropType::Armor(armor::Armor::from_bytes(data).ok()?)),
  92. ItemType::Shield(_s) => Some(ItemDropType::Shield(shield::Shield::from_bytes(data).ok()?)),
  93. ItemType::Unit(_u) => Some(ItemDropType::Unit(unit::Unit::from_bytes(data).ok()?)),
  94. ItemType::Mag(_m) => Some(ItemDropType::Mag(mag::Mag::from_bytes(data).ok()?)),
  95. ItemType::Tool(_t) => Some(ItemDropType::Tool(tool::Tool::from_bytes(data).ok()?)),
  96. _ => None,
  97. }
  98. }
  99. }
  100. #[derive(Clone, Debug)]
  101. pub struct ItemDrop {
  102. pub map_area: MapArea,
  103. pub x: f32,
  104. pub y: f32,
  105. pub z: f32,
  106. pub item: ItemDropType,
  107. }
  108. pub trait DropTable {
  109. fn get_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option<ItemDropType>;
  110. fn get_box_drop(&mut self, map_area: &MapArea, object: &MapObject) -> Option<ItemDropType>;
  111. }
  112. pub struct StandardDropTable {
  113. monster_stats: HashMap<MonsterType, MonsterDropStats>,
  114. rare_table: RareDropTable,
  115. weapon_table: GenericWeaponTable,
  116. armor_table: GenericArmorTable,
  117. shield_table: GenericShieldTable,
  118. unit_table: GenericUnitTable,
  119. tool_table: ToolTable,
  120. box_table: BoxDropTable,
  121. rng: rand_chacha::ChaCha20Rng,
  122. }
  123. impl StandardDropTable {
  124. #[allow(clippy::new_ret_no_self)]
  125. pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> Box<dyn DropTable + Send + Sync> {
  126. let monster_stats: HashMap<String, MonsterDropStats> = load_data_file(episode, difficulty, section_id, "monster_dar.toml");
  127. Box::new(StandardDropTable {
  128. monster_stats: monster_stats.into_iter().map(|(m, s)| (m.parse().unwrap(), s)).collect(),
  129. rare_table: RareDropTable::new(episode, difficulty, section_id),
  130. weapon_table: GenericWeaponTable::new(episode, difficulty, section_id),
  131. armor_table: GenericArmorTable::new(episode, difficulty, section_id),
  132. shield_table: GenericShieldTable::new(episode, difficulty, section_id),
  133. unit_table: GenericUnitTable::new(episode, difficulty, section_id),
  134. tool_table: ToolTable::new(episode, difficulty, section_id),
  135. box_table: BoxDropTable::new(episode, difficulty, section_id),
  136. rng: rand_chacha::ChaCha20Rng::from_entropy(),
  137. })
  138. }
  139. pub fn builder() -> DropTableBuilder {
  140. DropTableBuilder {
  141. monster_stats: None,
  142. rare_table: None,
  143. weapon_table: None,
  144. armor_table: None,
  145. shield_table: None,
  146. unit_table: None,
  147. tool_table: None,
  148. box_table: None,
  149. rng: None,
  150. }
  151. }
  152. fn generate_meseta(&mut self, monster: &MonsterDropStats) -> Option<ItemDropType> {
  153. Some(ItemDropType::Meseta(self.rng.gen_range(monster.min_meseta, monster.max_meseta + 1)))
  154. }
  155. fn generate_typed_drop(&mut self, map_area: &MapArea, monster: &MonsterDropStats) -> Option<ItemDropType> {
  156. match monster.drop_type {
  157. MonsterDropType::Weapon => self.weapon_table.get_drop(map_area, &mut self.rng),
  158. MonsterDropType::Armor => self.armor_table.get_drop(map_area, &mut self.rng),
  159. MonsterDropType::Shield => self.shield_table.get_drop(map_area, &mut self.rng),
  160. MonsterDropType::Unit => self.unit_table.get_drop(map_area, &mut self.rng),
  161. MonsterDropType::None => None,
  162. }
  163. }
  164. }
  165. impl DropTable for StandardDropTable {
  166. fn get_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option<ItemDropType> {
  167. let monster_stat = *self.monster_stats.get(monster)?;
  168. let drop_anything = self.rng.gen_range(0, 100);
  169. if drop_anything > monster_stat.dar {
  170. return None;
  171. }
  172. if let Some(item) = self.rare_table.get_drop(map_area, monster, &mut self.rng) {
  173. return Some(item);
  174. }
  175. let drop_type = self.rng.gen_range(0, 3);
  176. match drop_type {
  177. 0 => {
  178. self.generate_meseta(&monster_stat)
  179. },
  180. 1 => {
  181. self.tool_table.get_drop(map_area, &mut self.rng)
  182. },
  183. 2 => {
  184. self.generate_typed_drop(map_area, &monster_stat)
  185. },
  186. _ => panic!()
  187. }
  188. }
  189. fn get_box_drop(&mut self, map_area: &MapArea, object: &MapObject) -> Option<ItemDropType> {
  190. self.box_table.get_drop(map_area, object, &mut self.rng)
  191. }
  192. }
  193. pub struct DropTableBuilder {
  194. monster_stats: Option<HashMap<MonsterType, MonsterDropStats>>,
  195. rare_table: Option<RareDropTable>,
  196. weapon_table: Option<GenericWeaponTable>,
  197. armor_table: Option<GenericArmorTable>,
  198. shield_table: Option<GenericShieldTable>,
  199. unit_table: Option<GenericUnitTable>,
  200. tool_table: Option<ToolTable>,
  201. box_table: Option<BoxDropTable>,
  202. rng: Option<rand_chacha::ChaCha20Rng>,
  203. }
  204. // TODO: add the rest of these later I just need these ones right now
  205. impl DropTableBuilder {
  206. #[must_use]
  207. pub fn monster_stats(mut self, monster_stats: HashMap<MonsterType, MonsterDropStats>) -> DropTableBuilder {
  208. self.monster_stats = Some(monster_stats);
  209. self
  210. }
  211. #[must_use]
  212. pub fn monster_stat(mut self, monster_type: MonsterType, drop_stats: MonsterDropStats) -> DropTableBuilder {
  213. match &mut self.monster_stats {
  214. Some(monster_stats) => {
  215. monster_stats.insert(monster_type, drop_stats);
  216. },
  217. None => {
  218. let mut monster_stats = HashMap::default();
  219. monster_stats.insert(monster_type, drop_stats);
  220. self.monster_stats = Some(monster_stats);
  221. }
  222. }
  223. self
  224. }
  225. #[must_use]
  226. pub fn rare_table(mut self, rare_table: RareDropTable) -> DropTableBuilder {
  227. self.rare_table = Some(rare_table);
  228. self
  229. }
  230. pub fn build(self, episode: Episode, difficulty: Difficulty, section_id: SectionID) -> Box<dyn DropTable + Send + Sync> {
  231. Box::new(StandardDropTable {
  232. monster_stats: self.monster_stats.unwrap_or_else(|| {
  233. let monster_stats: HashMap<String, MonsterDropStats> = load_data_file(episode, difficulty, section_id, "monster_dar.toml");
  234. monster_stats.into_iter().map(|(m, s)| (m.parse().unwrap(), s)).collect()
  235. }),
  236. rare_table: self.rare_table.unwrap_or_else(|| RareDropTable::new(episode, difficulty, section_id)),
  237. weapon_table: self.weapon_table.unwrap_or_else(|| GenericWeaponTable::new(episode, difficulty, section_id)),
  238. armor_table: self.armor_table.unwrap_or_else(|| GenericArmorTable::new(episode, difficulty, section_id)),
  239. shield_table: self.shield_table.unwrap_or_else(|| GenericShieldTable::new(episode, difficulty, section_id)),
  240. unit_table: self.unit_table.unwrap_or_else(|| GenericUnitTable::new(episode, difficulty, section_id)),
  241. tool_table: self.tool_table.unwrap_or_else(|| ToolTable::new(episode, difficulty, section_id)),
  242. box_table: self.box_table.unwrap_or_else(|| BoxDropTable::new(episode, difficulty, section_id)),
  243. rng: self.rng.unwrap_or_else(rand_chacha::ChaCha20Rng::from_entropy),
  244. })
  245. }
  246. }
  247. #[cfg(test)]
  248. mod test {
  249. use super::*;
  250. use rand::seq::IteratorRandom;
  251. #[test]
  252. fn test_initializing_drop_table() {
  253. let mut rng = rand_chacha::ChaCha20Rng::from_entropy();
  254. let episode = vec![Episode::One, Episode::Two].into_iter().choose(&mut rng).unwrap();
  255. let difficulty = vec![Difficulty::Normal, Difficulty::Hard, Difficulty::VeryHard, Difficulty::Ultimate]
  256. .into_iter().choose(&mut rng).unwrap();
  257. let section_id = vec![SectionID::Viridia, SectionID::Greenill, SectionID::Skyly, SectionID::Bluefull, SectionID::Purplenum,
  258. SectionID::Pinkal, SectionID::Redria, SectionID::Oran, SectionID::Yellowboze, SectionID::Whitill]
  259. .into_iter().choose(&mut rng).unwrap();
  260. DropTable::new(episode, difficulty, section_id);
  261. }
  262. }