infinite-fusion 0.2.0

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

#[derive(Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(test, serde(deny_unknown_fields))]
pub struct Item {
    #[serde(rename = "@id")]
    pub id: String,
    #[serde(rename = "@id_number")]
    pub id_number: u16,
    #[serde(rename = "@real_name", deserialize_with = "as_transparent")]
    pub real_name: String,
    #[serde(rename = "@real_name_plural", deserialize_with = "as_transparent")]
    pub real_name_plural: String,
    #[serde(rename = "@pocket")]
    pub pocket: u8,
    #[serde(rename = "@price")]
    pub price: u32,
    #[serde(rename = "@real_description", deserialize_with = "as_transparent")]
    pub real_description: String,
    #[serde(rename = "@field_use")]
    pub field_use: u8,
    #[serde(rename = "@battle_use")]
    pub battle_use: u8,
    #[serde(rename = "@type")]
    pub ty: u8,
    #[serde(rename = "@move")]
    pub mov: Option<String>,
}

/// [`Deserialize`] implementation for `items.dat`
#[derive(Debug, Clone, Serialize)]
pub struct ItemDex(pub Vec<Item>);

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

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

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

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

    use super::*;

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

    #[test]
    fn deserialize_items_dat_vec() {
        let PathTracked(dex) = reikland::from_bytes::<PathTracked<ItemDex>>(ITEMS_DAT)
            .unwrap_or_else(|e| panic!("items.dat failed to deserialize: {e}"));

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

    #[test]
    fn deserialize_items_dat_map() {
        let PathTracked(dex) = reikland::from_bytes::<PathTracked<ItemMap>>(ITEMS_DAT)
            .unwrap_or_else(|e| panic!("items.dat failed to deserialize: {e}"));

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

    fn assert_repel(i: &Item) {
        assert_eq!(i.id, "REPEL");
        assert_eq!(i.id_number, 1);
        assert_eq!(i.real_name, "Repel");
        assert_eq!(i.real_name_plural, "Repels");
        assert_eq!(i.pocket, 1);
        assert_eq!(i.price, 300);
        assert_eq!(i.field_use, 2);
        assert_eq!(i.battle_use, 0);
        assert_eq!(i.ty, 0);
        assert!(i.mov.is_none());
    }
}