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.

197 lines
6.7 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 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. mod drop_table;
  7. mod rare_drop_table;
  8. mod generic_weapon;
  9. mod generic_armor;
  10. mod generic_shield;
  11. mod generic_unit;
  12. mod tool_table;
  13. mod tech_table;
  14. mod box_drop_table;
  15. use std::collections::HashMap;
  16. use std::fs::File;
  17. use std::path::PathBuf;
  18. use std::io::Read;
  19. use serde::{Serialize, Deserialize};
  20. use rand::{Rng, SeedableRng};
  21. use crate::ship::monster::MonsterType;
  22. use crate::ship::room::{Difficulty, Episode};
  23. use crate::ship::map::MapArea;
  24. use crate::entity::character::SectionID;
  25. use crate::ship::drops::generic_weapon::GenericWeaponTable;
  26. use crate::ship::drops::generic_armor::GenericArmorTable;
  27. use crate::ship::drops::generic_shield::GenericShieldTable;
  28. use crate::ship::drops::generic_unit::GenericUnitTable;
  29. use crate::ship::drops::tool_table::ToolTable;
  30. use crate::ship::drops::rare_drop_table::RareDropTable;
  31. use crate::ship::drops::box_drop_table::BoxDropTable;
  32. use crate::ship::map::MapObject;
  33. use crate::entity::item::{weapon, armor, shield, unit, mag, tool, tech};
  34. fn data_file_path(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> PathBuf {
  35. let mut path = PathBuf::from("data/drops/");
  36. path.push(episode.to_string());
  37. path.push(difficulty.to_string().to_lowercase());
  38. path.push(section_id.to_string().to_lowercase());
  39. path.push(filename);
  40. path
  41. }
  42. pub fn load_data_file<T: serde::de::DeserializeOwned>(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> T {
  43. let path = data_file_path(episode, difficulty, section_id, filename);
  44. let mut f = File::open(path).unwrap();
  45. let mut s = String::new();
  46. f.read_to_string(&mut s);
  47. toml::from_str::<T>(s.as_str()).unwrap()
  48. }
  49. #[derive(Debug, Serialize, Deserialize, Copy, Clone)]
  50. pub enum MonsterDropType {
  51. #[serde(rename = "weapon")]
  52. Weapon,
  53. #[serde(rename = "armor")]
  54. Armor,
  55. #[serde(rename = "shield")]
  56. Shield,
  57. #[serde(rename = "unit")]
  58. Unit,
  59. #[serde(rename = "none")]
  60. None,
  61. }
  62. #[derive(Debug, Serialize, Deserialize, Copy, Clone)]
  63. pub struct MonsterDropStats {
  64. pub dar: u32,
  65. pub drop_type: MonsterDropType,
  66. pub min_meseta: u32,
  67. pub max_meseta: u32,
  68. }
  69. #[derive(Clone, Debug, PartialEq)]
  70. pub enum ItemDropType {
  71. Weapon(weapon::Weapon),
  72. Armor(armor::Armor),
  73. Shield(shield::Shield),
  74. Unit(unit::Unit),
  75. Tool(tool::Tool),
  76. //Tools(Vec<tool::Tool>),
  77. TechniqueDisk(tech::TechniqueDisk),
  78. Mag(mag::Mag),
  79. Meseta(u32),
  80. }
  81. #[derive(Clone, Debug)]
  82. pub struct ItemDrop {
  83. pub map_area: MapArea,
  84. pub x: f32,
  85. pub y: f32,
  86. pub z: f32,
  87. pub item: ItemDropType,
  88. }
  89. pub struct DropTable<R: Rng + SeedableRng> {
  90. monster_stats: HashMap<MonsterType, MonsterDropStats>,
  91. rare_table: RareDropTable,
  92. weapon_table: GenericWeaponTable,
  93. armor_table: GenericArmorTable,
  94. shield_table: GenericShieldTable,
  95. unit_table: GenericUnitTable,
  96. tool_table: ToolTable,
  97. box_table: BoxDropTable,
  98. rng: R,
  99. }
  100. impl<R: Rng + SeedableRng> DropTable<R> {
  101. pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable<R> {
  102. let monster_stats: HashMap<String, MonsterDropStats> = load_data_file(episode, difficulty, section_id, "monster_dar.toml");
  103. DropTable {
  104. monster_stats: monster_stats.into_iter().map(|(m, s)| (m.parse().unwrap(), s)).collect(),
  105. rare_table: RareDropTable::new(episode, difficulty, section_id),
  106. weapon_table: GenericWeaponTable::new(episode, difficulty, section_id),
  107. armor_table: GenericArmorTable::new(episode, difficulty, section_id),
  108. shield_table: GenericShieldTable::new(episode, difficulty, section_id),
  109. unit_table: GenericUnitTable::new(episode, difficulty, section_id),
  110. tool_table: ToolTable::new(episode, difficulty, section_id),
  111. box_table: BoxDropTable::new(episode, difficulty, section_id),
  112. rng: R::from_entropy(),
  113. }
  114. }
  115. fn generate_meseta(&mut self, monster: &MonsterDropStats) -> Option<ItemDropType> {
  116. Some(ItemDropType::Meseta(self.rng.gen_range(monster.min_meseta, monster.max_meseta + 1)))
  117. }
  118. fn generate_typed_drop(&mut self, map_area: &MapArea, monster: &MonsterDropStats) -> Option<ItemDropType> {
  119. match monster.drop_type {
  120. MonsterDropType::Weapon => self.weapon_table.get_drop(map_area, &mut self.rng),
  121. MonsterDropType::Armor => self.armor_table.get_drop(map_area, &mut self.rng),
  122. MonsterDropType::Shield => self.shield_table.get_drop(map_area, &mut self.rng),
  123. MonsterDropType::Unit => self.unit_table.get_drop(map_area, &mut self.rng),
  124. MonsterDropType::None => None,
  125. }
  126. }
  127. pub fn get_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option<ItemDropType> {
  128. let monster_stat = *self.monster_stats.get(monster)?;
  129. let drop_anything = self.rng.gen_range(0, 100);
  130. if drop_anything > monster_stat.dar {
  131. return None;
  132. }
  133. if let Some(item) = self.rare_table.get_drop(map_area, &monster, &mut self.rng) {
  134. return Some(item);
  135. }
  136. let drop_type = self.rng.gen_range(0, 3);
  137. match drop_type {
  138. 0 => {
  139. self.generate_meseta(&monster_stat)
  140. },
  141. 1 => {
  142. self.tool_table.get_drop(map_area, &mut self.rng)
  143. },
  144. 2 => {
  145. self.generate_typed_drop(map_area, &monster_stat)
  146. },
  147. _ => panic!()
  148. }
  149. }
  150. pub fn get_box_drop(&mut self, map_area: &MapArea, object: &MapObject) -> Option<ItemDropType> {
  151. self.box_table.get_drop(map_area, object, &mut self.rng)
  152. }
  153. }
  154. #[cfg(test)]
  155. mod test {
  156. use super::*;
  157. use rand::seq::IteratorRandom;
  158. #[test]
  159. fn test_initializing_drop_table() {
  160. let mut rng = rand_chacha::ChaCha20Rng::from_entropy();
  161. let episode = vec![Episode::One, Episode::Two].into_iter().choose(&mut rng).unwrap();
  162. let difficulty = vec![Difficulty::Normal, Difficulty::Hard, Difficulty::VeryHard, Difficulty::Ultimate]
  163. .into_iter().choose(&mut rng).unwrap();
  164. let section_id = vec![SectionID::Viridia, SectionID::Greenill, SectionID::Skyly, SectionID::Bluefull, SectionID::Purplenum,
  165. SectionID::Pinkal, SectionID::Redria, SectionID::Oran, SectionID::Yellowboze, SectionID::Whitill]
  166. .into_iter().choose(&mut rng).unwrap();
  167. DropTable::<rand_chacha::ChaCha20Rng>::new(episode, difficulty, section_id);
  168. }
  169. }