sc2_techtree/
lib.rs

1//! SC2 Tech Tree
2//! A library for validation and querying sc2-techtree data
3
4// Lints
5#![warn(clippy::all)]
6#![deny(missing_docs)]
7#![forbid(unused_must_use)]
8// Features
9#![feature(type_alias_enum_variants)]
10
11use serde::{Deserialize, Serialize};
12
13mod ability;
14mod attribute;
15mod cost;
16mod ids;
17mod race;
18mod requirement;
19mod unittype;
20mod upgrade;
21mod weapon;
22
23pub use serde_json::Error as DeserializeError;
24
25pub use self::ability::Ability;
26pub use self::attribute::Attribute;
27pub use self::cost::Cost;
28pub use self::ids::*;
29pub use self::race::Race;
30pub use self::requirement::Requirement;
31pub use self::unittype::UnitType;
32pub use self::upgrade::Upgrade;
33pub use self::weapon::*;
34
35use self::ability::{AbilityResearch, AbilityTarget};
36
37/// Error when querying tech data
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum QueryError {
40    /// Matching item not found
41    NotFound,
42}
43
44/// All tech data
45#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
46pub struct TechData {
47    /// All abilities
48    #[serde(rename = "Ability")]
49    pub abilities: Vec<Ability>,
50    /// All unit types
51    #[serde(rename = "Unit")]
52    pub unittypes: Vec<UnitType>,
53    /// All upgrades
54    #[serde(rename = "Upgrade")]
55    pub upgrades: Vec<Upgrade>,
56}
57impl TechData {
58    /// Use the version of the data bundled into this libarary
59    pub fn current() -> Self {
60        serde_json::from_str::<TechData>(include_str!("../data/data.json")).expect("Invalid bundled data")
61    }
62
63    /// Load data from a json string
64    pub fn from_json(source: &str) -> Result<Self, DeserializeError> {
65        serde_json::from_str::<TechData>(source)
66    }
67
68    /// Get an ability by id
69    pub fn ability(&self, ability_id: AbilityId) -> Option<Ability> {
70        for ability in &self.abilities {
71            if ability.id == ability_id {
72                return Some(ability.clone());
73            }
74        }
75        None
76    }
77
78    /// Get an upgrade by id
79    pub fn upgrade(&self, upgrade_id: UpgradeId) -> Option<Upgrade> {
80        for upgrade in &self.upgrades {
81            if upgrade.id == upgrade_id {
82                return Some(upgrade.clone());
83            }
84        }
85        None
86    }
87
88    /// Get a unit type by id
89    pub fn unittype(&self, unittype_id: UnitTypeId) -> Option<UnitType> {
90        for unittype in &self.unittypes {
91            if unittype.id == unittype_id {
92                return Some(unittype.clone());
93            }
94        }
95        None
96    }
97
98    /// Ability that researches an upgrade
99    pub fn upgrade_ability(&self, upgrade_id: UpgradeId) -> Result<Ability, QueryError> {
100        for ability in &self.abilities {
101            if let AbilityTarget::Research(AbilityResearch { upgrade }) = ability.target {
102                if upgrade == upgrade_id {
103                    return Ok(ability.clone());
104                }
105            }
106        }
107        Err(QueryError::NotFound)
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::{QueryError, TechData, UpgradeId};
114
115    use std::fs::File;
116    use std::io::prelude::*;
117
118    #[test]
119    fn load_data() {
120        let mut f = File::open("data/data.json").expect("File not found");
121
122        let mut contents = String::new();
123        f.read_to_string(&mut contents).expect("Could not read");
124
125        TechData::from_json(&contents).expect("Deserialization failed");
126    }
127
128    #[test]
129    fn upgrade_ability() {
130        let td = TechData::current();
131
132        assert_eq!(
133            td.upgrade_ability(UpgradeId::new(64)).unwrap().name,
134            "RESEARCH_BURROW"
135        );
136
137        assert_eq!(
138            td.upgrade_ability(UpgradeId::new(86)).unwrap().name,
139            "RESEARCH_CHARGE"
140        );
141
142        assert_eq!(
143            td.upgrade_ability(UpgradeId::new(123_456)),
144            Err(QueryError::NotFound)
145        );
146    }
147}