@@ -13,7 +13,7 @@ use std::sync::mpsc::Receiver;
1313use std:: sync:: mpsc:: Sender ;
1414use std:: sync:: mpsc:: TryRecvError ;
1515use std:: sync:: mpsc:: { self } ;
16- use std :: thread :: current ;
16+
1717use std:: time:: Duration ;
1818use std:: time:: Instant ;
1919
@@ -69,6 +69,7 @@ use wowsunpack::game_params::provider::GameMetadataProvider;
6969use crate :: error:: ToolkitError ;
7070use crate :: game_params:: game_params_bin_path;
7171use crate :: icons;
72+ use crate :: personal_rating:: PersonalRatingData ;
7273use crate :: plaintext_viewer:: PlaintextFileViewer ;
7374use crate :: task:: BackgroundParserThread ;
7475use crate :: task:: BackgroundTask ;
@@ -567,7 +568,14 @@ impl SessionStats {
567568 } ;
568569
569570 let ship_name = replay. vehicle_name ( metadata_provider) ;
571+ let ship_id = replay. player_vehicle ( ) . map ( |v| v. shipId as u64 ) ;
570572 let performance_info = results. entry ( ship_name) . or_default ( ) ;
573+
574+ // Set ship_id if not already set
575+ if performance_info. ship_id . is_none ( ) {
576+ performance_info. ship_id = ship_id;
577+ }
578+
571579 match battle_result {
572580 wows_replays:: analyzer:: battle_controller:: BattleResult :: Win ( _) => {
573581 performance_info. wins += 1 ;
@@ -647,10 +655,46 @@ impl SessionStats {
647655 accum + self_report. kills ( ) . unwrap_or_default ( )
648656 } )
649657 }
658+
659+ /// Calculate overall Personal Rating for this session
660+ pub fn calculate_pr ( & self , pr_data : & crate :: personal_rating:: PersonalRatingData ) -> Option < crate :: personal_rating:: PersonalRatingResult > {
661+ let stats: Vec < _ > = self . session_replays . iter ( ) . filter_map ( |replay| replay. read ( ) . to_battle_stats ( ) ) . collect ( ) ;
662+ pr_data. calculate_pr ( & stats)
663+ }
664+
665+ /// Calculate Personal Rating per ship for this session
666+ /// Returns a map of ship_id -> PR result
667+ #[ allow( dead_code) ]
668+ pub fn calculate_pr_per_ship ( & self , pr_data : & crate :: personal_rating:: PersonalRatingData ) -> HashMap < u64 , crate :: personal_rating:: PersonalRatingResult > {
669+ use crate :: personal_rating:: ShipBattleStats ;
670+
671+ // Group stats by ship_id
672+ let mut ship_stats: HashMap < u64 , ShipBattleStats > = HashMap :: new ( ) ;
673+
674+ for replay in & self . session_replays {
675+ if let Some ( stats) = replay. read ( ) . to_battle_stats ( ) {
676+ let entry = ship_stats. entry ( stats. ship_id ) . or_insert ( ShipBattleStats { ship_id : stats. ship_id , battles : 0 , damage : 0 , wins : 0 , frags : 0 } ) ;
677+ entry. battles += stats. battles ;
678+ entry. damage += stats. damage ;
679+ entry. wins += stats. wins ;
680+ entry. frags += stats. frags ;
681+ }
682+ }
683+
684+ // Calculate PR for each ship
685+ ship_stats
686+ . into_iter ( )
687+ . filter_map ( |( ship_id, stats) | {
688+ let pr = pr_data. calculate_pr ( & [ stats] ) ?;
689+ Some ( ( ship_id, pr) )
690+ } )
691+ . collect ( )
692+ }
650693}
651694
652695#[ derive( Default ) ]
653696pub struct PerformanceInfo {
697+ ship_id : Option < u64 > ,
654698 wins : usize ,
655699 losses : usize ,
656700 /// Total frags
@@ -743,6 +787,19 @@ impl PerformanceInfo {
743787 }
744788 Some ( self . total_win_adjusted_xp as f64 / self . total_games as f64 )
745789 }
790+
791+ /// Calculate Personal Rating for this ship's performance
792+ pub fn calculate_pr ( & self , pr_data : & crate :: personal_rating:: PersonalRatingData ) -> Option < crate :: personal_rating:: PersonalRatingResult > {
793+ let ship_id = self . ship_id ?;
794+ let stats = crate :: personal_rating:: ShipBattleStats {
795+ ship_id,
796+ battles : self . total_games as u32 ,
797+ damage : self . total_damage ,
798+ wins : self . wins as u32 ,
799+ frags : self . total_frags ,
800+ } ;
801+ pr_data. calculate_pr ( & [ stats] )
802+ }
746803}
747804
748805#[ derive( Serialize , Deserialize ) ]
@@ -842,6 +899,8 @@ pub struct TabState {
842899 pub show_session_stats : bool ,
843900 #[ serde( skip) ]
844901 pub session_stats : SessionStats ,
902+ #[ serde( skip) ]
903+ pub personal_rating_data : Arc < RwLock < PersonalRatingData > > ,
845904}
846905
847906impl Default for TabState {
@@ -884,6 +943,7 @@ impl Default for TabState {
884943 parser_lock : Arc :: new ( parking_lot:: Mutex :: new ( ( ) ) ) ,
885944 show_session_stats : false ,
886945 session_stats : Default :: default ( ) ,
946+ personal_rating_data : Arc :: new ( RwLock :: new ( PersonalRatingData :: new ( ) ) ) ,
887947 }
888948 }
889949}
@@ -1314,6 +1374,9 @@ impl WowsToolkitApp {
13141374 BackgroundTaskKind :: ModTask ( _task_info) => {
13151375 // do nothing
13161376 }
1377+ BackgroundTaskKind :: LoadingPersonalRatingData => {
1378+ // do nothing
1379+ }
13171380 BackgroundTaskKind :: UpdateTimedMessage ( timed_message) => {
13181381 self . tab_state . timed_message . write ( ) . replace ( timed_message. clone ( ) ) ;
13191382 }
@@ -1369,7 +1432,6 @@ impl WowsToolkitApp {
13691432 // Rename this process
13701433 let rename_process = move || {
13711434 std:: fs:: rename ( current_process. clone ( ) , & current_process_new_path) . context ( "failed to rename current process" ) ?;
1372-
13731435 // Rename the new exe
13741436 std:: fs:: rename ( new_exe, & current_process) . context ( "failed to rename new process" ) ?;
13751437
@@ -1391,6 +1453,9 @@ impl WowsToolkitApp {
13911453 BackgroundTaskCompletion :: ConstantsLoaded ( constants) => {
13921454 * self . tab_state . game_constants . write ( ) = constants;
13931455 }
1456+ BackgroundTaskCompletion :: PersonalRatingDataLoaded ( pr_data) => {
1457+ self . tab_state . personal_rating_data . write ( ) . load ( pr_data) ;
1458+ }
13941459 #[ cfg( feature = "mod_manager" ) ]
13951460 BackgroundTaskCompletion :: ModManager ( mod_manager_info) => {
13961461 match * mod_manager_info {
@@ -1547,6 +1612,16 @@ impl WowsToolkitApp {
15471612 update_background_task ! ( self . tab_state. background_tasks, Some ( task:: load_constants( constants_updates) ) ) ;
15481613 }
15491614 }
1615+
1616+ // Check and update PR expected values
1617+ if crate :: personal_rating:: needs_update ( ) {
1618+ if let Ok ( pr_data) = self . runtime . block_on ( crate :: personal_rating:: fetch_expected_values ( ) ) {
1619+ if crate :: personal_rating:: save_expected_values ( & pr_data) . is_ok ( ) {
1620+ update_background_task ! ( self . tab_state. background_tasks, Some ( task:: load_personal_rating_data( pr_data) ) ) ;
1621+ }
1622+ }
1623+ }
1624+
15501625 self . checked_for_updates = true ;
15511626 }
15521627
@@ -1880,7 +1955,11 @@ fn build_about_window(ui: &mut egui::Ui) {
18801955 ui. vertical ( |ui| {
18811956 ui. label ( "Made by landaire." ) ;
18821957 ui. label ( "Thanks to Trackpad, TTaro, lkolbly for their contributions." ) ;
1883- if ui. button ( "View on GitHub" ) . clicked ( ) {
1958+ ui. horizontal ( |ui| {
1959+ ui. label ( "Personal rating (PR) calculation data and formula provided by WoWs Numbers." ) ;
1960+ ui. hyperlink_to ( "More Info." , "https://wows-numbers.com/personal/rating" ) ;
1961+ } ) ;
1962+ if ui. button ( "View Project on GitHub" ) . clicked ( ) {
18841963 ui. ctx ( ) . open_url ( OpenUrl :: new_tab ( "https://github.com/landaire/wows-toolkit" ) ) ;
18851964 }
18861965
0 commit comments