import _ from "lodash" ; import type { GameStartType } from "../types" ; import type { ConversionType , InputCountsType , OverallType , RatioType , StockType } from "./common" ; import type { PlayerInput } from "./inputs" ; interface ConversionsByPlayerByOpening { [ playerIndex ] : { [ openingType ] : ConversionType [ ] ; } ; } export function generateOverallStats ( settings , inputs , stocks , conversions , playableFrameCount ) { const inputsByPlayer = _ . keyBy ( inputs , "playerIndex" ) ; const originalConversions = conversions ; const conversionsByPlayer = _ . groupBy ( conversions , ( conv ) => conv . moves [ 0 ] ?. playerIndex ) ; const conversionsByPlayerByOpening = _ . mapValues ( conversionsByPlayer , ( conversions ) => _ . groupBy ( conversions , "openingType" ) ) ; const gameMinutes = playableFrameCount / 3600 ; const overall = settings . players . map ( ( player ) => { const playerIndex = player . playerIndex ; const playerInputs = _ . get ( inputsByPlayer , playerIndex ) || { } ; const inputCounts = { buttons : _ . get ( playerInputs , "buttonInputCount" ) , triggers : _ . get ( playerInputs , "triggerInputCount" ) , cstick : _ . get ( playerInputs , "cstickInputCount" ) , joystick : _ . get ( playerInputs , "joystickInputCount" ) , total : _ . get ( playerInputs , "inputCount" ) , } ; let conversionCount = 0 ; let successfulConversionCount = 0 ; const opponentIndices = settings . players . filter ( ( opp ) => { if ( opp . playerIndex === playerIndex ) { return false ; } return ! settings . isTeams || opp . teamId !== player . teamId ; } ) . map ( ( opp ) => opp . playerIndex ) ; let totalDamage = 0 ; let killCount = 0 ; originalConversions . filter ( ( conversion ) => conversion . playerIndex !== playerIndex ) . forEach ( ( conversion ) => { conversionCount ++ ; if ( conversion . didKill && conversion . lastHitBy === playerIndex ) { killCount += 1 ; } if ( conversion . moves . length > 1 && conversion . moves [ 0 ] . playerIndex === playerIndex ) { successfulConversionCount ++ ; } conversion . moves . forEach ( ( move ) => { if ( move . playerIndex === playerIndex ) { totalDamage += move . damage ; } } ) ; } ) ; return { playerIndex : playerIndex , inputCounts : inputCounts , conversionCount : conversionCount , totalDamage : totalDamage , killCount : killCount , successfulConversions : getRatio ( successfulConversionCount , conversionCount ) , inputsPerMinute : getRatio ( inputCounts . total , gameMinutes ) , digitalInputsPerMinute : getRatio ( inputCounts . buttons , gameMinutes ) , openingsPerKill : getRatio ( conversionCount , killCount ) , damagePerOpening : getRatio ( totalDamage , conversionCount ) , neutralWinRatio : getOpeningRatio ( conversionsByPlayerByOpening , playerIndex , opponentIndices , "neutral-win" ) , counterHitRatio : getOpeningRatio ( conversionsByPlayerByOpening , playerIndex , opponentIndices , "counter-attack" ) , beneficialTradeRatio : getBeneficialTradeRatio ( conversionsByPlayerByOpening , playerIndex , opponentIndices ) , } ; } ) ; return overall ; } function getRatio ( count , total ) { return { count : count , total : total , ratio : total ? count / total : null , } ; } function getOpeningRatio ( conversionsByPlayerByOpening , playerIndex , opponentIndices , type ) { const openings = _ . get ( conversionsByPlayerByOpening , [ playerIndex , type ] ) || [ ] ; const opponentOpenings = _ . flatten ( opponentIndices . map ( ( opponentIndex ) => _ . get ( conversionsByPlayerByOpening , [ opponentIndex , type ] ) || [ ] ) ) ; return getRatio ( openings . length , openings . length + opponentOpenings . length ) ; } function getBeneficialTradeRatio ( conversionsByPlayerByOpening , playerIndex , opponentIndices ) { const playerTrades = _ . get ( conversionsByPlayerByOpening , [ playerIndex , "trade" ] ) || [ ] ; const opponentTrades = _ . flatten ( opponentIndices . map ( ( opponentIndex ) => _ . get ( conversionsByPlayerByOpening , [ opponentIndex , "trade" ] ) || [ ] ) ) ; const benefitsPlayer = [ ] ; const zippedTrades = _ . zip ( playerTrades , opponentTrades ) ; zippedTrades . forEach ( ( conversionPair ) => { const playerConversion = _ . first ( conversionPair ) ; const opponentConversion = _ . last ( conversionPair ) ; if ( playerConversion && opponentConversion ) { const playerDamage = playerConversion . currentPercent - playerConversion . startPercent ; const opponentDamage = opponentConversion . currentPercent - opponentConversion . startPercent ; if ( playerConversion ! . didKill && ! opponentConversion ! . didKill ) { benefitsPlayer . push ( playerConversion ) ; } else if ( playerDamage > opponentDamage ) { benefitsPlayer . push ( playerConversion ) ; } } } ) ; return getRatio ( benefitsPlayer . length , playerTrades . length ) ; }