rosu_pp/model/beatmap/attributes/
builder.rs1use rosu_map::section::general::GameMode;
2
3use crate::{
4 Beatmap, Difficulty, GameMods,
5 model::beatmap::{
6 BeatmapAttributes,
7 attributes::{ModStatus, attribute::BeatmapAttribute, difficulty::BeatmapDifficulty},
8 },
9};
10
11#[derive(Clone, Debug, PartialEq)]
13#[must_use]
14pub struct BeatmapAttributesBuilder {
15 mode: GameMode,
16 is_convert: bool,
17 difficulty: BeatmapDifficulty,
18 mods: GameMods,
19 clock_rate: Option<f64>,
20}
21
22#[rustfmt::skip]
23macro_rules! set_attr {
24 ( $short:ident = $long:literal ) => {
25 #[doc = concat!(
26 "Specify the ", $long,
27 ". \n\n`fixed` determines if the given value should be used before \
28 or after accounting for mods, e.g. on `true` the value will be used \
29 as-is and on `false` it will be modified based on the mods."
30 )]
31 pub const fn $short(&mut self, $short: f32, fixed: bool) -> &mut Self {
32 self.difficulty.$short = if fixed {
33 BeatmapAttribute::Fixed($short)
34 } else {
35 BeatmapAttribute::Given($short)
36 };
37
38 self
39 }
40 };
41}
42
43impl BeatmapAttributesBuilder {
44 pub const fn new() -> Self {
48 Self {
49 mode: GameMode::Osu,
50 is_convert: false,
51 difficulty: BeatmapDifficulty::DEFAULT,
52 mods: GameMods::DEFAULT,
53 clock_rate: None,
54 }
55 }
56
57 pub const fn map(&mut self, map: &Beatmap) -> &mut Self {
59 self.mode = map.mode;
60 self.is_convert = map.is_convert;
61
62 self.difficulty = BeatmapDifficulty {
63 ar: BeatmapAttribute::Value(map.ar.clamp(0.0, 10.0)),
65 od: BeatmapAttribute::Value(map.od.clamp(0.0, 10.0)),
66 cs: BeatmapAttribute::Value(map.cs),
67 hp: BeatmapAttribute::Value(map.hp),
68 };
69
70 self
71 }
72
73 set_attr!(ar = "approach rate");
74
75 set_attr!(od = "overall difficulty");
76
77 set_attr!(cs = "circle size");
78
79 set_attr!(hp = "drain rate");
80
81 pub fn mods(&mut self, mods: impl Into<GameMods>) -> &mut Self {
92 self.mods = mods.into();
93
94 self
95 }
96
97 pub const fn clock_rate(&mut self, clock_rate: f64) -> &mut Self {
99 self.clock_rate = Some(clock_rate);
100
101 self
102 }
103
104 pub const fn mode(&mut self, mode: GameMode, is_convert: bool) -> &mut Self {
106 self.mode = mode;
107 self.is_convert = is_convert;
108
109 self
110 }
111
112 pub fn difficulty(&mut self, difficulty: &Difficulty) -> &mut Self {
114 let map_diff = difficulty.get_map_difficulty();
115
116 self.difficulty = BeatmapDifficulty {
117 ar: self.difficulty.ar.overwrite(map_diff.ar),
118 cs: self.difficulty.cs.overwrite(map_diff.cs),
119 hp: self.difficulty.hp.overwrite(map_diff.hp),
120 od: self.difficulty.od.overwrite(map_diff.od),
121 };
122 self.mods = difficulty.get_mods().clone();
123 self.clock_rate = Some(difficulty.get_clock_rate());
124
125 self
126 }
127
128 pub fn build(&self) -> BeatmapAttributes {
130 let mods = &self.mods;
131
132 let mut difficulty = self.difficulty.clone();
133 difficulty.apply_mods(mods, self.mode);
134
135 BeatmapAttributes {
136 difficulty,
137 clock_rate: self.clock_rate.unwrap_or_else(|| mods.clock_rate()),
138 mod_status: ModStatus::new(mods),
139 mode: self.mode,
140 is_convert: self.is_convert,
141 classic_and_not_v2: mods.cl() && !mods.sv2(),
142 }
143 }
144}
145
146impl Default for BeatmapAttributesBuilder {
147 fn default() -> Self {
148 Self::new()
149 }
150}
151
152impl From<&Beatmap> for BeatmapAttributesBuilder {
153 fn from(map: &Beatmap) -> Self {
154 let mut this = Self::new();
155 this.map(map);
156
157 this
158 }
159}
160
161impl From<&Difficulty> for BeatmapAttributesBuilder {
162 fn from(difficulty: &Difficulty) -> Self {
163 let mut this = Self::new();
164 this.difficulty(difficulty);
165
166 this
167 }
168}