pp/
pp.rs

1use rosu_pp::Beatmap;
2use rosu_memory_lib::init_loop;
3use rosu_memory_lib::reader::beatmap::stable::file::get_beatmap_path;
4use rosu_pp::{Difficulty, Performance};
5use rosu_memory_lib::reader::common::stable::memory::get_menu_mods;
6use rosu_mods::GameModsLegacy;
7use std::time::Duration;
8use eyre::Result;
9use rosu_mem::process::{Process};
10use rosu_memory_lib::reader::structs::State;
11
12/// This example demonstrates how to calculate PP (Performance Points) in real-time
13/// for the currently selected beatmap in osu!stable, taking into account active mods.
14///
15/// The program will:
16/// 1. Initialize a connection to the osu! process
17/// 2. Continuously monitor the selected beatmap and mods
18/// 3. Calculate and display the PP value for the current beatmap + mods combination
19///
20/// Optimizations:
21/// - Caches the beatmap to avoid reloading the same file multiple times
22/// - Only updates PP display when values actually change
23/// - Converts mod bits to human-readable format
24///
25///   Represents the current state of the PP calculator
26struct CalculatorState {
27    current_pp: f64,
28    current_mods: i32,
29    current_beatmap: Beatmap,
30    current_beatmap_path: String,
31}
32
33impl CalculatorState {
34    /// Creates a new calculator state with default values
35    fn new() -> Self {
36        Self {
37            current_pp: 0.0,
38            current_mods: 0,
39            current_beatmap: Beatmap::default(),
40            current_beatmap_path: String::new(),
41        }
42    }
43
44    /// Updates the mods if they have changed and returns whether an update occurred
45    fn update_mods(&mut self, new_mods: i32) -> bool {
46        if new_mods != self.current_mods {
47            self.current_mods = new_mods;
48            
49            // Convert mod bits to human-readable format
50            let mods_readable = GameModsLegacy::from_bits(self.current_mods as u32).to_string();
51            println!("Mods: {mods_readable}");
52            true
53        } else {
54            false
55        }
56    }
57
58    /// Attempts to load a new beatmap if the path has changed
59    fn update_beatmap(&mut self, new_path: String) -> Result<bool> {
60        if new_path != self.current_beatmap_path {
61            println!("Loading new beatmap: {new_path}");
62            
63            // Load and validate the new beatmap
64            let beatmap = Beatmap::from_path(&new_path)?;
65            if let Err(suspicion) = beatmap.check_suspicion() {
66                eprintln!("Warning: Suspicious beatmap detected: {suspicion:?}");
67                return Ok(false);
68            }
69
70            // Update cached beatmap
71            self.current_beatmap = beatmap;
72            self.current_beatmap_path = new_path;
73            println!("Beatmap loaded successfully!");
74            Ok(true)
75        } else {
76            Ok(false)
77        }
78    }
79
80    /// Calculates and updates PP if the value has changed
81    fn update_pp(&mut self) {
82        let diff_attrs = Difficulty::new()
83            .mods(self.current_mods as u32)
84            .calculate(&self.current_beatmap);
85
86        let new_pp = Performance::new(diff_attrs).calculate().pp();
87        if (new_pp - self.current_pp).abs() > f64::EPSILON {
88            self.current_pp = new_pp;
89            println!("PP for current beatmap: {:.2}", self.current_pp);
90        }
91    }
92}
93
94/// Processes a single iteration of the monitoring loop
95fn process_game_state(process: &Process, state: &mut State, calc_state: &mut CalculatorState) -> Result<()> {
96    let mut mods_changed = false;
97    match get_beatmap_path(process, state) {
98        Ok(beatmap_path) => {
99            // Update mods if they changed
100            if let Ok(new_mods) = get_menu_mods(process, state) {
101                mods_changed = calc_state.update_mods(new_mods);
102            }
103
104            // Update beatmap if path changed and mods changed else it's useless to recalculate 
105            if calc_state.update_beatmap(beatmap_path)? && mods_changed {
106                calc_state.update_pp(); 
107            }
108        }
109        Err(e) => {
110            eprintln!("Failed to read beatmap path: {e}");
111        }
112    }
113    Ok(())
114}
115
116fn main() -> Result<()> {
117    // Initialize connection to osu! process, checking every 500ms
118    let (mut state, process) = init_loop(500)?;
119    println!("Successfully connected to osu! process!");
120
121    // Initialize calculator state
122    let mut calc_state = CalculatorState::new();
123
124    // Main monitoring loop
125    loop {
126        if let Err(e) = process_game_state(&process, &mut state, &mut calc_state) {
127            eprintln!("Error during processing: {e}");
128        }
129
130        // Wait before next check to avoid excessive CPU usage
131        std::thread::sleep(Duration::from_millis(1000));
132    }
133}