team04_server/config/
unit.rs

1use crate::unit::UnitType;
2use serde::de::Error;
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5
6/// The unit configuration.
7#[derive(Debug, PartialEq)]
8pub struct UnitConfig {
9    pub level1: Vec<UnitDef>,
10    pub level2: Vec<UnitDef>,
11    pub level3: Vec<UnitDef>,
12}
13
14impl UnitConfig {
15    pub fn get_unit<'a>(&'a self, unit_type: UnitType) -> Option<&'a UnitDef> {
16        let find = move |vec: &'a [UnitDef]| -> Option<&'a UnitDef> {
17            vec.iter().find(|unit| unit.unit_type == unit_type)
18        };
19        find(&self.level1).or_else(|| find(&self.level2).or_else(|| find(&self.level3)))
20    }
21}
22impl UnitDef {
23    /// If there are `count` units of this type, this returns
24    /// the appropriate `Special` value, that is, the
25    /// `Special` value with the highest unit count
26    /// requirement which does not exceed `count`.
27    pub fn special(&self, count: u64) -> Option<&Special> {
28        self.specials
29            .range(0..=count)
30            .rev()
31            .next()
32            .map(|(_, special)| special)
33    }
34}
35
36/// Defines the base stats and specials of a specific unit type.
37#[derive(Debug, Clone, PartialEq)]
38pub struct UnitDef {
39    pub unit_type: UnitType,
40    pub health: u64,
41    pub attack: u64,
42    pub armor: u64,
43    pub attack_range: u64,
44
45    /// Contains the special definitions for this unit type, with the key being
46    /// the minimum number of units of this type needed to activate the
47    /// corresponding special.
48    pub specials: BTreeMap<u64, Special>,
49
50    /// The minimum number of `JEDI` units required for activating their
51    /// 'double movement' feature. Should be ignored for other unit types.
52    pub multiple_moves_threshold: u64,
53
54    /// The minimum number of `SITH` units required for activating their
55    /// infinite attack range feature. Should be ignored for other unit types.
56    pub infinite_range_threshold: u64,
57
58    /// The minimum number of `SITH` units required for activating their
59    /// 'unobstructed sight' feature. Should be ignored for other unit types.
60    pub unobstructed_sight_threshold: u64,
61}
62
63fn verify_units(units: &[json::UnitJson]) -> Vec<UnitDef> {
64    units
65        .iter()
66        .map(|unit| UnitDef {
67            unit_type: unit.unit_type,
68            health: unit.stats.health,
69            attack: unit.stats.attack,
70            armor: unit.stats.armor,
71            attack_range: unit.stats.attackRange,
72            specials: unit
73                .special
74                .iter()
75                .map(|(n, special)| {
76                    let special = Special {
77                        health: special.health,
78                        attack: special.attack,
79                        armor: special.armor,
80                        attack_range: special.attackRange,
81                        targets: special.targets,
82                        bonus_damage: special.bonusDamage,
83                        healing: special.healing,
84                    };
85                    (*n, special)
86                })
87                .collect(),
88            multiple_moves_threshold: unit.multipleMovesThreshold.unwrap_or(0),
89            infinite_range_threshold: unit.infiniteRangeThreshold.unwrap_or(0),
90            unobstructed_sight_threshold: unit.unobstructedSightThreshold.unwrap_or(0),
91        })
92        .collect()
93}
94
95impl<'de> Deserialize<'de> for UnitConfig {
96    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
97    where
98        D: serde::Deserializer<'de>,
99    {
100        let json = json::UnitConfigJson::deserialize(deserializer)?;
101
102        let conf = UnitConfig {
103            level1: verify_units(&json.level1),
104            level2: verify_units(&json.level2),
105            level3: verify_units(&json.level3),
106        };
107        if conf.level1.is_empty() {
108            return Err(Error::custom("no level1 units"));
109        }
110        if conf.level2.is_empty() {
111            return Err(Error::custom("no level2 units"));
112        }
113        if conf.level3.is_empty() {
114            return Err(Error::custom("no level3 units"));
115        }
116        Ok(conf)
117    }
118}
119
120fn units_to_json(units: &[UnitDef], level3: bool) -> Vec<json::UnitJson> {
121    units
122        .iter()
123        .map(|unit| json::UnitJson {
124            unit_type: unit.unit_type,
125            stats: json::StatsJson {
126                health: unit.health,
127                attack: unit.attack,
128                armor: unit.armor,
129                attackRange: unit.attack_range,
130            },
131            special: unit
132                .specials
133                .iter()
134                .map(|(n, special)| {
135                    (
136                        *n,
137                        json::SpecialJson {
138                            health: special.health,
139                            attack: special.attack,
140                            armor: special.armor,
141                            attackRange: special.attack_range,
142                            targets: special.targets,
143                            bonusDamage: special.bonus_damage,
144                            healing: special.healing,
145                        },
146                    )
147                })
148                .collect(),
149            multipleMovesThreshold: if level3 {
150                Some(unit.multiple_moves_threshold)
151            } else {
152                None
153            },
154            infiniteRangeThreshold: if level3 {
155                Some(unit.infinite_range_threshold)
156            } else {
157                None
158            },
159            unobstructedSightThreshold: if level3 {
160                Some(unit.unobstructed_sight_threshold)
161            } else {
162                None
163            },
164        })
165        .collect()
166}
167
168impl Serialize for UnitConfig {
169    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
170    where
171        S: serde::Serializer,
172    {
173        json::UnitConfigJson {
174            level1: units_to_json(&self.level1, false),
175            level2: units_to_json(&self.level2, false),
176            level3: units_to_json(&self.level3, true),
177        }
178        .serialize(serializer)
179    }
180}
181
182/// Defines the stat boost / effect strengths for special abilities.
183#[derive(Debug, Clone, PartialEq)]
184pub struct Special {
185    pub health: u64,
186    pub attack: u64,
187    pub armor: u64,
188    pub attack_range: u64,
189    pub targets: u64,
190    pub bonus_damage: u64,
191    pub healing: u64,
192}
193
194#[allow(non_snake_case)]
195mod json {
196    use super::*;
197
198    #[derive(Serialize, Deserialize)]
199    pub struct UnitConfigJson {
200        pub level1: Vec<UnitJson>,
201        pub level2: Vec<UnitJson>,
202        pub level3: Vec<UnitJson>,
203    }
204
205    #[derive(Serialize, Deserialize)]
206    pub struct UnitJson {
207        #[serde(rename = "type")]
208        pub unit_type: UnitType,
209        pub stats: StatsJson,
210        pub special: BTreeMap<u64, SpecialJson>,
211        pub multipleMovesThreshold: Option<u64>,
212        pub infiniteRangeThreshold: Option<u64>,
213        pub unobstructedSightThreshold: Option<u64>,
214    }
215
216    #[derive(Serialize, Deserialize)]
217    pub struct StatsJson {
218        pub health: u64,
219        pub attack: u64,
220        pub armor: u64,
221        pub attackRange: u64,
222    }
223
224    #[derive(Serialize, Deserialize)]
225    pub struct SpecialJson {
226        pub health: u64,
227        pub attack: u64,
228        pub armor: u64,
229        pub attackRange: u64,
230        pub targets: u64,
231        pub bonusDamage: u64,
232        pub healing: u64,
233    }
234}