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.

367 lines
20 KiB

3 years ago
  1. // TOOD: `pub(super) for most of these?`
  2. use std::io::{Read};
  3. use std::collections::HashMap;
  4. use byteorder::{LittleEndian, ReadBytesExt};
  5. use thiserror::Error;
  6. use crate::ship::monster::MonsterType;
  7. use crate::ship::room::Episode;
  8. use crate::ship::map::*;
  9. use rand::{Rng, SeedableRng};
  10. use serde::{Serialize, Deserialize};
  11. use crate::ship::drops::{load_rare_monster_file};
  12. #[derive(Debug, Copy, Clone)]
  13. pub struct RawMapEnemy {
  14. id: u32,
  15. _unknown1: u16,
  16. pub children: u16,
  17. _map_area: u16,
  18. _unknown4: u16,
  19. _section: u16,
  20. _wave_idd: u16,
  21. _wave_id: u32,
  22. _x: f32,
  23. _y: f32,
  24. _z: f32,
  25. _xrot: u32,
  26. _yrot: u32,
  27. _zrot: u32,
  28. _field1: u32,
  29. field2: u32,
  30. _field3: u32,
  31. _field4: u32,
  32. _field5: u32,
  33. skin: u32,
  34. _field6: u32
  35. }
  36. impl RawMapEnemy {
  37. pub fn from_byte_stream<R: Read>(cursor: &mut R) -> Result<RawMapEnemy, std::io::Error> {
  38. Ok(RawMapEnemy {
  39. id: cursor.read_u32::<LittleEndian>()?, // TODO: is this really u32? shiny monsters are referred to by u16 in the client
  40. _unknown1: cursor.read_u16::<LittleEndian>()?,
  41. children: cursor.read_u16::<LittleEndian>()?,
  42. _map_area: cursor.read_u16::<LittleEndian>()?,
  43. _unknown4: cursor.read_u16::<LittleEndian>()?,
  44. _section: cursor.read_u16::<LittleEndian>()?,
  45. _wave_idd: cursor.read_u16::<LittleEndian>()?,
  46. _wave_id: cursor.read_u32::<LittleEndian>()?,
  47. _x: cursor.read_f32::<LittleEndian>()?,
  48. _y: cursor.read_f32::<LittleEndian>()?,
  49. _z: cursor.read_f32::<LittleEndian>()?,
  50. _xrot: cursor.read_u32::<LittleEndian>()?,
  51. _yrot: cursor.read_u32::<LittleEndian>()?,
  52. _zrot: cursor.read_u32::<LittleEndian>()?,
  53. _field1: cursor.read_u32::<LittleEndian>()?,
  54. field2: cursor.read_u32::<LittleEndian>()?,
  55. _field3: cursor.read_u32::<LittleEndian>()?,
  56. _field4: cursor.read_u32::<LittleEndian>()?,
  57. _field5: cursor.read_u32::<LittleEndian>()?,
  58. skin: cursor.read_u32::<LittleEndian>()?,
  59. _field6: cursor.read_u32::<LittleEndian>()?,
  60. })
  61. }
  62. }
  63. #[derive(Error, Debug)]
  64. #[error("")]
  65. pub enum MapEnemyError {
  66. UnknownEnemyId(u32),
  67. MapAreaError(#[from] MapAreaError),
  68. }
  69. // making this `pub type` doesn't allow `impl`s to be defined?
  70. #[derive(Clone, Debug, Serialize, Deserialize)]
  71. pub struct RareMonsterAppearTable {
  72. pub appear_rate: HashMap<MonsterType, f32>,
  73. }
  74. impl RareMonsterAppearTable {
  75. pub fn new(episode: Episode) -> RareMonsterAppearTable {
  76. let cfg: HashMap<String, f32> = load_rare_monster_file(episode);
  77. let appear_rates: HashMap<MonsterType, f32> = cfg
  78. .into_iter()
  79. .map(|(monster, rate)| {
  80. let monster: MonsterType = monster.parse().unwrap(); // TODO: don't unwrap!
  81. let appear_rate = rate;
  82. (monster, appear_rate)
  83. })
  84. .collect();
  85. RareMonsterAppearTable {
  86. appear_rate: appear_rates,
  87. }
  88. }
  89. pub fn roll_appearance(&self, monster: &MonsterType) -> bool {
  90. if rand_chacha::ChaChaRng::from_entropy().gen::<f32>() < *self.appear_rate.get(monster).unwrap_or(&0.0f32) {
  91. return true
  92. }
  93. false
  94. }
  95. }
  96. #[derive(Debug, Copy, Clone)]
  97. pub struct MapEnemy {
  98. pub monster: MonsterType,
  99. pub map_area: MapArea,
  100. _hp: u32,
  101. // TODO: other stats from battleparam
  102. pub player_hit: [bool; 4],
  103. pub dropped_item: bool,
  104. pub gave_exp: bool,
  105. pub shiny: bool,
  106. }
  107. impl MapEnemy {
  108. pub fn from_raw(enemy: RawMapEnemy, episode: &Episode, map_area: &MapArea /*, battleparam */) -> Result<MapEnemy, MapEnemyError> {
  109. // TODO: rare enemies ep1-4, tower lilys, event rappies, ult variants?
  110. // TODO: check what "skin" actually does. some unexpected enemies have many (panarms, slimes, lilys)
  111. let monster = match map_area {
  112. MapArea::Forest1 | MapArea::Forest2 | MapArea::Dragon |
  113. MapArea::Caves1 | MapArea::Caves2 | MapArea::Caves3 | MapArea::DeRolLe |
  114. MapArea::Mines1 | MapArea::Mines2 | MapArea::VolOpt |
  115. MapArea::Ruins1 | MapArea::Ruins2 | MapArea::Ruins3 | MapArea::DarkFalz => {
  116. match (enemy, episode) {
  117. (RawMapEnemy {id: 64, skin: 0, ..}, _) => MonsterType::Hildebear,
  118. (RawMapEnemy {id: 64, skin: 1, ..}, _) => MonsterType::Hildeblue,
  119. (RawMapEnemy {id: 65, skin: 0, ..}, _) => MonsterType::RagRappy,
  120. (RawMapEnemy {id: 65, skin: 1, ..}, _) => MonsterType::AlRappy,
  121. (RawMapEnemy {id: 66, ..}, _) => MonsterType::Monest,
  122. (RawMapEnemy {id: 67, field2: 0, ..}, _) => MonsterType::SavageWolf,
  123. (RawMapEnemy {id: 67, ..}, _) => MonsterType::BarbarousWolf,
  124. (RawMapEnemy {id: 68, skin: 0, ..}, _) => MonsterType::Booma,
  125. (RawMapEnemy {id: 68, skin: 1, ..}, _) => MonsterType::Gobooma,
  126. (RawMapEnemy {id: 68, skin: 2, ..}, _) => MonsterType::Gigobooma,
  127. (RawMapEnemy {id: 96, ..}, _) => MonsterType::GrassAssassin,
  128. (RawMapEnemy {id: 97, ..}, _) => MonsterType::PoisonLily,
  129. // (RawMapEnemy {id: 97, skin: 0, ..}, _) => MonsterType::PoisonLily,
  130. // (RawMapEnemy {id: 97, skin: 1, ..}, _) => MonsterType::NarLily,
  131. (RawMapEnemy {id: 98, ..}, _) => MonsterType::NanoDragon,
  132. (RawMapEnemy {id: 99, skin: 0, ..}, _) => MonsterType::EvilShark,
  133. (RawMapEnemy {id: 99, skin: 1, ..}, _) => MonsterType::PalShark,
  134. (RawMapEnemy {id: 99, skin: 2, ..}, _) => MonsterType::GuilShark,
  135. (RawMapEnemy {id: 100, ..}, _) => MonsterType::PofuillySlime,
  136. // (RawMapEnemy {id: 100, skin: 0, ..}, _) => MonsterType::PofuillySlime,
  137. // (RawMapEnemy {id: 100, skin: 1, ..}, _) => MonsterType::PouillySlime,
  138. // (RawMapEnemy {id: 100, skin: 2, ..}, _) => MonsterType::PofuillySlime,
  139. (RawMapEnemy {id: 101, ..}, _) => MonsterType::PanArms,
  140. (RawMapEnemy {id: 128, skin: 0, ..}, _) => MonsterType::Dubchic,
  141. (RawMapEnemy {id: 128, skin: 1, ..}, _) => MonsterType::Gillchic,
  142. (RawMapEnemy {id: 129, ..}, _) => MonsterType::Garanz,
  143. (RawMapEnemy {id: 130, field2: 0, ..}, _) => MonsterType::SinowBeat,
  144. (RawMapEnemy {id: 130, ..}, _) => MonsterType::SinowGold,
  145. (RawMapEnemy {id: 131, ..}, _) => MonsterType::Canadine,
  146. (RawMapEnemy {id: 132, ..}, _) => MonsterType::Canane,
  147. (RawMapEnemy {id: 133, ..}, _) => MonsterType::Dubwitch,
  148. (RawMapEnemy {id: 160, ..}, _) => MonsterType::Delsaber,
  149. (RawMapEnemy {id: 161, ..}, _) => MonsterType::ChaosSorcerer,
  150. (RawMapEnemy {id: 162, ..}, _) => MonsterType::DarkGunner,
  151. (RawMapEnemy {id: 163, ..}, _) => MonsterType::DeathGunner,
  152. (RawMapEnemy {id: 164, ..}, _) => MonsterType::ChaosBringer,
  153. (RawMapEnemy {id: 165, ..}, _) => MonsterType::DarkBelra,
  154. (RawMapEnemy {id: 166, skin: 0, ..}, _) => MonsterType::Dimenian,
  155. (RawMapEnemy {id: 166, skin: 1, ..}, _) => MonsterType::LaDimenian,
  156. (RawMapEnemy {id: 166, skin: 2, ..}, _) => MonsterType::SoDimenian,
  157. (RawMapEnemy {id: 167, ..}, _) => MonsterType::Bulclaw,
  158. (RawMapEnemy {id: 168, ..}, _) => MonsterType::Claw,
  159. (RawMapEnemy {id: 192, ..}, Episode::One) => MonsterType::Dragon,
  160. (RawMapEnemy {id: 193, ..}, _) => MonsterType::DeRolLe,
  161. (RawMapEnemy {id: 194, ..}, _) => MonsterType::VolOptPartA,
  162. (RawMapEnemy {id: 197, ..}, _) => MonsterType::VolOpt,
  163. (RawMapEnemy {id: 200, ..}, _) => MonsterType::DarkFalz,
  164. _ => return Err(MapEnemyError::UnknownEnemyId(enemy.id))
  165. }
  166. },
  167. MapArea::VrTempleAlpha | MapArea::VrTempleBeta | MapArea::BarbaRay |
  168. MapArea::VrSpaceshipAlpha | MapArea::VrSpaceshipBeta | MapArea::GolDragon |
  169. MapArea::JungleAreaNorth | MapArea::JungleAreaEast | MapArea::Mountain | MapArea::Seaside | MapArea::SeasideNight | MapArea::Cca | MapArea::GalGryphon |
  170. MapArea::SeabedUpper | MapArea::SeabedLower | MapArea::OlgaFlow => {
  171. match (enemy, episode) {
  172. (RawMapEnemy {id: 64, skin: 0, ..}, _) => MonsterType::Hildebear,
  173. (RawMapEnemy {id: 64, skin: 1, ..}, _) => MonsterType::Hildeblue,
  174. (RawMapEnemy {id: 65, skin: 0, ..}, _) => MonsterType::RagRappy,
  175. (RawMapEnemy {id: 65, skin: 1, ..}, _) => MonsterType::EventRappy,
  176. (RawMapEnemy {id: 66, ..}, _) => MonsterType::Monest,
  177. (RawMapEnemy {id: 67, field2: 0, ..}, _) => MonsterType::SavageWolf,
  178. (RawMapEnemy {id: 67, ..}, _) => MonsterType::BarbarousWolf,
  179. (RawMapEnemy {id: 96, ..}, _) => MonsterType::GrassAssassin,
  180. (RawMapEnemy {id: 97, skin: 0, ..}, _) => MonsterType::PoisonLily,
  181. (RawMapEnemy {id: 97, skin: 1, ..}, _) => MonsterType::NarLily,
  182. (RawMapEnemy {id: 101, ..}, _) => MonsterType::PanArms,
  183. (RawMapEnemy {id: 128, skin: 0, ..}, _) => MonsterType::Dubchic,
  184. (RawMapEnemy {id: 128, skin: 1, ..}, _) => MonsterType::Gillchic,
  185. (RawMapEnemy {id: 129, ..}, _) => MonsterType::Garanz,
  186. (RawMapEnemy {id: 133, ..}, _) => MonsterType::Dubwitch,
  187. (RawMapEnemy {id: 160, ..}, _) => MonsterType::Delsaber,
  188. (RawMapEnemy {id: 161, ..}, _) => MonsterType::ChaosSorcerer,
  189. (RawMapEnemy {id: 165, ..}, _) => MonsterType::DarkBelra,
  190. (RawMapEnemy {id: 166, skin: 0, ..}, _) => MonsterType::Dimenian,
  191. (RawMapEnemy {id: 166, skin: 1, ..}, _) => MonsterType::LaDimenian,
  192. (RawMapEnemy {id: 166, skin: 2, ..}, _) => MonsterType::SoDimenian,
  193. (RawMapEnemy {id: 192, ..}, Episode::Two) => MonsterType::GalGryphon,
  194. (RawMapEnemy {id: 202, ..}, _) => MonsterType::OlgaFlow,
  195. (RawMapEnemy {id: 203, ..}, _) => MonsterType::BarbaRay,
  196. (RawMapEnemy {id: 204, ..}, _) => MonsterType::GolDragon,
  197. (RawMapEnemy {id: 212, skin: 0, ..}, _) => MonsterType::SinowBerill,
  198. (RawMapEnemy {id: 212, skin: 1, ..}, _) => MonsterType::SinowSpigell,
  199. (RawMapEnemy {id: 213, skin: 0, ..}, _) => MonsterType::Merillia,
  200. (RawMapEnemy {id: 213, skin: 1, ..}, _) => MonsterType::Meriltas,
  201. (RawMapEnemy {id: 214, skin: 0, ..}, _) => MonsterType::Mericarol,
  202. (RawMapEnemy {id: 214, skin: 1, ..}, _) => MonsterType::Merikle,
  203. (RawMapEnemy {id: 214, skin: 2, ..}, _) => MonsterType::Mericus,
  204. (RawMapEnemy {id: 215, skin: 0, ..}, _) => MonsterType::UlGibbon,
  205. (RawMapEnemy {id: 215, skin: 1, ..}, _) => MonsterType::ZolGibbon,
  206. (RawMapEnemy {id: 216, ..}, _) => MonsterType::Gibbles,
  207. (RawMapEnemy {id: 217, ..}, _) => MonsterType::Gee,
  208. (RawMapEnemy {id: 218, ..}, _) => MonsterType::GiGue,
  209. (RawMapEnemy {id: 219, ..}, _) => MonsterType::Deldepth,
  210. (RawMapEnemy {id: 220, ..}, _) => MonsterType::Delbiter,
  211. (RawMapEnemy {id: 221, skin: 0, ..}, _) => MonsterType::Dolmolm,
  212. (RawMapEnemy {id: 221, skin: 1, ..}, _) => MonsterType::Dolmdarl,
  213. (RawMapEnemy {id: 222, ..}, _) => MonsterType::Morfos,
  214. (RawMapEnemy {id: 223, ..}, _) => MonsterType::Recobox,
  215. (RawMapEnemy {id: 224, skin: 0, ..}, _) => MonsterType::SinowZoa,
  216. (RawMapEnemy {id: 224, skin: 1, ..}, _) => MonsterType::SinowZele,
  217. (RawMapEnemy {id: 224, ..}, _) => MonsterType::Epsilon,
  218. (RawMapEnemy {id: 225, ..}, _) => MonsterType::IllGill,
  219. _ => return Err(MapEnemyError::UnknownEnemyId(enemy.id))
  220. }
  221. },
  222. MapArea::Tower => {
  223. match (enemy, episode) {
  224. (RawMapEnemy {id: 97, ..}, _) => MonsterType::DelLily,
  225. (RawMapEnemy {id: 214, skin: 0, ..}, _) => MonsterType::Mericarol,
  226. (RawMapEnemy {id: 214, skin: 1, ..}, _) => MonsterType::Merikle,
  227. (RawMapEnemy {id: 214, skin: 2, ..}, _) => MonsterType::Mericus,
  228. (RawMapEnemy {id: 216, ..}, _) => MonsterType::Gibbles,
  229. (RawMapEnemy {id: 218, ..}, _) => MonsterType::GiGue,
  230. (RawMapEnemy {id: 220, ..}, _) => MonsterType::Delbiter,
  231. (RawMapEnemy {id: 223, ..}, _) => MonsterType::Recobox,
  232. (RawMapEnemy {id: 224, ..}, _) => MonsterType::Epsilon,
  233. (RawMapEnemy {id: 225, ..}, _) => MonsterType::IllGill,
  234. _ => return Err(MapEnemyError::UnknownEnemyId(enemy.id))
  235. }
  236. },
  237. MapArea::CraterEast | MapArea::CraterWest | MapArea::CraterSouth | MapArea::CraterNorth | MapArea::CraterInterior => {
  238. match (enemy, episode) {
  239. (RawMapEnemy {id: 65, skin: 0, ..}, Episode::Four) => MonsterType::SandRappyCrater,
  240. (RawMapEnemy {id: 65, skin: 1, ..}, Episode::Four) => MonsterType::DelRappyCrater,
  241. (RawMapEnemy {id: 272, ..}, _) => MonsterType::Astark,
  242. (RawMapEnemy {id: 273, field2: 0, ..}, _) => MonsterType::SatelliteLizardCrater,
  243. (RawMapEnemy {id: 273, ..}, _) => MonsterType::YowieCrater,
  244. (RawMapEnemy {id: 276, skin: 0, ..}, _) => MonsterType::ZuCrater,
  245. (RawMapEnemy {id: 276, skin: 1, ..}, _) => MonsterType::PazuzuCrater,
  246. (RawMapEnemy {id: 277, skin: 0, ..}, _) => MonsterType::Boota,
  247. (RawMapEnemy {id: 277, skin: 1, ..}, _) => MonsterType::ZeBoota,
  248. (RawMapEnemy {id: 277, skin: 2, ..}, _) => MonsterType::BaBoota,
  249. (RawMapEnemy {id: 278, skin: 0, ..}, _) => MonsterType::Dorphon,
  250. (RawMapEnemy {id: 278, skin: 1, ..}, _) => MonsterType::DorphonEclair,
  251. _ => return Err(MapEnemyError::UnknownEnemyId(enemy.id))
  252. }
  253. },
  254. MapArea::SubDesert1 | MapArea::SubDesert2 | MapArea::SubDesert3 | MapArea::SaintMillion => {
  255. match (enemy, episode) {
  256. (RawMapEnemy {id: 65, skin: 0, ..}, Episode::Four) => MonsterType::SandRappyDesert,
  257. (RawMapEnemy {id: 65, skin: 1, ..}, Episode::Four) => MonsterType::DelRappyDesert,
  258. (RawMapEnemy {id: 273, field2: 0, ..}, _) => MonsterType::SatelliteLizardDesert,
  259. (RawMapEnemy {id: 273, ..}, _) => MonsterType::YowieDesert,
  260. (RawMapEnemy {id: 274, skin: 0, ..}, _) => MonsterType::MerissaA,
  261. (RawMapEnemy {id: 274, skin: 1, ..}, _) => MonsterType::MerissaAA,
  262. (RawMapEnemy {id: 275, ..}, _) => MonsterType::Girtablulu,
  263. (RawMapEnemy {id: 276, skin: 0, ..}, _) => MonsterType::ZuDesert,
  264. (RawMapEnemy {id: 276, skin: 1, ..}, _) => MonsterType::PazuzuDesert,
  265. (RawMapEnemy {id: 279, skin: 0, ..}, _) => MonsterType::Goran,
  266. (RawMapEnemy {id: 279, skin: 1, ..}, _) => MonsterType::PyroGoran,
  267. (RawMapEnemy {id: 279, skin: 2, ..}, _) => MonsterType::GoranDetonator,
  268. (RawMapEnemy {id: 281, skin: 0, ..}, _) => MonsterType::SaintMillion,
  269. (RawMapEnemy {id: 281, skin: 1, ..}, _) => MonsterType::Shambertin, // TODO: don't guess the skin
  270. (RawMapEnemy {id: 281, skin: 2, ..}, _) => MonsterType::Kondrieu, // TODO: don't guess the skin
  271. _ => return Err(MapEnemyError::UnknownEnemyId(enemy.id))
  272. }
  273. },
  274. _ => return Err(MapEnemyError::UnknownEnemyId(enemy.id))
  275. };
  276. Ok(MapEnemy {
  277. monster,
  278. map_area: *map_area,
  279. _hp: 0,
  280. dropped_item: false,
  281. gave_exp: false,
  282. player_hit: [false; 4],
  283. shiny: false,
  284. })
  285. }
  286. pub fn new(monster: MonsterType, map_area: MapArea) -> MapEnemy {
  287. MapEnemy {
  288. monster,
  289. map_area,
  290. _hp: 0,
  291. dropped_item: false,
  292. gave_exp: false,
  293. player_hit: [false; 4],
  294. shiny: false,
  295. }
  296. }
  297. pub fn set_shiny(self) -> MapEnemy {
  298. MapEnemy {
  299. shiny: true,
  300. ..self
  301. }
  302. }
  303. pub fn has_rare_appearance(self) -> bool {
  304. matches!(self.monster,
  305. MonsterType::RagRappy | MonsterType::Hildebear |
  306. MonsterType::PoisonLily | MonsterType::PofuillySlime |
  307. MonsterType::SandRappyCrater | MonsterType::ZuCrater | MonsterType::Dorphon |
  308. MonsterType::SandRappyDesert | MonsterType::ZuDesert | MonsterType::MerissaA |
  309. MonsterType::SaintMillion | MonsterType::Shambertin
  310. )
  311. }
  312. /*
  313. TODO: distinguish between a `random` rare monster and a `set/guaranteed` rare monster? (does any acceptable quest even have this?)
  314. guaranteed rare monsters don't count towards the limit
  315. */
  316. pub fn set_rare_appearance(self) -> MapEnemy {
  317. match (self.monster, self.map_area.to_episode()) {
  318. (MonsterType::RagRappy, Episode::One) => {MapEnemy {monster: MonsterType::AlRappy, shiny:true, ..self}},
  319. (MonsterType::RagRappy, Episode::Two) => {MapEnemy {monster: MonsterType::EventRappy, shiny:true, ..self}},
  320. (MonsterType::Hildebear, _) => {MapEnemy {monster: MonsterType::Hildeblue, shiny:true, ..self}},
  321. (MonsterType::PoisonLily, _) => {MapEnemy {monster: MonsterType::NarLily, shiny:true, ..self}},
  322. (MonsterType::PofuillySlime, _) => {MapEnemy {monster: MonsterType::PouillySlime, shiny:true, ..self}},
  323. (MonsterType::SandRappyCrater, _) => {MapEnemy {monster: MonsterType::DelRappyCrater, shiny:true, ..self}},
  324. (MonsterType::ZuCrater, _) => {MapEnemy {monster: MonsterType::PazuzuCrater, shiny:true, ..self}},
  325. (MonsterType::Dorphon, _) => {MapEnemy {monster: MonsterType::DorphonEclair, shiny:true, ..self}},
  326. (MonsterType::SandRappyDesert, _) => {MapEnemy {monster: MonsterType::DelRappyDesert, shiny:true, ..self}},
  327. (MonsterType::ZuDesert, _) => {MapEnemy {monster: MonsterType::PazuzuDesert, shiny:true, ..self}},
  328. (MonsterType::MerissaA, _) => {MapEnemy {monster: MonsterType::MerissaAA, shiny:true, ..self}},
  329. (MonsterType::SaintMillion, _) => {MapEnemy {monster: MonsterType::Kondrieu, shiny:true, ..self}},
  330. (MonsterType::Shambertin, _) => {MapEnemy {monster: MonsterType::Kondrieu, shiny:true, ..self}},
  331. _ => {self},
  332. }
  333. }
  334. // in theory this should only be called on monsters we know can have rare types
  335. pub fn roll_appearance_for_mission(self, rare_monster_table: &RareMonsterAppearTable) -> MapEnemy {
  336. if rare_monster_table.roll_appearance(&self.monster) {
  337. return self.set_rare_appearance()
  338. }
  339. self
  340. }
  341. }