use serde::Serialize;
use crate::models::resources::{Skill, SkillLevel};
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
pub struct LevelProgress {
pub level: u32,
pub total_xp: f64,
pub xp_into_level: f64,
pub xp_to_next: Option<f64>,
pub fractional_level: f64,
}
pub fn level_from_cumulative(total_xp: f64, cumulative: &[f64]) -> LevelProgress {
let mut level = 0u32;
let mut level_start = 0.0;
for &threshold in cumulative {
if total_xp < threshold {
let span = threshold - level_start;
let into = total_xp - level_start;
let fractional = if span > 0.0 { into / span } else { 0.0 };
return LevelProgress {
level,
total_xp,
xp_into_level: into,
xp_to_next: Some(threshold - total_xp),
fractional_level: level as f64 + fractional,
};
}
level += 1;
level_start = threshold;
}
LevelProgress {
level,
total_xp,
xp_into_level: total_xp - level_start,
xp_to_next: None,
fractional_level: level as f64,
}
}
pub fn cumulative_from_levels(levels: &[SkillLevel]) -> Vec<f64> {
let mut thresholds: Vec<f64> = levels.iter().map(|l| l.total_exp_required).collect();
thresholds.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
thresholds
}
pub fn skill_level(total_xp: f64, skill: &Skill) -> LevelProgress {
let cumulative = cumulative_from_levels(&skill.levels);
level_from_cumulative(total_xp, &cumulative)
}
#[cfg(test)]
mod tests {
use super::*;
fn table() -> Vec<f64> {
vec![100.0, 250.0, 500.0]
}
#[test]
fn resolves_mid_level() {
let p = level_from_cumulative(175.0, &table());
assert_eq!(p.level, 1);
assert_eq!(p.xp_into_level, 75.0);
assert_eq!(p.xp_to_next, Some(75.0));
assert_eq!(p.fractional_level, 1.5);
}
#[test]
fn caps_at_max_level() {
let p = level_from_cumulative(900.0, &table());
assert_eq!(p.level, 3);
assert_eq!(p.xp_to_next, None);
assert_eq!(p.fractional_level, 3.0);
}
#[test]
fn handles_zero() {
let p = level_from_cumulative(0.0, &table());
assert_eq!(p.level, 0);
assert_eq!(p.fractional_level, 0.0);
}
}