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