1use serde::Serialize;
2
3use crate::models::resources::{Skill, SkillLevel};
4
5#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
7pub struct LevelProgress {
8 pub level: u32,
10 pub total_xp: f64,
12 pub xp_into_level: f64,
14 pub xp_to_next: Option<f64>,
16 pub fractional_level: f64,
18}
19
20pub fn level_from_cumulative(total_xp: f64, cumulative: &[f64]) -> LevelProgress {
27 let mut level = 0u32;
28 let mut level_start = 0.0;
29 for &threshold in cumulative {
30 if total_xp < threshold {
31 let span = threshold - level_start;
32 let into = total_xp - level_start;
33 let fractional = if span > 0.0 { into / span } else { 0.0 };
34 return LevelProgress {
35 level,
36 total_xp,
37 xp_into_level: into,
38 xp_to_next: Some(threshold - total_xp),
39 fractional_level: level as f64 + fractional,
40 };
41 }
42 level += 1;
43 level_start = threshold;
44 }
45 LevelProgress {
46 level,
47 total_xp,
48 xp_into_level: total_xp - level_start,
49 xp_to_next: None,
50 fractional_level: level as f64,
51 }
52}
53
54pub fn cumulative_from_levels(levels: &[SkillLevel]) -> Vec<f64> {
56 let mut thresholds: Vec<f64> = levels.iter().map(|l| l.total_exp_required).collect();
57 thresholds.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
58 thresholds
59}
60
61pub fn skill_level(total_xp: f64, skill: &Skill) -> LevelProgress {
64 let cumulative = cumulative_from_levels(&skill.levels);
65 level_from_cumulative(total_xp, &cumulative)
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 fn table() -> Vec<f64> {
73 vec![100.0, 250.0, 500.0]
74 }
75
76 #[test]
77 fn resolves_mid_level() {
78 let p = level_from_cumulative(175.0, &table());
79 assert_eq!(p.level, 1);
80 assert_eq!(p.xp_into_level, 75.0);
81 assert_eq!(p.xp_to_next, Some(75.0));
82 assert_eq!(p.fractional_level, 1.5);
83 }
84
85 #[test]
86 fn caps_at_max_level() {
87 let p = level_from_cumulative(900.0, &table());
88 assert_eq!(p.level, 3);
89 assert_eq!(p.xp_to_next, None);
90 assert_eq!(p.fractional_level, 3.0);
91 }
92
93 #[test]
94 fn handles_zero() {
95 let p = level_from_cumulative(0.0, &table());
96 assert_eq!(p.level, 0);
97 assert_eq!(p.fractional_level, 0.0);
98 }
99}