veloren_common/
skillset_builder.rs

1#![warn(clippy::pedantic)]
2//#![warn(clippy::nursery)]
3use crate::comp::skillset::{SkillGroupKind, SkillSet, skills::Skill};
4
5use crate::assets::{AssetExt, Ron};
6use serde::{Deserialize, Serialize};
7use tracing::warn;
8
9/// `SkillSetBuilder` preset. Consider using loading from assets, when possible.
10/// When you're adding new enum variant,
11/// handle it in [`with_preset`](SkillSetBuilder::with_preset) method
12#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
13pub enum Preset {
14    Rank1,
15    Rank2,
16    Rank3,
17    Rank4,
18    Rank5,
19}
20
21#[derive(Debug, Deserialize, Clone)]
22enum SkillNode {
23    Tree(String),
24    Skill((Skill, u16)),
25    Group(SkillGroupKind),
26}
27
28#[must_use]
29fn skills_from_asset_expect(asset_specifier: &str) -> Vec<(Skill, u16)> {
30    let nodes = Ron::<Vec<SkillNode>>::load_expect(asset_specifier).read();
31
32    skills_from_nodes(&nodes.0)
33}
34
35#[must_use]
36fn skills_from_nodes(nodes: &[SkillNode]) -> Vec<(Skill, u16)> {
37    let mut skills = Vec::new();
38    for node in nodes {
39        match node {
40            SkillNode::Tree(asset) => {
41                skills.append(&mut skills_from_asset_expect(asset));
42            },
43            SkillNode::Skill(req) => {
44                skills.push(*req);
45            },
46            SkillNode::Group(group) => {
47                skills.push((Skill::UnlockGroup(*group), 1));
48            },
49        }
50    }
51
52    skills
53}
54
55#[derive(Default)]
56pub struct SkillSetBuilder(SkillSet);
57
58impl SkillSetBuilder {
59    /// Creates `SkillSetBuilder` from `asset_specifier`
60    #[must_use]
61    pub fn from_asset_expect(asset_specifier: &str) -> Self {
62        let builder = Self::default();
63
64        builder.with_asset_expect(asset_specifier)
65    }
66
67    /// Applies `asset_specifier` with needed skill tree
68    #[must_use]
69    pub fn with_asset_expect(mut self, asset_specifier: &str) -> Self {
70        let tree = skills_from_asset_expect(asset_specifier);
71        for (skill, level) in tree {
72            self = self.with_skill(skill, level);
73        }
74
75        self
76    }
77
78    /// Creates `SkillSetBuilder` for given preset
79    #[must_use]
80    pub fn from_preset(preset: Preset) -> Self {
81        let builder = Self::default();
82
83        builder.with_preset(preset)
84    }
85
86    /// Applies preset
87    #[must_use]
88    pub fn with_preset(self, preset: Preset) -> Self {
89        match preset {
90            Preset::Rank1 => self.with_asset_expect("common.skillset.preset.rank1.fullskill"),
91            Preset::Rank2 => self.with_asset_expect("common.skillset.preset.rank2.fullskill"),
92            Preset::Rank3 => self.with_asset_expect("common.skillset.preset.rank3.fullskill"),
93            Preset::Rank4 => self.with_asset_expect("common.skillset.preset.rank4.fullskill"),
94            Preset::Rank5 => self.with_asset_expect("common.skillset.preset.rank5.fullskill"),
95        }
96    }
97
98    #[must_use]
99    /// # Panics
100    /// will panic only in tests
101    /// 1) If added skill doesn't have any group
102    /// 2) If added skill already applied
103    /// 3) If added skill wasn't applied at the end
104    pub fn with_skill(mut self, skill: Skill, level: u16) -> Self {
105        let Some(group) = skill.skill_group_kind() else {
106            let err = format!(
107                "Tried to add skill: {skill:?} which does not have an associated skill group."
108            );
109            common_base::dev_panic!(err);
110
111            return self;
112        };
113
114        let SkillSetBuilder(ref mut skill_set) = self;
115        if skill_is_applied(skill_set, skill, level) {
116            let err = format!(
117                "Tried to add skill: {skill:?} with level {level:?} which is already applied"
118            );
119            common_base::dev_panic!(err);
120
121            return self;
122        }
123        for _ in 0..level {
124            skill_set.add_skill_points(group, skill_set.skill_cost(skill));
125            if let Err(err) = skill_set.unlock_skill(skill) {
126                let err_msg = format!("Failed to add skill: {skill:?}. Error: {err:?}");
127                common_base::dev_panic!(err_msg);
128            }
129        }
130        if !skill_is_applied(skill_set, skill, level) {
131            let err = format!(
132                "Failed to add skill: {skill:?}. Verify that it has the appropriate skill group \
133                 available and meets all prerequisite skills."
134            );
135            common_base::dev_panic!(err);
136        }
137        self
138    }
139
140    #[must_use]
141    pub fn build(self) -> SkillSet { self.0 }
142}
143
144#[must_use]
145fn skill_is_applied(skill_set: &SkillSet, skill: Skill, level: u16) -> bool {
146    if let Ok(applied_level) = skill_set.skill_level(skill) {
147        applied_level == level
148    } else {
149        false
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156    use crate::assets;
157
158    #[test]
159    fn test_all_skillset_assets() {
160        let skillsets =
161            assets::load_rec_dir::<Ron<Vec<SkillNode>>>("common.skillset").expect("load skillsets");
162        for skillset in skillsets.read().ids() {
163            let skillset = Ron::<Vec<SkillNode>>::load_expect(skillset).read();
164
165            drop({
166                let mut skillset_builder = SkillSetBuilder::default();
167                let nodes = &*skillset.0;
168                let tree = skills_from_nodes(nodes);
169                for (skill, level) in tree {
170                    skillset_builder = skillset_builder.with_skill(skill, level);
171                }
172
173                skillset_builder
174            });
175        }
176    }
177}