Skip to main content

steel_core/player/advancement/
progress.rs

1//! represent the different struct related to the progress of an advancement
2use rustc_hash::FxHashMap;
3use serde::{Deserialize, Serialize};
4use std::borrow::Cow;
5use std::time::SystemTime;
6use steel_registry::advancement::AdvancementRequirement;
7use steel_registry::advancement::registry::AdvancementRef;
8
9/// represent a map of all progress made for advancement of a specific player
10#[derive(Debug, Default, Clone)]
11pub struct AdvancementProgressMap {
12    /// A map of all advancement progress for the player.
13    map: FxHashMap<AdvancementRef, usize>,
14    order: Vec<(AdvancementRef, AdvancementProgress)>,
15}
16
17impl AdvancementProgressMap {
18    /// Gets a mutable reference to the current progress for a given advancement. Creates the state entry if missing.
19    #[must_use]
20    pub fn get_mut_or_start_progress(
21        &mut self,
22        advancement: AdvancementRef,
23    ) -> &mut AdvancementProgress {
24        let idx = self.map.entry(advancement).or_insert_with(|| {
25            let mut progress = AdvancementProgress::default();
26            progress.update(advancement.requirements());
27            self.order.push((advancement, progress));
28            self.order.len() - 1
29        });
30        if let Some(progress) = self.order.get_mut(*idx) {
31            &mut progress.1
32        } else {
33            unreachable!("Index {} not found in order vector", idx);
34        }
35    }
36
37    /// Replaces the progress of an advancement with persisted state.
38    pub fn start_progress(
39        &mut self,
40        advancement: AdvancementRef,
41        mut progress: AdvancementProgress,
42    ) {
43        progress.update(advancement.requirements());
44        if let Some(&idx) = self.map.get(&advancement) {
45            self.order[idx].1 = progress;
46        } else {
47            let idx = self.order.len();
48            self.order.push((advancement, progress));
49            self.map.insert(advancement, idx);
50        }
51    }
52
53    /// clear all the progress made
54    #[inline]
55    pub fn clear(&mut self) {
56        self.map.clear();
57        self.order.clear();
58    }
59
60    /// insert a new advancement to the progress map
61    #[inline]
62    pub fn insert(&mut self, advancement: AdvancementRef, progress: AdvancementProgress) {
63        self.order.push((advancement, progress));
64        self.map.insert(advancement, self.order.len() - 1);
65    }
66
67    /// return the len of the map
68    #[must_use]
69    #[inline]
70    pub fn len(&self) -> usize {
71        self.map.len()
72    }
73
74    /// return `true` if empty
75    #[must_use]
76    #[inline]
77    pub fn is_empty(&self) -> bool {
78        self.map.is_empty()
79    }
80
81    /// return whether the advancement has a progress instance
82    #[must_use]
83    #[inline]
84    pub fn has_progress(&self, advancement: AdvancementRef) -> bool {
85        self.map.contains_key(&advancement)
86    }
87
88    /// return the progress of a specific advancement
89    #[must_use]
90    #[inline]
91    pub fn get_progress(&self, advancement: AdvancementRef) -> Option<&AdvancementProgress> {
92        self.map.get(&advancement).map(|&i| &self.order[i].1)
93    }
94
95    /// return an iterator over all the progress made using the order of insertion
96    pub fn iter(&self) -> impl Iterator<Item = &(AdvancementRef, AdvancementProgress)> {
97        self.order.iter()
98    }
99}
100
101/// Represents the progress of a given advancement for a player.
102///
103/// Tracks whether the advancement has been fully completed. In the future,
104/// this will also track specific criteria progress.
105#[derive(Debug, Clone, Default)]
106pub struct AdvancementProgress {
107    /// Indicates the different progress of all criteria currently only a boolean
108    criteria: FxHashMap<Cow<'static, str>, CriterionProgress>,
109    /// The Requirement for the Advancement to be mark as complete
110    requirements: AdvancementRequirement,
111}
112
113impl AdvancementProgress {
114    /// Creates a new `AdvancementProgress` with the given criteria.
115    /// need to call `update` to set the requirements for the advancement
116    #[must_use]
117    pub fn new(criteria: FxHashMap<Cow<'static, str>, CriterionProgress>) -> AdvancementProgress {
118        Self {
119            criteria,
120            requirements: AdvancementRequirement::default(),
121        }
122    }
123
124    /// Returns a reference to the criteria map.
125    #[must_use]
126    pub const fn criteria(&self) -> &FxHashMap<Cow<'static, str>, CriterionProgress> {
127        &self.criteria
128    }
129
130    /// Returns `true` if the advancement is done.
131    #[must_use]
132    pub fn is_done(&self) -> bool {
133        self.requirements.test(|s| self.is_criterion_done(s))
134    }
135
136    /// Check if a criterion his mark has complete
137    fn is_criterion_done(&self, criterion: &str) -> bool {
138        self.criteria
139            .get(criterion)
140            .is_some_and(CriterionProgress::is_done)
141    }
142
143    /// Returns `true` if the advancement has any progress.
144    #[must_use]
145    pub fn has_progress(&self) -> bool {
146        for value in self.criteria.values() {
147            if value.is_done() {
148                return true;
149            }
150        }
151        false
152    }
153
154    /// grant a specific criterion
155    pub fn grant_progress(&mut self, name: &str) -> bool {
156        if let Some(value) = self.criteria.get_mut(name)
157            && !value.is_done()
158        {
159            value.grant();
160            true
161        } else {
162            false
163        }
164    }
165
166    /// revoke a specific criterion
167    pub fn revoke_progress(&mut self, name: &str) -> bool {
168        if let Some(value) = self.criteria.get_mut(name)
169            && value.is_done()
170        {
171            value.revoke();
172            true
173        } else {
174            false
175        }
176    }
177
178    /// update the progress to match a new `AdvancementRequirement` list
179    pub fn update(&mut self, requirements: &AdvancementRequirement) {
180        let names = requirements.names();
181        self.criteria.retain(|key, _criterion| names.contains(key));
182        for name in names {
183            self.criteria.entry(name).or_default();
184        }
185        self.requirements = requirements.clone();
186    }
187
188    /// return the remaining criteria
189    #[inline]
190    pub(crate) fn get_remaining_criteria(&self) -> impl Iterator<Item = &str> {
191        self.criteria
192            .iter()
193            .filter(|&(_id, criterion)| !criterion.is_done())
194            .map(|(id, _criterion)| &**id)
195    }
196
197    /// get the completed criteria
198    #[inline]
199    pub(crate) fn get_completed_criteria(&self) -> impl Iterator<Item = &str> {
200        self.criteria
201            .iter()
202            .filter(|&(_id, criterion)| criterion.is_done())
203            .map(|(id, _criterion)| &**id)
204    }
205}
206/// represent a timestamp to when the criterion has been completed or None if it still un completed
207#[derive(Clone, Serialize, Deserialize, Debug, Default)]
208pub struct CriterionProgress(pub Option<SystemTime>);
209
210impl CriterionProgress {
211    /// grant the progress
212    pub fn grant(&mut self) {
213        self.0 = Some(SystemTime::now());
214    }
215
216    /// revoke the progress
217    pub const fn revoke(&mut self) {
218        self.0 = None;
219    }
220
221    /// return whether the criterion has been completed
222    #[must_use]
223    pub const fn is_done(&self) -> bool {
224        self.0.is_some()
225    }
226}