import { Injectable } from '@angular/core' ; import { parseHsReplayString } from '@firestone-hs/hs-replay-xml-parser/dist/public-api' ; import { BattlegroundsInfo } from '../../models/battlegrounds-info' ; import { GameEvent } from '../../models/game-event' ; import { MemoryMercenariesCollectionInfo , MemoryTeam } from '../../models/memory/memory-mercenaries-collection-info' ; import { MemoryMercenariesInfo } from '../../models/memory/memory-mercenaries-info' ; import { BattlegroundsStoreService } from '../battlegrounds/store/battlegrounds-store.service' ; import { BgsGlobalInfoUpdatedParser } from '../battlegrounds/store/event-parsers/bgs-global-info-updated-parser' ; import { CardsFacadeService } from '../cards-facade.service' ; import { ArenaRunParserService } from '../decktracker/arena-run-parser.service' ; import { DungeonLootParserService } from '../decktracker/dungeon-loot-parser.service' ; import { LogsUploaderService } from '../logs-uploader.service' ; import { MainWindowStoreService } from '../mainwindow/store/main-window-store.service' ; import { MercenariesReferenceData } from '../mercenaries/mercenaries-state-builder.service' ; import { isMercenaries , isMercenariesPvE , isMercenariesPvP , normalizeMercenariesCardId , } from '../mercenaries/mercenaries-utils' ; import { PlayersInfoService } from '../players-info.service' ; import { MemoryInspectionService } from '../plugins/memory-inspection.service' ; import { RewardMonitorService } from '../rewards/rewards-monitor' ; import { extractHeroTimings } from '../stats/game/game-stats-updater.service' ; import { sleep } from '../utils' ; import { GameForUpload } from './game-for-upload' ; import { GameParserService } from './game-parser.service' ; import { ReplayUploadService } from './replay-upload.service' ; @ Injectable ( ) export class EndGameUploaderService { private readonly supportedModesDeckRetrieve = [ 'practice' , 'friendly' , 'ranked' , 'casual' , 'arena' , 'tavernbrawl' , 'duels' , 'paid-duels' , ] ; constructor ( private replayUploadService , private gameParserService , private playersInfo , private memoryInspection , private dungeonLootParser , private arenaService , private logService , private rewards , private mainWindowStore , private readonly bgsStore , private readonly allCards ) { } public async upload ( gameEvent , currentReviewId , deckstring , deckName , buildNumber , scenarioId , params ? ) { console . log ( '[manastorm-bridge]' , currentReviewId , 'Uploading game info' ) ; const game = await this . initializeGame ( gameEvent , currentReviewId , deckstring , deckName , buildNumber , params ) ; await this . replayUploadService . uploadGame ( game ) ; } private async initializeGame ( gameEvent , currentReviewId , deckstring , deckName , buildNumber , params ? ) { const gameResult = gameEvent . additionalData . game ; const replayXml = gameEvent . additionalData . replayXml ; if ( ! replayXml ) { console . warn ( '[manastorm-bridge]' , currentReviewId , 'could not convert replay' ) ; } console . log ( '[manastorm-bridge]' , currentReviewId , 'Creating new game' , 'with replay length' , replayXml . length ) ; const game = GameForUpload . createEmptyGame ( currentReviewId ) ; console . log ( '[manastorm-bridge]' , currentReviewId , 'Created new game' ) ; game . gameFormat = this . gameParserService . toFormatType ( gameResult . FormatType ) ; console . log ( '[manastorm-bridge]' , currentReviewId , 'parsed format' , gameResult . FormatType , game . gameFormat ) ; game . gameMode = this . gameParserService . toGameType ( gameResult . GameType ) ; console . log ( '[manastorm-bridge]' , currentReviewId , 'parsed type' , gameResult . GameType , game . gameMode ) ; let playerRank ; let newPlayerRank ; console . log ( '[manastorm-bridge]' , currentReviewId , 'reading memory info' ) ; const [ battlegroundsInfo , mercenariesCollectionInfo , mercenariesInfo , duelsInfo , arenaInfo , matchInfo , xpForGame , ] = await Promise . all ( [ game . gameMode === 'battlegrounds' ? this . getBattlegroundsEndGame ( currentReviewId ) : null , isMercenaries ( game . gameMode ) ? this . getMercenariesCollectionInfo ( currentReviewId ) : null , isMercenaries ( game . gameMode ) ? this . getMercenariesInfo ( currentReviewId ) : null , game . gameMode === 'duels' || game . gameMode === 'paid-duels' ? this . memoryInspection . getDuelsInfo ( ) : null , game . gameMode === 'arena' ? this . memoryInspection . getArenaInfo ( ) : null , isMercenaries ( game . gameMode ) || game . gameMode === 'battlegrounds' ? null : this . memoryInspection . getMatchInfo ( ) , this . rewards . getXpForGameInfo ( ) , ] ) ; console . log ( '[manastorm-bridge]' , currentReviewId , 'read memory info' ) ; const playerInfo = matchInfo ?. localPlayer ; const opponentInfo = matchInfo ?. opponent ; const replay = parseHsReplayString ( replayXml ) ; if ( game . gameMode === 'battlegrounds' ) { playerRank = battlegroundsInfo ?. rating ?? this . bgsStore ?. state ?. currentGame ?. mmrAtStart ; newPlayerRank = battlegroundsInfo ?. newRating ?? params . bgsNewRating ; let [ availableRaces , bannedRaces ] = BgsGlobalInfoUpdatedParser . buildRaces ( battlegroundsInfo ?. game ?. AvailableRaces ) ; availableRaces = availableRaces ?? this . bgsStore ?. state ?. currentGame ?. availableRaces ; bannedRaces = bannedRaces ?? this . bgsStore ?. state ?. currentGame ?. bannedRaces ; game . availableTribes = availableRaces ; game . bannedTribes = bannedRaces ; game . additionalResult = replay . additionalResult ; console . log ( '[manastorm-bridge]' , currentReviewId , 'updated player rank' , playerRank , newPlayerRank ) ; game . hasBgsPrizes = params . hasPrizes ; } else if ( isMercenaries ( game . gameMode ) ) { game . runId = isMercenariesPvE ( game . gameMode ) ? mercenariesInfo ?. Map ?. PlayerTeamName + '-' + mercenariesInfo ?. Map ?. MapId + '-' + mercenariesInfo ?. Map ?. Seed : null ; game . mercsBountyId = isMercenariesPvE ( game . gameMode ) ? mercenariesInfo ?. Map ?. BountyId : null ; const referenceData = this . mainWindowStore ?. state ?. mercenaries ?. referenceData ; const { mercHeroTimings , ... other } = await extractHeroTimings ( { gameMode : game . gameMode } , replay , referenceData , this . allCards . getService ( ) ) ; if ( ! ! mercHeroTimings ?. length ) { if ( isMercenariesPvE ( game . gameMode ) ) { game . deckName = mercenariesInfo ?. Map ?. PlayerTeamName ; game . deckstring = [ ... ( mercenariesInfo ?. Map ?. PlayerTeamMercIds ?? [ ] ) ] . sort ( ) . join ( ',' ) ; } else if ( isMercenariesPvP ( game . gameMode ) ) { const allPlayerHeroes = mercHeroTimings . map ( ( timing ) => timing . cardId ) . map ( ( c ) => normalizeMercenariesCardId ( c ) ) . sort ( ) ; const team = this . findMercTeam ( mercenariesCollectionInfo ?. Teams , allPlayerHeroes , referenceData ) ; game . deckName = team ?. Name ; game . deckstring = [ ... ( team ?. Mercenaries ?. map ( ( merc ) => merc . Id ) ?? [ ] ) ] . sort ( ) . join ( ',' ) ; } } playerRank = game . gameMode === 'mercenaries-pvp' ? mercenariesInfo ?. PvpRating : game . gameMode === 'mercenaries-pve' || game . gameMode === 'mercenaries-pve-coop' ? this . getMercenariesBountyDifficulty ( game . mercsBountyId ) : null ; game . forceOpponentName = game . gameMode === 'mercenaries-pve' || game . gameMode === 'mercenaries-pve-coop' ? this . buildOpponentName ( mercenariesInfo ) : null ; } else if ( game . gameMode === 'duels' || game . gameMode === 'paid-duels' ) { console . log ( '[manastorm-bridge]' , currentReviewId , 'handline duels' , game . gameMode ) ; console . log ( '[manastorm-bridge]' , currentReviewId , 'got duels info' , duelsInfo , params . duelsInfo ) ; playerRank = game . gameMode === 'duels' ? params . duelsInfo ?. rating ?? duelsInfo ?. Rating : params . duelsInfo ?. paidRating ?? duelsInfo ?. PaidRating ; game . additionalResult = duelsInfo ?. Wins + '-' + duelsInfo ?. Losses ; try { if ( ( replay . result === 'won' && ( params . duelsInfo ?. wins ?? duelsInfo . Wins ) === 11 ) || ( replay . result === 'lost' && ( params . duelsInfo ?. losses ?? duelsInfo . Losses ) === 2 ) ) { const newPlayerRank = await this . getDuelsNewPlayerRank ( playerRank ) ; console . log ( '[manastorm-bridge]' , currentReviewId , 'got duels new player rank' , newPlayerRank ) ; if ( newPlayerRank != null ) { game . newPlayerRank = '' + newPlayerRank ; } } } catch ( e ) { console . error ( '[manastorm-bridge]' , currentReviewId , 'Could not handle rating change in duels' , e ) ; } } else if ( game . gameMode === 'arena' ) { playerRank = arenaInfo ? arenaInfo . wins + '-' + arenaInfo . losses : undefined ; game . additionalResult = arenaInfo ? arenaInfo . wins + '-' + arenaInfo . losses : undefined ; console . log ( '[manastorm-bridge]' , currentReviewId , 'updated player rank for arena' , playerRank ) ; } else if ( game . gameFormat !== 'unknown' ) { if ( playerInfo && game . gameFormat === 'standard' ) { if ( playerInfo . standard ?. legendRank > 0 ) { playerRank = `legend-${ playerInfo . standard . legendRank }` ; } else if ( playerInfo . standard . leagueId >= 0 && playerInfo . standard . rankValue >= 0 ) { playerRank = `${ playerInfo . standard . leagueId }-${ playerInfo . standard . rankValue }` ; } else { console . warn ( '[manastorm-bridge]' , currentReviewId , 'Could not extract standard player rank' , playerInfo . standard ) ; playerRank = null ; } } else if ( playerInfo && game . gameFormat === 'wild' ) { if ( playerInfo . wild ?. legendRank > 0 ) { playerRank = `legend-${ playerInfo . wild . legendRank }` ; } else if ( playerInfo . wild . leagueId >= 0 && playerInfo . wild . rankValue >= 0 ) { playerRank = `${ playerInfo . wild . leagueId }-${ playerInfo . wild . rankValue }` ; } else { console . warn ( '[manastorm-bridge]' , currentReviewId , 'Could not extract wild player rank' , playerInfo . wild ) ; playerRank = null ; } } else if ( playerInfo && game . gameFormat === 'classic' ) { if ( playerInfo . classic ?. legendRank > 0 ) { playerRank = `legend-${ playerInfo . classic . legendRank }` ; } else if ( playerInfo . classic . leagueId >= 0 && playerInfo . classic . rankValue >= 0 ) { playerRank = `${ playerInfo . classic . leagueId }-${ playerInfo . classic . rankValue }` ; } else { console . warn ( '[manastorm-bridge]' , currentReviewId , 'Could not extract classic player rank' , playerInfo . classic ) ; playerRank = null ; } } } let opponentRank ; if ( game . gameMode === 'battlegrounds' || game . gameMode === 'duels' || game . gameMode === 'paid-duels' ) { } else if ( game . gameFormat === 'standard' || game . gameFormat === 'wild' ) { if ( opponentInfo && game . gameFormat === 'standard' ) { if ( opponentInfo . standard ?. legendRank > 0 ) { opponentRank = `legend-${ opponentInfo . standard . legendRank }` ; } else if ( opponentInfo . standard . leagueId >= 0 && opponentInfo . standard . rankValue >= 0 ) { opponentRank = `${ opponentInfo . standard . leagueId }-${ opponentInfo . standard ?. rankValue }` ; } else { console . warn ( '[manastorm-bridge]' , currentReviewId , 'Could not extract opponent rank' , opponentInfo . standard ) ; opponentRank = null ; } } else if ( opponentInfo && game . gameFormat === 'wild' ) { if ( opponentInfo . wild ?. legendRank > 0 ) { opponentRank = `legend-${ opponentInfo . wild . legendRank }` ; } else if ( opponentInfo . wild . leagueId >= 0 && opponentInfo . wild . rankValue >= 0 ) { opponentRank = `${ opponentInfo . wild . leagueId }-${ opponentInfo . wild ?. rankValue }` ; } else { console . warn ( '[manastorm-bridge]' , currentReviewId , 'Could not extract opponent rank' , opponentInfo . wild ) ; opponentRank = null ; } } } game . opponentRank = opponentRank ; game . playerRank = playerRank ; game . newPlayerRank = newPlayerRank ; console . log ( '[manastorm-bridge]' , currentReviewId , 'extracted player rank' ) ; game . reviewId = currentReviewId ; game . buildNumber = buildNumber ; game . scenarioId = gameResult . ScenarioID ; game . xpForGame = xpForGame ; if ( this . supportedModesDeckRetrieve . indexOf ( game . gameMode ) !== - 1 ) { console . log ( '[manastorm-bridge]' , currentReviewId , 'adding deckstring' , deckstring , game . gameMode ) ; game . deckstring = deckstring ; game . deckName = deckName ; } else { console . log ( '[manastorm-bridge]' , currentReviewId , 'game mode not supporting deckstrings, not sending it' , deckstring , game . gameMode ) ; } console . log ( '[manastorm-bridge]' , currentReviewId , 'added meta data' ) ; game . uncompressedXmlReplay = replayXml ; console . log ( '[manastorm-bridge]' , currentReviewId , 'set xml replay' ) ; this . gameParserService . extractMatchup ( replay , game ) ; console . log ( '[manastorm-bridge]' , currentReviewId , 'extracted matchup' ) ; this . gameParserService . extractDuration ( replay , game ) ; console . log ( '[manastorm-bridge]' , currentReviewId , 'extracted duration' ) ; if ( game . gameMode === 'duels' || game . gameMode === 'paid-duels' ) { game . runId = this . dungeonLootParser . currentDuelsRunId ; console . log ( '[manastorm-bridge]' , currentReviewId , 'added duels run id' , game . runId ) ; if ( ! game . runId ) { console . log ( '[manastorm-bridge]' , currentReviewId , 'currentDuelsRunId is missing, waiting for dungeon loot info to be retrieved' ) ; while ( this . dungeonLootParser . busyRetrievingInfo ) { await sleep ( 200 ) ; } game . runId = this . dungeonLootParser . currentDuelsRunId ; console . log ( '[manastorm-bridge]' , currentReviewId , 'dungeon loot info retrieved, continuing' , game . runId ) ; } if ( ! game . runId ) { console . warn ( '[manastorm-bridge]' , currentReviewId , 'currentDuelsRunId is missing' , game . runId , game . gameMode , this . dungeonLootParser . currentDuelsRunId ) ; game . runId = this . dungeonLootParser . currentDuelsRunId ; setTimeout ( ( ) => this . logService . reportSpecificBug ( 'duels-empty-run-id' ) , 5000 ) ; } } else if ( game . gameMode === 'arena' ) { game . runId = this . arenaService . currentArenaRunId ; } console . log ( '[manastorm-bridge]' , currentReviewId , 'game ready' ) ; return game ; } private findMercTeam ( Teams , allPlayerHeroes , referenceData ) { if ( ! Teams ?. length ) { return null ; } for ( const team of Teams ) { const mercIds = team . Mercenaries . map ( ( merc ) => merc . Id ) ; const mercCardIds = mercIds . map ( ( mercId ) => referenceData . mercenaries . find ( ( m ) => m . id === mercId ) ) . filter ( ( m ) => ! ! m ) . map ( ( m ) => this . allCards . getCardFromDbfId ( m . cardDbfId ) ) . map ( ( card ) => normalizeMercenariesCardId ( card . id ) ) . sort ( ) ; if ( allPlayerHeroes . join ( ',' ) === mercCardIds . join ( ',' ) ) { return team ; } } return null ; } private buildOpponentName ( mercenariesInfo ) { const bountyId = mercenariesInfo ?. Map ?. BountyId ; if ( bountyId == null ) { return null ; } const referenceData = this . mainWindowStore ?. state ?. mercenaries ?. referenceData ; console . debug ( 'referenceData ' , referenceData , bountyId ) ; if ( ! referenceData ) { return null ; } const allBounties = referenceData . bountySets . map ( ( set ) => set . bounties ) . reduce ( ( a , b ) => a . concat ( b ) , [ ] ) ; console . debug ( 'allBounties' , allBounties ) ; const bounty = allBounties . find ( ( b ) => b . id === bountyId ) ; if ( ! bounty ) { return `${ bountyId }` ; } return `${ bounty . name } (${ mercenariesInfo . Map . CurrentStep } / ${ mercenariesInfo . Map . MaxStep })` ; } private getMercenariesBountyDifficulty ( mercsBountyId ) { const referenceData = this . mainWindowStore ?. state ?. mercenaries ?. referenceData ; console . debug ( 'referenceData' , referenceData , mercsBountyId ) ; if ( ! referenceData ) { return null ; } const allBounties = referenceData . bountySets . map ( ( set ) => set . bounties ) . reduce ( ( a , b ) => a . concat ( b ) , [ ] ) ; console . debug ( 'allBounties' , allBounties ) ; const bounty = allBounties . find ( ( b ) => b . id === mercsBountyId ) ; console . debug ( 'bounty' , bounty ) ; if ( ! bounty ) { return null ; } return bounty . heroic === 1 ? 'heroic' : 'normal' ; } private async getBattlegroundsEndGame ( currentReviewId ) { const result = await this . memoryInspection . getBattlegroundsEndGame ( ) ; console . log ( '[manastorm-bridge]' , currentReviewId , 'received BG rank result' , result ) ; return result ; } private async getMercenariesCollectionInfo ( currentReviewId ) { const result = await this . memoryInspection . getMercenariesCollectionInfo ( ) ; console . log ( '[manastorm-bridge]' , currentReviewId , 'getMercenariesCollectionInfo' , result ) ; return result ; } private async getMercenariesInfo ( currentReviewId ) { const result = await this . memoryInspection . getMercenariesInfo ( ) ; console . log ( '[manastorm-bridge]' , currentReviewId , 'getMercenariesInfo' , result ) ; return result ; } private async getDuelsNewPlayerRank ( initialRank ) { let duelsInfo = await this . memoryInspection . getDuelsInfo ( ) ; let retriesLeft = 10 ; while ( ! duelsInfo ?. LastRatingChange && retriesLeft >= 0 ) { await sleep ( 2000 ) ; duelsInfo = await this . memoryInspection . getDuelsInfo ( ) ; retriesLeft -- ; } if ( ! duelsInfo ) { return null ; } return duelsInfo . LastRatingChange + initialRank ; } }