use indexmap::IndexMap;
use reikland::RbObject;
use serde::{Deserialize, Deserializer, Serialize};
#[derive(Debug, Deserialize, Clone, Copy, Serialize, Hash, PartialEq, Eq)]
pub enum EncounterMethod {
Cave,
GoodRod,
Land,
Land1,
Land2,
Land3,
LandDay,
LandMorning,
LandNight,
OldRod,
RockSmash,
SuperRod,
Water,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct EncounterSlot {
pub probability: u8,
pub species: String,
pub min_level: u8,
pub max_level: u8,
}
impl<'de> Deserialize<'de> for EncounterSlot {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let (probability, species, min_level, max_level) =
<(u8, String, u8, u8)>::deserialize(deserializer)?;
Ok(Self {
probability,
species,
min_level,
max_level,
})
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(test, serde(deny_unknown_fields))]
pub struct Encounter {
#[serde(rename = "@id")]
pub id: String,
#[serde(rename = "@map")]
pub map: u16,
#[serde(rename = "@version")]
pub version: u8,
#[serde(rename = "@step_chances")]
pub step_chances: IndexMap<EncounterMethod, u8>,
#[serde(rename = "@types")]
pub types: IndexMap<EncounterMethod, Vec<EncounterSlot>>,
}
#[derive(Debug, Clone, Serialize)]
pub struct EncounterMap(pub IndexMap<String, Encounter>);
impl<'de> serde::Deserialize<'de> for EncounterMap {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
IndexMap::<String, RbObject<Encounter>>::deserialize(deserializer)
.map(|m| EncounterMap(m.into_iter().map(|(k, v)| (k, v.fields)).collect()))
}
}
#[derive(Debug, Clone, Serialize)]
pub struct EncounterDex(pub Vec<Encounter>);
impl<'de> serde::Deserialize<'de> for EncounterDex {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
EncounterMap::deserialize(deserializer)
.map(|EncounterMap(m)| EncounterDex(m.into_values().collect()))
}
}
#[cfg(test)]
mod tests {
use crate::test::PathTracked;
use super::*;
const ENCOUNTERS_DAT: &[u8] = include_bytes!("../InfiniteFusion/Data/encounters.dat");
const ENCOUNTERS_RANDOMIZED_DAT: &[u8] =
include_bytes!("../InfiniteFusion/Data/encounters_randomized.dat");
const ENCOUNTERS_REMIX_DAT: &[u8] =
include_bytes!("../InfiniteFusion/Data/encounters_remix.dat");
fn parse_dex(bytes: &[u8]) -> EncounterDex {
let PathTracked(dex) = reikland::from_bytes::<PathTracked<EncounterDex>>(bytes)
.unwrap_or_else(|e| panic!("encounters.dat failed to deserialize: {e}"));
dex
}
fn parse_map(bytes: &[u8]) -> EncounterMap {
let PathTracked(dex) = reikland::from_bytes::<PathTracked<EncounterMap>>(bytes)
.unwrap_or_else(|e| panic!("encounters.dat failed to deserialize: {e}"));
dex
}
#[test]
fn deserialize_encounters_dat_vec() {
let dex = parse_dex(ENCOUNTERS_DAT);
assert!(!dex.0.is_empty());
assert_first_standard(dex.0.first().unwrap());
}
#[test]
fn deserialize_encounters_dat_map() {
let dex = parse_map(ENCOUNTERS_DAT);
assert_first_standard(&dex.0["8_0"]);
}
#[test]
fn deserialize_encounters_randomized_dat_vec() {
let dex = parse_dex(ENCOUNTERS_RANDOMIZED_DAT);
assert_first_randomized(dex.0.first().unwrap());
}
#[test]
fn deserialize_encounters_randomized_dat_map() {
let dex = parse_map(ENCOUNTERS_RANDOMIZED_DAT);
assert_first_randomized(&dex.0["1_0"]);
}
#[test]
fn deserialize_encounters_remix_dat_vec() {
let dex = parse_dex(ENCOUNTERS_REMIX_DAT);
assert_first_remix(dex.0.first().unwrap());
}
#[test]
fn deserialize_encounters_remix_dat_map() {
let dex = parse_map(ENCOUNTERS_REMIX_DAT);
assert_first_remix(&dex.0["1_0"]);
}
fn assert_first_standard(e: &Encounter) {
assert_eq!(e.id, "8_0");
assert_eq!(e.map, 8);
assert_eq!(e.version, 0);
assert_eq!(e.step_chances[&EncounterMethod::Land], 10);
assert_eq!(e.step_chances[&EncounterMethod::Water], 2);
let land = &e.types[&EncounterMethod::Land];
assert_eq!(land.len(), 8);
assert_eq!(
land[0],
EncounterSlot {
probability: 20,
species: "ABRA".to_string(),
min_level: 8,
max_level: 12,
}
);
}
fn assert_first_randomized(e: &Encounter) {
assert_eq!(e.id, "1_0");
assert_eq!(e.map, 1);
let water = &e.types[&EncounterMethod::Water];
assert_eq!(
water[0],
EncounterSlot {
probability: 60,
species: "SMOOCHUM".to_string(),
min_level: 5,
max_level: 40,
}
);
}
fn assert_first_remix(e: &Encounter) {
assert_eq!(e.id, "1_0");
assert_eq!(e.map, 1);
let water = &e.types[&EncounterMethod::Water];
assert_eq!(
water[0],
EncounterSlot {
probability: 30,
species: "BRUXISH".to_string(),
min_level: 5,
max_level: 40,
}
);
}
}