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, or return self);
118        };
119
120        let SkillSetBuilder(ref mut skill_set) = self;
121        if skill_is_applied(skill_set, skill, level) {
122            let err = format!(
123                "Tried to add skill: {skill:?} with level {level:?} which is already applied"
124            );
125            common_base::dev_panic!(err, or return self);
126        }
127        for _ in 0..level {
128            skill_set.add_skill_points(group, skill_set.skill_cost(skill));
129            if let Err(err) = skill_set.unlock_skill(skill) {
130                let err_msg = format!("Failed to add skill: {skill:?}. Error: {err:?}");
131                common_base::dev_panic!(err_msg);
132            }
133        }
134        if !skill_is_applied(skill_set, skill, level) {
135            let err = format!(
136                "Failed to add skill: {skill:?}. Verify that it has the appropriate skill group \
137                 available and meets all prerequisite skills."
138            );
139            common_base::dev_panic!(err);
140        }
141        self
142    }
143
144    #[must_use]
145    pub fn build(self) -> SkillSet { self.0 }
146}
147
148#[must_use]
149fn skill_is_applied(skill_set: &SkillSet, skill: Skill, level: u16) -> bool {
150    if let Ok(applied_level) = skill_set.skill_level(skill) {
151        applied_level == level
152    } else {
153        false
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_all_skillset_assets() {
163        let skillsets =
164            assets::load_rec_dir::<SkillSetTree>("common.skillset").expect("load skillsets");
165        for skillset in skillsets.read().ids() {
166            let skillset = SkillSetTree::load_expect(skillset).read();
167
168            drop({
169                let mut skillset_builder = SkillSetBuilder::default();
170                let nodes = &*skillset.0;
171                let tree = skills_from_nodes(nodes);
172                for (skill, level) in tree {
173                    skillset_builder = skillset_builder.with_skill(skill, level);
174                }
175
176                skillset_builder
177            });
178        }
179    }
180}