rosu_pp/catch/difficulty/
gradual.rs1use std::cmp;
2
3use rosu_map::section::general::GameMode;
4
5use crate::{
6 Beatmap, Difficulty,
7 any::{CalculateError, difficulty::skills::StrainSkill},
8 catch::{
9 CatchDifficultyAttributes,
10 attributes::{GradualObjectCount, ObjectCountBuilder},
11 catcher::Catcher,
12 convert::convert_objects,
13 },
14 model::mode::ConvertError,
15};
16
17use super::{
18 CatchDifficultySetup, DifficultyValues, object::CatchDifficultyObject,
19 skills::movement::Movement,
20};
21
22pub struct CatchGradualDifficulty {
59 pub(crate) idx: usize,
60 pub(crate) difficulty: Difficulty,
61 attrs: CatchDifficultyAttributes,
62 count: Vec<GradualObjectCount>,
64 diff_objects: Box<[CatchDifficultyObject]>,
65 movement: Movement,
66}
67
68impl CatchGradualDifficulty {
69 pub fn new(difficulty: Difficulty, map: &Beatmap) -> Result<Self, ConvertError> {
71 let map = super::prepare_map(&difficulty, map)?;
72
73 Ok(new(difficulty, &map))
74 }
75
76 pub fn checked_new(difficulty: Difficulty, map: &Beatmap) -> Result<Self, CalculateError> {
79 let map = super::prepare_map(&difficulty, map)?;
80 map.check_suspicion()?;
81
82 Ok(new(difficulty, &map))
83 }
84}
85
86fn new(difficulty: Difficulty, map: &Beatmap) -> CatchGradualDifficulty {
87 debug_assert_eq!(map.mode, GameMode::Catch);
88
89 let clock_rate = difficulty.get_clock_rate();
90
91 let CatchDifficultySetup { map_attrs, attrs } = CatchDifficultySetup::new(&difficulty, map);
92
93 let hr_offsets = difficulty.get_hardrock_offsets();
94 let reflection = difficulty.get_mods().reflection();
95 let mut count = ObjectCountBuilder::new_gradual();
96
97 let palpable_objects = convert_objects(map, &mut count, reflection, hr_offsets, map_attrs.cs());
98
99 let mut half_catcher_width = Catcher::calculate_catch_width(map_attrs.cs()) * 0.5;
100 half_catcher_width *= 1.0 - ((map_attrs.cs() - 5.5).max(0.0) * 0.0625);
101
102 let diff_objects = DifficultyValues::create_difficulty_objects(
103 clock_rate,
104 half_catcher_width,
105 palpable_objects.iter(),
106 );
107
108 let count = count.into_gradual();
109 let movement = Movement::new(clock_rate);
110
111 CatchGradualDifficulty {
112 idx: 0,
113 difficulty,
114 attrs,
115 count,
116 diff_objects,
117 movement,
118 }
119}
120
121impl Iterator for CatchGradualDifficulty {
122 type Item = CatchDifficultyAttributes;
123
124 fn next(&mut self) -> Option<Self::Item> {
125 if self.idx > 0 {
130 let curr = self.diff_objects.get(self.idx - 1)?;
131 self.movement.process(curr, &self.diff_objects);
132 } else if self.count.is_empty() {
133 return None;
134 }
135
136 self.attrs.add_object_count(self.count[self.idx]);
137 self.idx += 1;
138
139 let mut attrs = self.attrs.clone();
140
141 let movement = self.movement.cloned_difficulty_value();
142 DifficultyValues::eval(&mut attrs, movement);
143
144 Some(attrs)
145 }
146
147 fn size_hint(&self) -> (usize, Option<usize>) {
148 let len = self.len();
149
150 (len, Some(len))
151 }
152
153 fn nth(&mut self, n: usize) -> Option<Self::Item> {
154 let skip_iter = self.diff_objects.iter().skip(self.idx.saturating_sub(1));
155
156 let mut take = cmp::min(n, self.len().saturating_sub(1));
157
158 if self.idx == 0 && take > 0 {
160 take -= 1;
161 self.attrs.add_object_count(self.count[self.idx]);
162 self.idx += 1;
163 }
164
165 for curr in skip_iter.take(take) {
166 self.movement.process(curr, &self.diff_objects);
167
168 self.attrs.add_object_count(self.count[self.idx]);
169 self.idx += 1;
170 }
171
172 self.next()
173 }
174}
175
176impl ExactSizeIterator for CatchGradualDifficulty {
177 fn len(&self) -> usize {
178 self.diff_objects.len() + 1 - self.idx
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use crate::{Beatmap, catch::Catch};
185
186 use super::*;
187
188 #[test]
189 fn empty() {
190 let map = Beatmap::from_bytes(&[]).unwrap();
191 let mut gradual = CatchGradualDifficulty::new(Difficulty::new(), &map).unwrap();
192 assert!(gradual.next().is_none());
193 }
194
195 #[test]
196 fn next_and_nth() {
197 let map = Beatmap::from_path("./resources/2118524.osu").unwrap();
198
199 let difficulty = Difficulty::new();
200
201 let mut gradual = CatchGradualDifficulty::new(difficulty.clone(), &map).unwrap();
202 let mut gradual_2nd = CatchGradualDifficulty::new(difficulty.clone(), &map).unwrap();
203 let mut gradual_3rd = CatchGradualDifficulty::new(difficulty.clone(), &map).unwrap();
204
205 for i in 1.. {
206 let Some(next_gradual) = gradual.next() else {
207 assert_eq!(i, 731);
208 assert!(gradual_2nd.last().is_none()); assert!(gradual_3rd.last().is_some()); break;
211 };
212
213 if i % 2 == 0 {
214 let next_gradual_2nd = gradual_2nd.nth(1).unwrap();
215 assert_eq!(next_gradual, next_gradual_2nd);
216 }
217
218 if i % 3 == 0 {
219 let next_gradual_3rd = gradual_3rd.nth(2).unwrap();
220 assert_eq!(next_gradual, next_gradual_3rd);
221 }
222
223 let expected = difficulty
224 .clone()
225 .passed_objects(i as u32)
226 .calculate_for_mode::<Catch>(&map)
227 .unwrap();
228
229 assert_eq!(next_gradual, expected);
230 }
231 }
232}