@@ -2,35 +2,138 @@ use std::collections::HashMap;
22use std:: sync:: Arc ;
33
44use parking_lot:: RwLock ;
5+ use wows_replays:: analyzer:: battle_controller:: BattleResult ;
56use wowsunpack:: game_params:: provider:: GameMetadataProvider ;
67
78use crate :: personal_rating:: PersonalRatingData ;
89use crate :: personal_rating:: PersonalRatingResult ;
910use crate :: personal_rating:: ShipBattleStats ;
11+ use crate :: tab_state:: ChartableStat ;
1012use crate :: ui:: replay_parser:: Replay ;
1113
12- /// Performance statistics for a single ship across multiple games
14+ /// Per-game statistics extracted from a single replay
15+ #[ derive( Clone ) ]
16+ pub struct PerGameStat {
17+ pub ship_name : String ,
18+ pub ship_id : u64 ,
19+ #[ allow( dead_code) ]
20+ pub game_time : String ,
21+ pub damage : u64 ,
22+ pub spotting_damage : u64 ,
23+ pub frags : i64 ,
24+ pub raw_xp : i64 ,
25+ pub base_xp : i64 ,
26+ pub is_win : bool ,
27+ pub is_loss : bool ,
28+ }
29+
30+ impl PerGameStat {
31+ /// Create a PerGameStat from a replay
32+ pub fn from_replay ( replay : & Replay , metadata_provider : & GameMetadataProvider ) -> Option < Self > {
33+ let ui_report = replay. ui_report . as_ref ( ) ?;
34+ let self_report = ui_report. player_reports ( ) . iter ( ) . find ( |r| r. relation ( ) . is_self ( ) ) ?;
35+ let ship_name = replay. vehicle_name ( metadata_provider) ;
36+ let ship_id = replay. player_vehicle ( ) ?. shipId ;
37+ let game_time = replay. game_time ( ) . to_string ( ) ;
38+ let battle_result = replay. battle_result ( ) ;
39+
40+ Some ( PerGameStat {
41+ ship_name,
42+ ship_id,
43+ game_time,
44+ damage : self_report. actual_damage ( ) . unwrap_or_default ( ) ,
45+ spotting_damage : self_report. spotting_damage ( ) . unwrap_or_default ( ) ,
46+ frags : self_report. kills ( ) . unwrap_or_default ( ) ,
47+ raw_xp : self_report. raw_xp ( ) . unwrap_or_default ( ) ,
48+ base_xp : self_report. base_xp ( ) . unwrap_or_default ( ) ,
49+ is_win : matches ! ( battle_result, Some ( BattleResult :: Win ( _) ) ) ,
50+ is_loss : matches ! ( battle_result, Some ( BattleResult :: Loss ( _) ) ) ,
51+ } )
52+ }
53+
54+ /// Get the value of a specific stat for charting
55+ pub fn get_stat ( & self , stat : ChartableStat , pr_data : Option < & PersonalRatingData > ) -> f64 {
56+ match stat {
57+ ChartableStat :: Damage => self . damage as f64 ,
58+ ChartableStat :: SpottingDamage => self . spotting_damage as f64 ,
59+ ChartableStat :: Frags => self . frags as f64 ,
60+ ChartableStat :: RawXp => self . raw_xp as f64 ,
61+ ChartableStat :: BaseXp => self . base_xp as f64 ,
62+ ChartableStat :: WinRate => 0.0 , // Win rate doesn't make sense per-game
63+ ChartableStat :: PersonalRating => self . calculate_pr ( pr_data) . unwrap_or ( 0.0 ) ,
64+ }
65+ }
66+
67+ /// Calculate Personal Rating for this single game
68+ pub fn calculate_pr ( & self , pr_data : Option < & PersonalRatingData > ) -> Option < f64 > {
69+ let pr_data = pr_data?;
70+ let stats = ShipBattleStats {
71+ ship_id : self . ship_id ,
72+ battles : 1 ,
73+ damage : self . damage ,
74+ wins : if self . is_win { 1 } else { 0 } ,
75+ frags : self . frags ,
76+ } ;
77+ pr_data. calculate_pr ( & [ stats] ) . map ( |r| r. pr )
78+ }
79+ }
80+
81+ /// Performance statistics for a single ship aggregated from multiple games
1382#[ derive( Default ) ]
1483pub struct PerformanceInfo {
1584 ship_id : Option < u64 > ,
1685 wins : usize ,
1786 losses : usize ,
18- /// Total frags
1987 total_frags : i64 ,
20- /// Highest frags in a single match
2188 max_frags : i64 ,
2289 total_damage : u64 ,
2390 max_damage : u64 ,
2491 total_games : usize ,
2592 max_xp : i64 ,
2693 max_win_adjusted_xp : i64 ,
27- total_xp : usize ,
28- total_win_adjusted_xp : usize ,
94+ total_xp : i64 ,
95+ total_win_adjusted_xp : i64 ,
2996 max_spotting_damage : u64 ,
3097 total_spotting_damage : u64 ,
3198}
3299
33100impl PerformanceInfo {
101+ /// Create a PerformanceInfo by aggregating multiple PerGameStat instances
102+ pub fn from_games ( games : & [ & PerGameStat ] ) -> Self {
103+ let mut info = PerformanceInfo :: default ( ) ;
104+
105+ for game in games {
106+ if info. ship_id . is_none ( ) {
107+ info. ship_id = Some ( game. ship_id ) ;
108+ }
109+
110+ if game. is_win {
111+ info. wins += 1 ;
112+ } else if game. is_loss {
113+ info. losses += 1 ;
114+ }
115+
116+ info. total_frags += game. frags ;
117+ info. max_frags = info. max_frags . max ( game. frags ) ;
118+
119+ info. total_damage += game. damage ;
120+ info. max_damage = info. max_damage . max ( game. damage ) ;
121+
122+ info. total_spotting_damage += game. spotting_damage ;
123+ info. max_spotting_damage = info. max_spotting_damage . max ( game. spotting_damage ) ;
124+
125+ info. total_xp += game. raw_xp ;
126+ info. max_xp = info. max_xp . max ( game. raw_xp ) ;
127+
128+ info. total_win_adjusted_xp += game. base_xp ;
129+ info. max_win_adjusted_xp = info. max_win_adjusted_xp . max ( game. base_xp ) ;
130+
131+ info. total_games += 1 ;
132+ }
133+
134+ info
135+ }
136+
34137 pub fn wins ( & self ) -> usize {
35138 self . wins
36139 }
@@ -153,6 +256,32 @@ impl SessionStats {
153256 }
154257
155258 self . session_replays . push ( replay) ;
259+ self . session_replays . sort_by ( |a, b| a. read ( ) . game_time ( ) . cmp ( b. read ( ) . game_time ( ) ) ) ;
260+ }
261+
262+ /// Get per-game statistics for all replays in the session
263+ pub fn per_game_stats ( & self , metadata_provider : & GameMetadataProvider ) -> Vec < PerGameStat > {
264+ self . session_replays
265+ . iter ( )
266+ . filter_map ( |replay| {
267+ let replay = replay. read ( ) ;
268+ PerGameStat :: from_replay ( & replay, metadata_provider)
269+ } )
270+ . collect ( )
271+ }
272+
273+ /// Get aggregated ship statistics derived from per-game stats
274+ pub fn ship_stats ( & self , metadata_provider : & GameMetadataProvider ) -> HashMap < String , PerformanceInfo > {
275+ let per_game = self . per_game_stats ( metadata_provider) ;
276+
277+ // Group by ship name
278+ let mut by_ship: HashMap < String , Vec < & PerGameStat > > = HashMap :: new ( ) ;
279+ for game in & per_game {
280+ by_ship. entry ( game. ship_name . clone ( ) ) . or_default ( ) . push ( game) ;
281+ }
282+
283+ // Convert to PerformanceInfo
284+ by_ship. into_iter ( ) . map ( |( name, games) | ( name, PerformanceInfo :: from_games ( & games) ) ) . collect ( )
156285 }
157286
158287 /// Returns the win rate percentage for this session. Will return `None`
@@ -173,131 +302,27 @@ impl SessionStats {
173302 /// Total number of games won in the current session
174303 pub fn games_won ( & self ) -> usize {
175304 self . session_replays . iter ( ) . fold ( 0 , |accum, replay| {
176- if let Some ( wows_replays:: analyzer:: battle_controller:: BattleResult :: Win ( _) ) = replay. read ( ) . battle_result ( )
177- {
178- accum + 1
179- } else {
180- accum
181- }
305+ if let Some ( BattleResult :: Win ( _) ) = replay. read ( ) . battle_result ( ) { accum + 1 } else { accum }
182306 } )
183307 }
184308
185309 /// Total number of games lost in the current session
186310 pub fn games_lost ( & self ) -> usize {
187311 self . session_replays . iter ( ) . fold ( 0 , |accum, replay| {
188- if let Some ( wows_replays:: analyzer:: battle_controller:: BattleResult :: Loss ( _) ) =
189- replay. read ( ) . battle_result ( )
190- {
191- accum + 1
192- } else {
193- accum
194- }
312+ if let Some ( BattleResult :: Loss ( _) ) = replay. read ( ) . battle_result ( ) { accum + 1 } else { accum }
195313 } )
196314 }
197315
198- pub fn ship_stats ( & self , metadata_provider : & GameMetadataProvider ) -> HashMap < String , PerformanceInfo > {
199- let mut results: HashMap < String , PerformanceInfo > = HashMap :: new ( ) ;
200-
201- for replay in & self . session_replays {
202- let replay = replay. read ( ) ;
203- let Some ( battle_result) = replay. battle_result ( ) else {
204- continue ;
205- } ;
206-
207- let ship_name = replay. vehicle_name ( metadata_provider) ;
208- let ship_id = replay. player_vehicle ( ) . map ( |v| v. shipId ) ;
209- let performance_info = results. entry ( ship_name) . or_default ( ) ;
210-
211- // Set ship_id if not already set
212- if performance_info. ship_id . is_none ( ) {
213- performance_info. ship_id = ship_id;
214- }
215-
216- match battle_result {
217- wows_replays:: analyzer:: battle_controller:: BattleResult :: Win ( _) => {
218- performance_info. wins += 1 ;
219- }
220- wows_replays:: analyzer:: battle_controller:: BattleResult :: Loss ( _) => {
221- performance_info. losses += 1 ;
222- }
223- wows_replays:: analyzer:: battle_controller:: BattleResult :: Draw => {
224- // do nothing for draws at the moment
225- }
226- }
227-
228- let Some ( ui_report) = replay. ui_report . as_ref ( ) else {
229- continue ;
230- } ;
231- let Some ( self_report) = ui_report. player_reports ( ) . iter ( ) . find ( |report| report. relation ( ) . is_self ( ) ) else {
232- continue ;
233- } ;
234-
235- performance_info. total_frags += self_report. kills ( ) . unwrap_or_default ( ) ;
236- performance_info. max_frags = performance_info. max_frags . max ( self_report. kills ( ) . unwrap_or_default ( ) ) ;
237-
238- performance_info. total_damage += self_report. actual_damage ( ) . unwrap_or_default ( ) ;
239- performance_info. max_damage =
240- performance_info. max_damage . max ( self_report. actual_damage ( ) . unwrap_or_default ( ) ) ;
241-
242- performance_info. total_spotting_damage += self_report. spotting_damage ( ) . unwrap_or_default ( ) ;
243- performance_info. max_spotting_damage =
244- performance_info. max_spotting_damage . max ( self_report. spotting_damage ( ) . unwrap_or_default ( ) ) ;
245-
246- performance_info. total_xp += self_report. raw_xp ( ) . unwrap_or_default ( ) as usize ;
247- performance_info. max_xp = performance_info. max_xp . max ( self_report. raw_xp ( ) . unwrap_or_default ( ) ) ;
248-
249- performance_info. total_win_adjusted_xp += self_report. base_xp ( ) . unwrap_or_default ( ) as usize ;
250- performance_info. max_win_adjusted_xp =
251- performance_info. max_win_adjusted_xp . max ( self_report. base_xp ( ) . unwrap_or_default ( ) ) ;
252-
253- performance_info. total_games += 1 ;
254- }
255-
256- results
257- }
258-
259316 pub fn max_damage ( & self , metadata_provider : & GameMetadataProvider ) -> Option < ( String , u64 ) > {
260- self . session_replays
261- . iter ( )
262- . filter_map ( |replay| {
263- let replay = replay. read ( ) ;
264-
265- let ui_report = replay. ui_report . as_ref ( ) ?;
266- let self_report = ui_report. player_reports ( ) . iter ( ) . find ( |report| report. relation ( ) . is_self ( ) ) ?;
267-
268- Some ( ( replay. vehicle_name ( metadata_provider) , self_report. actual_damage ( ) ?) )
269- } )
270- . max_by_key ( |result| result. 1 )
317+ self . per_game_stats ( metadata_provider) . into_iter ( ) . map ( |g| ( g. ship_name , g. damage ) ) . max_by_key ( |r| r. 1 )
271318 }
272319
273320 pub fn max_frags ( & self , metadata_provider : & GameMetadataProvider ) -> Option < ( String , i64 ) > {
274- self . session_replays
275- . iter ( )
276- . filter_map ( |replay| {
277- let replay = replay. read ( ) ;
278-
279- let ui_report = replay. ui_report . as_ref ( ) ?;
280- let self_report = ui_report. player_reports ( ) . iter ( ) . find ( |report| report. relation ( ) . is_self ( ) ) ?;
281-
282- Some ( ( replay. vehicle_name ( metadata_provider) , self_report. kills ( ) ?) )
283- } )
284- . max_by_key ( |result| result. 1 )
321+ self . per_game_stats ( metadata_provider) . into_iter ( ) . map ( |g| ( g. ship_name , g. frags ) ) . max_by_key ( |r| r. 1 )
285322 }
286323
287- pub fn total_frags ( & self ) -> i64 {
288- self . session_replays . iter ( ) . fold ( 0 , |accum, replay| {
289- let replay = replay. read ( ) ;
290-
291- let Some ( ui_report) = replay. ui_report . as_ref ( ) else {
292- return accum;
293- } ;
294-
295- let Some ( self_report) = ui_report. player_reports ( ) . iter ( ) . find ( |report| report. relation ( ) . is_self ( ) ) else {
296- return accum;
297- } ;
298-
299- accum + self_report. kills ( ) . unwrap_or_default ( )
300- } )
324+ pub fn total_frags ( & self , metadata_provider : & GameMetadataProvider ) -> i64 {
325+ self . per_game_stats ( metadata_provider) . iter ( ) . map ( |g| g. frags ) . sum ( )
301326 }
302327
303328 /// Calculate overall Personal Rating for this session
0 commit comments