import { EventEmitter , Injectable } from '@angular/core' ; import { BgsBestStat , buildNewStats , Input as BgsComputeRunStatsInput } from '@firestone-hs/user-bgs-post-match-stats' ; import Worker from 'worker-loader!../../workers/bgs-post-match-stats.worker' ; import { BgsGame } from '../../models/battlegrounds/bgs-game' ; import { BgsPostMatchStatsForReview } from '../../models/battlegrounds/bgs-post-match-stats-for-review' ; import { BgsPostMatchStats } from '../../models/battlegrounds/post-match/bgs-post-match-stats' ; import { ApiRunner } from '../api-runner' ; import { Events } from '../events.service' ; import { BgsPersonalStatsSelectHeroDetailsWithRemoteInfoEvent } from '../mainwindow/store/events/battlegrounds/bgs-personal-stats-select-hero-details-with-remote-info-event' ; import { BgsPostMatchStatsComputedEvent } from '../mainwindow/store/events/battlegrounds/bgs-post-match-stats-computed-event' ; import { MainWindowStoreEvent } from '../mainwindow/store/events/main-window-store-event' ; import { ShowMatchStatsEvent } from '../mainwindow/store/events/replays/show-match-stats-event' ; import { GameForUpload } from '../manastorm-bridge/game-for-upload' ; import { OverwolfService } from '../overwolf.service' ; import { UserService } from '../user.service' ; import { sleep } from '../utils' ; import { BgsGameEndEvent } from './store/events/bgs-game-end-event' ; import { BattlegroundsStoreEvent } from './store/events/_battlegrounds-store-event' ; import { RealTimeStatsState } from './store/real-time-stats/real-time-stats' ; const POST_MATCH_STATS_UPDATE_URL = 'https://api.firestoneapp.com/userBgsPostMatchStats/save/userBgsPostMatchStats/{proxy+}' ; const POST_MATCH_STATS_RETRIEVE_URL = 'https://api.firestoneapp.com/userBgsPostMatchStats/get/userBgsPostMatchStats/{proxy+}' ; @ Injectable ( ) export class BgsRunStatsService { private bgsStateUpdater : EventEmitter < BattlegroundsStoreEvent > ; private stateUpdater : EventEmitter < MainWindowStoreEvent > ; private worker : Worker = new Worker ( ) ; constructor ( private readonly apiRunner , private readonly events , private readonly ow , private readonly userService ) { this . events . on ( Events . START_BGS_RUN_STATS ) . subscribe ( async ( event ) => { this . computeRunStats ( event . data [ 0 ] , event . data [ 1 ] , event . data [ 2 ] , event . data [ 3 ] ) ; } ) ; this . events . on ( Events . POPULATE_HERO_DETAILS_FOR_BG ) . subscribe ( async ( event ) => { console . debug ( '[gr] POPULATE_HERO_DETAILS_FOR_BG' , event ) ; this . computeHeroDetailsForBg ( event . data [ 0 ] ) ; } ) ; setTimeout ( ( ) => { this . bgsStateUpdater = this . ow . getMainWindow ( ) . battlegroundsUpdater ; this . stateUpdater = this . ow . getMainWindow ( ) . mainWindowStoreUpdater ; } ) ; } public async retrieveReviewPostMatchStats ( reviewId ) { const results = await this . apiRunner . callPostApi < readonly BgsPostMatchStatsForReview [ ] > ( `${ POST_MATCH_STATS_RETRIEVE_URL }` , { reviewId : reviewId , } ) ; const result = results && results . length > 0 ? results [ 0 ] : null ; console . log ( '[bgs-run-stats] post-match results for review' , reviewId , results && results . length > 0 ) ; this . stateUpdater . next ( new ShowMatchStatsEvent ( reviewId , result ?. stats ) ) ; } private async computeHeroDetailsForBg ( heroCardId ) { console . debug ( '[gr] ready to reprodcess' , heroCardId ) ; const lastHeroPostMatchStats = await this . retrieveLastBgsRunStats ( heroCardId ) ; this . stateUpdater . next ( new BgsPersonalStatsSelectHeroDetailsWithRemoteInfoEvent ( lastHeroPostMatchStats , heroCardId ) ) ; } private async retrieveLastBgsRunStats ( heroCardId , numberOfStats ? ) { const user = await this . userService . getCurrentUser ( ) ; const input = { userId : user . userId , userName : user . username , heroCardId : heroCardId , limitResults : numberOfStats , } ; const results = await this . apiRunner . callPostApi < readonly BgsPostMatchStatsForReview [ ] > ( `${ POST_MATCH_STATS_RETRIEVE_URL }` , input ) ; console . debug ( '[bgs-run-stats] last run stats' , results ) ; return results ; } private async computeRunStats ( reviewId , currentGame , bestBgsUserStats , game ) { const newMmr = parseInt ( game . newPlayerRank ) ; const liveStats = currentGame . liveStats ; const user = await this . userService . getCurrentUser ( ) ; const input = { reviewId : reviewId , heroCardId : currentGame . getMainPlayer ( ) ?. cardId , userId : user . userId , userName : user . username , battleResultHistory : currentGame . buildBattleResultHistory ( ) . map ( ( history ) => ( { ... history , simulationResult : { ... history . simulationResult , outcomeSamples : undefined } , } ) ) , mainPlayer : currentGame . getMainPlayer ( ) , faceOffs : currentGame . faceOffs . map ( ( faceOff ) => ( { damage : faceOff . damage , opponentCardId : faceOff . opponentCardId , playerCardId : faceOff . playerCardId , result : faceOff . result , turn : faceOff . turn , } ) ) , oldMmr : currentGame . mmrAtStart , newMmr : isNaN ( newMmr ) ? null : newMmr , } ; const [ postMatchStats , newBestValues ] = this . populateObject ( liveStats , input , bestBgsUserStats || [ ] ) ; console . log ( '[bgs-run-stats] newBestVaues' ) ; this . buildStatsRemotely ( input ) ; this . bgsStateUpdater . next ( new BgsGameEndEvent ( postMatchStats , newBestValues , reviewId ) ) ; await sleep ( 1000 ) ; this . stateUpdater . next ( new BgsPostMatchStatsComputedEvent ( reviewId , postMatchStats , newBestValues ) ) ; } private async buildStatsRemotely ( input ) { console . log ( '[bgs-run-stats] preparing to build stats remotely' , input . reviewId ) ; await sleep ( 5000 ) ; console . log ( '[bgs-run-stats] contacting remote endpoint' , input . reviewId ) ; try { await this . apiRunner . callPostApi ( POST_MATCH_STATS_UPDATE_URL , input ) ; return ; } catch ( e ) { console . error ( '[bgs-run-stats] issue while posting post-match stats' , input . reviewId , e ) ; } } private populateObject ( realTimeStatsState , input , existingBestStats ) { const result = BgsPostMatchStats . create ( { ... realTimeStatsState , boardHistory : input . mainPlayer ?. boardHistory || [ ] , tripleTimings : input . mainPlayer && realTimeStatsState . triplesPerHero [ input . mainPlayer . cardId ] ? new Array ( realTimeStatsState . triplesPerHero [ input . mainPlayer . cardId ] ) : [ ] , } ) ; const newBestStats = buildNewStats ( existingBestStats , result , ( { mainPlayer : input . mainPlayer , reviewId : input . reviewId , userId : input . userName || input . userId , } as any ) as BgsComputeRunStatsInput , `${ new Date ( ) . toISOString ( ) . slice ( 0 , 19 ) . replace ( 'T' , ' ' ) }.${ new Date ( ) . getMilliseconds ( ) }` ) ; const finalStats = this . mergeStats ( existingBestStats , newBestStats ) ; return [ result , finalStats ] ; } private mergeStats ( existingBestStats , newBestStats ) { const statsToKeep = existingBestStats . filter ( ( existing ) => ! this . isStatIncluded ( existing , newBestStats ) ) ; return [ ... newBestStats , ... statsToKeep ] ; } private isStatIncluded ( toFind , list ) { return list . find ( ( existing ) => existing . statName === toFind . statName ) != null ; } }