deepwoken 0.3.1

A library for interacting with Deepwoken data in a more convenient format, with a few added utilities.
Documentation
use std::{
    collections::HashMap,
    ops::{Deref, DerefMut},
};

use serde::{Deserialize, Serialize};

use crate::{
    Stat,
    model::data::{DeepData, Talent},
    constants::{MAX_LEVEL, MAX_TOTAL},
    req::Requirement,
    util::algos,
};

/// Wrapper around a `HashMap` of stats to their values
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct StatMap(pub HashMap<Stat, i64>);

impl StatMap {
    /// Creates a new empty Stats map.
    #[must_use]
    pub fn new() -> Self {
        StatMap(HashMap::new())
    }

    #[must_use]
    #[allow(
        clippy::cast_possible_wrap,
        reason = "we're never having too many stats in the statmap"
    )]
    pub fn cost(&self) -> i64 {
        self.0.values().sum::<i64>()
            - (self
                .0
                .iter()
                .filter(|(s, v)| s.is_attunement() && **v > 0)
                .count() as i64
                - 1)
            .max(0)
    }

    #[must_use]
    pub fn remaining(&self) -> i64 {
        MAX_TOTAL - self.cost()
    }

    #[must_use]
    pub fn level(&self, max_level: Option<u32>) -> i64 {
        ((self.cost() - 15) / 15).clamp(0, i64::from(max_level.unwrap_or(MAX_LEVEL)))
    }

    #[must_use]
    pub fn get(&self, stat: &Stat) -> i64 {
        *self.0.get(stat).unwrap_or(&0)
    }

    #[must_use]
    pub fn shrine_order(&self, racial: &StatMap) -> StatMap {
        algos::shrine_order_dwb(self, racial)
    }

    #[must_use]
    pub fn satisfies(&self, req: Requirement) -> bool {
        req.satisfied_by(&self)
    }

    /// Returns the implicit talents granted by this stat map.
    ///
    /// Implicit talents are flagged `implicit` in the source data. They are granted automatically
    /// when meeting some requirement, removed when using the SoO to go below the stat reqs, and
    /// they gate usage of the SoM.
    #[must_use]
    pub fn implicit_talents(&self, data: &DeepData) -> Vec<Talent> {
        data.talents()
            .filter(|t| t.implicit)
            .filter(|t| t.reqs.satisfied_by(self))
            .cloned()
            .collect()
    }
}

impl Default for StatMap {
    fn default() -> Self {
        Self::new()
    }
}

impl Deref for StatMap {
    type Target = HashMap<Stat, i64>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for StatMap {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl From<HashMap<Stat, i64>> for StatMap {
    fn from(map: HashMap<Stat, i64>) -> Self {
        StatMap(map)
    }
}

#[allow(
    clippy::implicit_hasher,
    reason = "StatMap itself is not generic over hashers"
)]
impl From<StatMap> for HashMap<Stat, i64> {
    fn from(val: StatMap) -> Self {
        val.0
    }
}