infinite-fusion 0.2.0

A simple crate for reading Pokemon Infinite Fusion's data
Documentation
use crate::{as_transparent, moves::flags::MoveFlags};
use indexmap::IndexMap;
use reikland::Ignored;
use serde::{Deserialize, Deserializer, Serialize};

pub mod flags;

#[derive(Debug, Clone, Copy, Deserialize, Serialize, Hash, PartialEq, Eq)]
pub enum MoveTarget {
    AllBattlers,
    AllNearFoes,
    AllNearOthers,
    BothSides,
    FoeSide,
    NearAlly,
    NearFoe,
    NearOther,
    None,
    Other,
    RandomNearFoe,
    User,
    UserAndAllies,
    UserOrNearAlly,
    UserSide,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(test, serde(deny_unknown_fields))]
pub struct Move {
    #[serde(rename = "@id")]
    pub id: String,
    #[serde(rename = "@id_number")]
    pub id_number: u16,
    #[serde(rename = "@real_name")]
    pub real_name: String,
    #[serde(rename = "@function_code")]
    pub function_code: String,
    #[serde(rename = "@base_damage")]
    pub base_damage: u8,
    #[serde(rename = "@type")]
    pub r#type: String,
    #[serde(rename = "@category")]
    pub category: u8,
    #[serde(rename = "@accuracy")]
    pub accuracy: u8,
    #[serde(rename = "@total_pp")]
    pub total_pp: u8,
    #[serde(rename = "@effect_chance")]
    pub effect_chance: u8,
    #[serde(rename = "@target")]
    pub target: MoveTarget,
    #[serde(rename = "@priority")]
    pub priority: i8,
    #[serde(rename = "@flags")]
    pub flags: MoveFlags,
    #[serde(rename = "@real_description", deserialize_with = "as_transparent")]
    pub real_description: String,
}

/// [`Deserialize`] implementation for `moves.dat`
#[derive(Debug, Clone, Serialize)]
pub struct MoveDex(pub Vec<Move>);

impl<'de> serde::Deserialize<'de> for MoveDex {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        reikland::DualKeyVec::<(Move, Ignored)>::deserialize(deserializer)
            .map(|reikland::DualKeyVec(mut d)| {
                d.sort_by_key(|(m, _)| m.id_number);
                d.into_iter().map(|(m, _)| m).collect()
            })
            .map(MoveDex)
    }
}

/// [`Deserialize`] implementation for `moves.dat`, indexable by key or value
#[derive(Debug, Clone, Serialize)]
pub struct MoveMap(pub IndexMap<String, Move>);

impl<'de> serde::Deserialize<'de> for MoveMap {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        MoveDex::deserialize(deserializer)
            .map(|MoveDex(v)| MoveMap(v.into_iter().map(|m| (m.id.clone(), m)).collect()))
    }
}

#[cfg(test)]
mod tests {
    use crate::test::PathTracked;

    use super::*;

    const MOVES_DAT: &[u8] = include_bytes!("../InfiniteFusion/Data/moves.dat");

    #[test]
    fn deserialize_moves_dat_vec() {
        let PathTracked(dex) = reikland::from_bytes::<PathTracked<MoveDex>>(MOVES_DAT)
            .unwrap_or_else(|e| panic!("moves.dat failed to deserialize: {e}"));

        assert!(!dex.0.is_empty());
        assert_megahorn(dex.0.first().unwrap());
    }

    #[test]
    fn deserialize_moves_dat_map() {
        let PathTracked(dex) = reikland::from_bytes::<PathTracked<MoveMap>>(MOVES_DAT)
            .unwrap_or_else(|e| panic!("moves.dat failed to deserialize: {e}"));

        assert!(!dex.0.is_empty());
        assert_megahorn(dex.0.first().unwrap().1);
    }

    fn assert_megahorn(m: &Move) {
        assert_eq!(m.id, "MEGAHORN");
        assert_eq!(m.id_number, 1);
        assert_eq!(m.real_name, "Megahorn");
        assert_eq!(m.function_code, "000");
        assert_eq!(m.base_damage, 120);
        assert_eq!(m.r#type, "BUG");
        assert_eq!(m.category, 0);
        assert_eq!(m.accuracy, 85);
        assert_eq!(m.total_pp, 10);
        assert_eq!(m.effect_chance, 0);
        assert_eq!(m.target, MoveTarget::NearOther);
        assert_eq!(m.priority, 0);
        assert!(m.flags.contains(MoveFlags::CONTACT));
        assert!(m.flags.contains(MoveFlags::CAN_PROTECT));
        assert!(m.flags.contains(MoveFlags::CAN_MIRROR_MOVE));
        assert!(m.flags.contains(MoveFlags::CAN_KINGS_ROCK));
    }
}