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