data/
lib.rs

1//! Data structures and logic about the DMW2 game data.
2//!
3//! This crate deals with the generic DWM2 game data, not a particular
4//! play through. Therefore when it refers to “monster name”, it
5//! refers to the name of the monster type (e.g. “Slime”), not the
6//! name you made up for your monsters.
7
8#![allow(non_snake_case)]
9
10use std::collections::HashMap;
11
12#[macro_use] extern crate error;
13pub mod monster;
14pub mod breed;
15pub mod xml;
16pub mod skill;
17
18use monster::{Monster, Family};
19use breed::Formula;
20use skill::Skill;
21use crate::error::Error;
22
23/// All DWM2 game data. This is the entry point of the whole library.
24#[derive(Default, Clone)]
25pub struct GameData
26{
27    /// Data about monsters
28    pub monster_data: monster::Info,
29    /// All breed formulae
30    pub breed_formulae: Vec<Formula>,
31    /// All skills
32    pub skills: HashMap<String, Skill>,
33}
34
35impl GameData
36{
37    /// Create a GameData from XML.
38    pub fn fromXML(x: &[u8]) -> Result<Self, Error>
39    {
40        let mut monster_data = monster::Info::default();
41        let mut breed_formulae = Vec::new();
42        let mut skills = HashMap::new();
43
44        let mut p = xml::Parser::new();
45        p.addTagHandler("families", |_, tag| {
46            monster_data = monster::Info::fromXML(tag)?;
47            Ok(())
48        });
49        p.addTagHandler("breed", |_, tag| {
50            breed_formulae.push(Formula::fromXML(tag)?);
51            Ok(())
52        });
53        p.addTagHandler("skill-data", |_, tag| {
54            let s = Skill::fromXML(tag)?;
55            skills.insert(s.name.clone(), s);
56            Ok(())
57        });
58        p.parse(x)?;
59        drop(p);
60
61        // Populate Skill::upgrade_to
62        let keys: Vec<String> = skills.keys().map(|k| k.to_owned()).collect();
63        for s in keys
64        {
65            if let Some(precursor) = skills[&s].upgrade_from.clone()
66            {
67                skills.get_mut(&precursor).ok_or_else(
68                    || rterr!("{} is upgraded from {}, which is not found.",
69                              s, precursor))?
70                    .upgrade_to = Some(s.to_owned());
71            }
72        }
73
74        Ok(Self { monster_data, breed_formulae, skills })
75    }
76
77    /// Find familiy by name.
78    pub fn family(&self, name: &str) -> Option<&Family>
79    {
80        self.monster_data.families.iter()
81            .find(|f| f.name == name)
82    }
83
84    /// Find monster by name.
85    pub fn monster(&self, name: &str) -> Option<&Monster>
86    {
87        self.monster_data.monsters.iter()
88            .find(|m| m.name == name)
89    }
90
91    /// Find skill by name.
92    pub fn skill(&self, name: &str) -> Option<&Skill>
93    {
94        self.skills.get(name)
95    }
96
97    /// Find all monsters having a skill natrually.
98    pub fn monstersWithSkill<'a>(&'a self, skill_name: &'a str) ->
99        impl Iterator<Item = &Monster> + 'a
100    {
101        self.monster_data.monsters.iter().filter(
102            move |m| m.skills.iter().any(|a| a == skill_name))
103    }
104
105    /// Find all monsters in a family.
106    pub fn monstersInFamily<'a>(&'a self, family: &'a Family) ->
107        impl Iterator<Item = &Monster> + 'a
108    {
109        self.monster_data.monsters.iter().filter(
110            |m| family.members.contains(&m.name))
111    }
112
113    /// Find all the formulae a monster or a family is used in.
114    pub fn usedInFormulae<'a>(&'a self, parent: &'a breed::Parent) ->
115        impl Iterator<Item = &Formula> + 'a
116    {
117        self.breed_formulae.iter().filter(
118            move |form| form.base.iter().find(|p| &p.parent == parent).is_some()
119                || form.mate.iter().find(|p| &p.parent == parent).is_some())
120    }
121
122    /// Find all the formulae that produces a specific monster.
123    pub fn breedFromFormulae<'a>(&'a self, offspring: &'a str) ->
124        impl Iterator<Item = &Formula> + 'a
125    {
126        self.breed_formulae.iter()
127            .filter(move |form| &form.offspring == offspring)
128    }
129
130    /// Find all the upgrades of `skill`. This includes skills that
131    /// are not the ultimate upgrades.
132    fn skillUpgrades<'a>(&'a self, skill: &'a Skill) -> Vec<&'a Skill>
133    {
134        let mut result: Vec<&Skill> = vec![skill];
135        if let Some(name) = &skill.upgrade_to
136        {
137            let upgrade = self.skill(name).unwrap();
138            result.append(&mut self.skillUpgrades(upgrade));
139        }
140        result
141    }
142
143    fn skillFirstPrecursor<'a>(&'a self, skill: &'a Skill) -> &'a Skill
144    {
145        if let Some(name) = &skill.upgrade_from
146        {
147            let precursor = self.skill(name).unwrap();
148            self.skillFirstPrecursor(precursor)
149        }
150        else
151        {
152            skill
153        }
154    }
155
156    /// Return the skill update path the skill is in. The result is
157    /// arranged from weak to strong. Exmaple:
158    ///
159    /// ```
160    /// # use data::*;
161    /// let content = include_bytes!("../../monster-data.xml");
162    /// let data = GameData::fromXML(content).unwrap();
163    /// let upgrade_path = data.skillUpgradePath(
164    ///     data.skill("Infermore").unwrap());
165    /// assert_eq!(upgrade_path.iter().map(|s| &s.name).collect::<Vec<&String>>(),
166    ///            vec!["Infernos", "Infermore", "Infermost"]);
167    /// ```
168    pub fn skillUpgradePath<'a>(&'a self, skill: &'a Skill) -> Vec<&'a Skill>
169    {
170        if skill.upgrade_from.is_some() || skill.upgrade_to.is_some()
171        {
172            self.skillUpgrades(self.skillFirstPrecursor(skill))
173        }
174        else
175        {
176            vec![skill,]
177        }
178    }
179
180    /// Return all the skills that `skill` and its upgrades combines
181    /// into.
182    pub fn skillCombinesInto<'a>(&'a self, skill: &'a Skill) ->
183        impl Iterator<Item = &Skill> + 'a
184    {
185        let mut upgrades = self.skillUpgrades(skill);
186        upgrades.push(skill);
187        self.skills.values().filter(move |s| {
188            for upgrade in &upgrades
189            {
190                if s.combine_from.contains(&upgrade.name)
191                {
192                    return true;
193                }
194            }
195            false
196        })
197    }
198}
199
200// ========== Tests =================================================>
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    use anyhow::Result;
207
208    #[test]
209    fn readXML() -> Result<()>
210    {
211        let xml = r#"<monster-data>
212  <families>
213    <family name="slime">
214      <monsters>
215        <monster name="SpotSlime" in_story="true">
216          <spawn-locations>
217            <location>
218              <map>Oasis Key World</map>
219              <description>Near Door Shrine</description>
220            </location>
221          </spawn-locations>
222          <skills>
223            <skill>CallHelp</skill>
224            <skill>LushLicks</skill>
225            <skill>Imitate</skill>
226          </skills>
227          <growth agl="17" int="8" maxlvl="35" atk="17" mp="1" exp="10" hp="17" def="4"/>
228        </monster>
229        <monster name="BoxSlime" in_story="true">
230          <spawn-locations>
231            <location>
232              <map>Sky Key World</map>
233              <description>Heaven Helm Cave Floor 1</description>
234            </location>
235          </spawn-locations>
236          <skills>
237            <skill>Blaze</skill>
238            <skill>Upper</skill>
239            <skill>Ramming</skill>
240          </skills>
241          <growth agl="14" int="13" maxlvl="50" atk="14" mp="10" exp="11" hp="11" def="19"/>
242        </monster>
243      </monsters>
244    </family>
245  </families>
246  <breeds>
247    <breed target="Zoma">
248      <base>
249        <breed-requirement monster="DracoLord1"/>
250        <breed-requirement monster="DracoLord2"/>
251      </base>
252      <mate>
253        <breed-requirement monster="Sidoh"/>
254      </mate>
255    </breed>
256  </breeds>
257  <skills-data>
258    <skill-data name="VacuSlash">
259      <skill-requirements lvl="11" hp="77" mp="34" atk="66" def="0" agl="0" int="76"/>
260      <combine-from>
261        <skill>WindBeast</skill>
262        <skill>ChargeUp</skill>
263      </combine-from>
264    </skill-data>
265    <skill-data name="Vacuum">
266      <skill-requirements lvl="19" hp="112" mp="0" atk="114" def="0" agl="132" int="0"/>
267      <precursor>VacuSlash</precursor>
268    </skill-data>
269  </skills-data>
270</monster-data>
271"#;
272        let data = GameData::fromXML(xml.as_bytes())?;
273        assert_eq!(data.breed_formulae.len(), 1);
274        assert_eq!(data.monster_data.monsters.len(), 2);
275        assert_eq!(data.monster_data.families.len(), 1);
276        assert_eq!(data.monster_data.families[0].members.len(), 2);
277        assert_eq!(data.monster_data.monsters[0].skills.len(), 3);
278        assert_eq!(data.skills.len(), 2);
279        assert_eq!(data.skill("VacuSlash").unwrap().combine_from,
280                   vec![String::from("WindBeast"), String::from("ChargeUp")]);
281        assert_eq!(data.skill("VacuSlash").unwrap().requirements.hp, 77);
282        assert_eq!(data.skill("VacuSlash").unwrap().upgrade_from, None);
283        assert_eq!(data.skill("Vacuum").unwrap().upgrade_from,
284                   Some(String::from("VacuSlash")));
285        Ok(())
286    }
287
288    #[test]
289    fn gameDataOk() -> Result<()>
290    {
291        let content = include_bytes!("../../monster-data.xml");
292        GameData::fromXML(content)?;
293        Ok(())
294    }
295}