Skip to main content

infinite_fusion/
lib.rs

1pub mod abilities;
2pub mod encounters;
3pub mod items;
4pub mod moves;
5pub mod species;
6pub mod types;
7
8use reikland::Transparent;
9use reikland::deserializer::MarshalDeserializeError;
10use serde::{Deserialize, Deserializer};
11
12pub use abilities::{AbilityDex, AbilityMap};
13pub use encounters::{EncounterDex, EncounterMap};
14pub use items::{ItemDex, ItemMap};
15pub use moves::{MoveDex, MoveMap};
16pub use species::{SpeciesDex, SpeciesMap};
17pub use types::{TypeDex, TypeMap};
18
19use crate::{
20    species::{Species, base_stats::BaseStats},
21    types::fuse_types,
22};
23
24/// Deserialize T as maybe being wrapped in some other type (such as an ivar)
25pub(crate) fn as_transparent<'de, D, T>(deserializer: D) -> Result<T, D::Error>
26where
27    D: Deserializer<'de>,
28    T: Deserialize<'de>,
29{
30    Transparent::<T>::deserialize(deserializer).map(|t| t.0)
31}
32
33#[derive(Debug)]
34pub enum FromDatError {
35    Io(std::io::Error),
36    Marshal(MarshalDeserializeError),
37}
38
39impl std::fmt::Display for FromDatError {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        match self {
42            Self::Io(e) => write!(f, "io error: {e}"),
43            Self::Marshal(e) => write!(f, "marshal error: {e}"),
44        }
45    }
46}
47
48impl std::error::Error for FromDatError {
49    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
50        match self {
51            Self::Io(e) => Some(e),
52            Self::Marshal(e) => Some(e),
53        }
54    }
55}
56
57impl From<std::io::Error> for FromDatError {
58    fn from(e: std::io::Error) -> Self {
59        Self::Io(e)
60    }
61}
62
63impl From<MarshalDeserializeError> for FromDatError {
64    fn from(e: MarshalDeserializeError) -> Self {
65        Self::Marshal(e)
66    }
67}
68
69/// Generates `from_bytes` / `from_file` / `from_path` inherent methods on the given type.
70macro_rules! impl_from_dat {
71    ($t:ty) => {
72        impl $t {
73            pub fn from_bytes(bytes: &[u8]) -> Result<Self, FromDatError> {
74                Ok(reikland::from_bytes(bytes)?)
75            }
76
77            pub fn from_file(file: &mut std::fs::File) -> Result<Self, FromDatError> {
78                let mut bytes = Vec::new();
79                std::io::Read::read_to_end(file, &mut bytes)?;
80                Self::from_bytes(&bytes)
81            }
82
83            pub fn from_path(path: impl AsRef<std::path::Path>) -> Result<Self, FromDatError> {
84                let bytes = std::fs::read(path)?;
85                Self::from_bytes(&bytes)
86            }
87        }
88    };
89}
90
91impl_from_dat!(AbilityDex);
92impl_from_dat!(AbilityMap);
93impl_from_dat!(EncounterDex);
94impl_from_dat!(EncounterMap);
95impl_from_dat!(ItemDex);
96impl_from_dat!(ItemMap);
97impl_from_dat!(MoveDex);
98impl_from_dat!(MoveMap);
99impl_from_dat!(SpeciesDex);
100impl_from_dat!(SpeciesMap);
101impl_from_dat!(TypeDex);
102impl_from_dat!(TypeMap);
103
104/// The result of fusing two pokemon via [`fuse`]
105#[derive(Debug, Clone)]
106pub struct FusedPokemon {
107    pub head_id: u32,
108    pub body_id: u32,
109    pub head_name: String,
110    pub body_name: String,
111    pub type1: String,
112    pub type2: Option<String>,
113    pub abilities: Vec<String>,
114    pub hidden_abilities: Vec<String>,
115    pub moves: Vec<(u8, String)>,
116    pub egg_moves: Vec<String>,
117    pub tutor_moves: Vec<String>,
118    pub base_stats: BaseStats,
119}
120
121/// Fuse two pokemon. Note the implementation is naive due to the data structures being naive. This may or may not scale well across the full list of pokemon fusions.
122/// If performance is a large concern, this crate is likely a poor fit and you may wish to take my implementations from FusionFinder Desktop (if I have released it when you're reading this)
123pub fn fuse(head: &Species, body: &Species) -> FusedPokemon {
124    use itertools::Itertools;
125    let (type1, type2) = fuse_types(
126        &head.type1,
127        head.type2.as_ref(),
128        &body.type1,
129        body.type2.as_ref(),
130    );
131
132    fn combine_string_vec(head: &[String], body: &[String]) -> Vec<String> {
133        head.iter().chain(body.iter()).unique().cloned().collect()
134    }
135
136    let abilities = combine_string_vec(&head.abilities, &body.abilities);
137    let hidden_abilities = combine_string_vec(&head.hidden_abilities, &body.hidden_abilities);
138
139    let mut moves = head
140        .moves
141        .iter()
142        .chain(body.moves.iter())
143        .cloned()
144        .collect::<Vec<_>>();
145
146    moves.sort_unstable_by_key(|(level, _)| *level);
147
148    let egg_moves = combine_string_vec(&head.egg_moves, &body.egg_moves);
149    let tutor_moves = combine_string_vec(&head.tutor_moves, &body.tutor_moves);
150
151    let base_stats = head.base_stats.fuse(&body.base_stats);
152
153    FusedPokemon {
154        head_id: head.id_number,
155        body_id: body.id_number,
156        head_name: head.real_name.clone(),
157        body_name: body.real_name.clone(),
158        type1: type1.clone(),
159        type2: type2.cloned(),
160        abilities,
161        hidden_abilities,
162        moves,
163        egg_moves,
164        tutor_moves,
165        base_stats,
166    }
167}
168
169#[cfg(test)]
170pub(crate) mod test {
171    use serde::Deserializer;
172
173    pub(crate) struct PathTracked<T>(pub(crate) T);
174
175    impl<'de, T> serde::Deserialize<'de> for PathTracked<T>
176    where
177        T: serde::Deserialize<'de>,
178    {
179        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
180        where
181            D: Deserializer<'de>,
182        {
183            match serde_path_to_error::deserialize::<D, T>(deserializer) {
184                Ok(value) => Ok(PathTracked(value)),
185                Err(err) => {
186                    let path = err.path().to_string();
187                    let inner = err.into_inner();
188                    Err(serde::de::Error::custom(format!("at `{path}`: {inner}")))
189                }
190            }
191        }
192    }
193}