2021-06-07 21:58:05 +00:00
|
|
|
#![warn(clippy::pedantic)]
|
|
|
|
//#![warn(clippy::nursery)]
|
|
|
|
use crate::comp::skills::{Skill, SkillGroupKind, SkillSet};
|
|
|
|
|
|
|
|
use crate::assets::{self, AssetExt};
|
2021-03-21 05:53:39 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2021-01-07 19:47:29 +00:00
|
|
|
use tracing::warn;
|
|
|
|
|
2021-06-14 10:55:25 +00:00
|
|
|
/// `SkillSetBuilder` preset. Consider using loading from assets, when possible.
|
|
|
|
/// When you're adding new enum variant,
|
|
|
|
/// handle it in [`with_preset`](SkillSetBuilder::with_preset) method
|
2021-03-21 05:53:39 +00:00
|
|
|
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, Debug)]
|
2021-06-14 10:55:25 +00:00
|
|
|
pub enum Preset {}
|
2021-06-07 21:58:05 +00:00
|
|
|
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
|
|
struct SkillSetTree(Vec<SkillNode>);
|
|
|
|
impl assets::Asset for SkillSetTree {
|
|
|
|
type Loader = assets::RonLoader;
|
|
|
|
|
|
|
|
const EXTENSION: &'static str = "ron";
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
|
|
enum SkillNode {
|
|
|
|
Tree(String),
|
|
|
|
Skill((Skill, Option<u16>)),
|
|
|
|
Group(SkillGroupKind),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
fn skills_from_asset_expect(asset_specifier: &str) -> Vec<(Skill, Option<u16>)> {
|
|
|
|
let nodes = SkillSetTree::load_expect(asset_specifier).read().0.clone();
|
|
|
|
|
|
|
|
skills_from_nodes(nodes)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
fn skills_from_nodes(nodes: Vec<SkillNode>) -> Vec<(Skill, Option<u16>)> {
|
|
|
|
let mut skills = Vec::new();
|
|
|
|
for node in nodes {
|
|
|
|
match node {
|
|
|
|
SkillNode::Tree(asset) => {
|
|
|
|
skills.append(&mut skills_from_asset_expect(&asset));
|
|
|
|
},
|
|
|
|
SkillNode::Skill(req) => {
|
|
|
|
skills.push(req);
|
|
|
|
},
|
|
|
|
SkillNode::Group(group) => {
|
|
|
|
skills.push((Skill::UnlockGroup(group), None));
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
skills
|
2021-01-07 19:47:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct SkillSetBuilder(SkillSet);
|
|
|
|
|
|
|
|
impl Default for SkillSetBuilder {
|
|
|
|
fn default() -> Self { Self(SkillSet::default()) }
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SkillSetBuilder {
|
2021-06-07 21:58:05 +00:00
|
|
|
/// Creates `SkillSetBuilder` from `asset_specifier`
|
|
|
|
#[must_use]
|
|
|
|
pub fn from_asset_expect(asset_specifier: &str) -> Self {
|
|
|
|
let builder = Self::default();
|
2021-01-08 20:53:52 +00:00
|
|
|
|
2021-06-07 21:58:05 +00:00
|
|
|
builder.with_asset_expect(asset_specifier)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Applies `asset_specifier` with needed skill tree
|
|
|
|
#[must_use]
|
|
|
|
pub fn with_asset_expect(mut self, asset_specifier: &str) -> Self {
|
|
|
|
let tree = skills_from_asset_expect(asset_specifier);
|
|
|
|
for (skill, level) in tree {
|
|
|
|
self = self.with_skill(skill, level);
|
|
|
|
}
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates `SkillSetBuilder` for given preset
|
|
|
|
#[must_use]
|
|
|
|
pub fn from_preset(preset: Preset) -> Self {
|
|
|
|
let builder = Self::default();
|
|
|
|
|
|
|
|
builder.with_preset(preset)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Applies preset
|
|
|
|
#[must_use]
|
2021-06-14 10:55:25 +00:00
|
|
|
pub const fn with_preset(self, _preset: Preset) -> Self { self }
|
2021-01-07 19:47:29 +00:00
|
|
|
|
2021-06-07 11:38:08 +00:00
|
|
|
#[must_use]
|
|
|
|
/// # Panics
|
|
|
|
/// will panic only in tests
|
|
|
|
/// 1) If added skill doesn't have any group
|
|
|
|
/// 2) If added skill already applied
|
|
|
|
/// 3) If added skill wasn't applied at the end
|
2021-01-21 19:52:07 +00:00
|
|
|
pub fn with_skill(mut self, skill: Skill, level: Option<u16>) -> Self {
|
2021-06-07 11:38:08 +00:00
|
|
|
let group = if let Some(skill_group) = skill.skill_group_kind() {
|
|
|
|
skill_group
|
2021-01-10 01:05:13 +00:00
|
|
|
} else {
|
2021-06-07 11:38:08 +00:00
|
|
|
let err = format!(
|
2021-01-10 01:05:13 +00:00
|
|
|
"Tried to add skill: {:?} which does not have an associated skill group.",
|
|
|
|
skill
|
|
|
|
);
|
2021-06-07 21:58:05 +00:00
|
|
|
common_base::dev_panic!(err, or return self);
|
2021-06-07 11:38:08 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let SkillSetBuilder(ref mut skill_set) = self;
|
|
|
|
if skill_is_applied(skill_set, skill, level) {
|
|
|
|
let err = format!(
|
|
|
|
"Tried to add skill: {:?} with level {:?} which is already applied",
|
|
|
|
skill, level,
|
|
|
|
);
|
2021-06-07 21:58:05 +00:00
|
|
|
common_base::dev_panic!(err, or return self);
|
2021-06-07 11:38:08 +00:00
|
|
|
}
|
|
|
|
for _ in 0..level.unwrap_or(1) {
|
|
|
|
skill_set.add_skill_points(group, skill_set.skill_cost(skill));
|
|
|
|
skill_set.unlock_skill(skill);
|
|
|
|
}
|
|
|
|
if !skill_is_applied(skill_set, skill, level) {
|
|
|
|
let err = format!(
|
|
|
|
"Failed to add skill: {:?}. Verify that it has the appropriate skill group \
|
|
|
|
available and meets all prerequisite skills.",
|
|
|
|
skill
|
|
|
|
);
|
2021-06-07 21:58:05 +00:00
|
|
|
common_base::dev_panic!(err);
|
2021-01-07 19:47:29 +00:00
|
|
|
}
|
2021-01-10 01:05:13 +00:00
|
|
|
self
|
2021-01-07 19:47:29 +00:00
|
|
|
}
|
|
|
|
|
2021-06-07 21:58:05 +00:00
|
|
|
#[must_use]
|
2021-01-07 19:47:29 +00:00
|
|
|
pub fn build(self) -> SkillSet { self.0 }
|
|
|
|
}
|
2021-06-07 11:38:08 +00:00
|
|
|
|
2021-06-07 21:58:05 +00:00
|
|
|
#[must_use]
|
2021-06-07 11:38:08 +00:00
|
|
|
fn skill_is_applied(skill_set: &SkillSet, skill: Skill, level: Option<u16>) -> bool {
|
|
|
|
if let Ok(applied_level) = skill_set.skill_level(skill) {
|
|
|
|
applied_level == level
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2021-06-07 21:58:05 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use assets::Error;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_all_skillset_assets() {
|
|
|
|
#[derive(Clone)]
|
|
|
|
struct SkillSetList(Vec<SkillSetTree>);
|
|
|
|
|
|
|
|
impl assets::Compound for SkillSetList {
|
|
|
|
fn load<S: assets::source::Source>(
|
|
|
|
cache: &assets::AssetCache<S>,
|
|
|
|
specifier: &str,
|
|
|
|
) -> Result<Self, Error> {
|
|
|
|
let list = cache
|
|
|
|
.load::<assets::Directory>(specifier)?
|
|
|
|
.read()
|
|
|
|
.iter()
|
|
|
|
.map(|spec| SkillSetTree::load_cloned(spec))
|
|
|
|
.collect::<Result<_, Error>>()?;
|
|
|
|
|
|
|
|
Ok(Self(list))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let skillsets = SkillSetList::load_expect_cloned("common.skillset.*").0;
|
|
|
|
for skillset in skillsets {
|
|
|
|
std::mem::drop({
|
|
|
|
let mut skillset_builder = SkillSetBuilder::default();
|
|
|
|
let nodes = skillset.0;
|
|
|
|
let tree = skills_from_nodes(nodes);
|
|
|
|
for (skill, level) in tree {
|
|
|
|
skillset_builder = skillset_builder.with_skill(skill, level);
|
|
|
|
}
|
|
|
|
|
|
|
|
skillset_builder
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|