2020-10-07 20:03:24 +00:00
|
|
|
import { getSeasonNumber, queuesWithRole, sortTeamByRole, supportItems } from 'App/helpers'
|
|
|
|
|
import Jax from 'App/Services/Jax'
|
2020-12-18 22:02:20 +00:00
|
|
|
import { Champion, Item, ParticipantBasic, ParticipantDetails, PercentStats, Perks, Stats, SummonerSpell } from 'App/Models/Match'
|
2020-10-11 16:13:14 +00:00
|
|
|
import RoleIdentificationService, { ChampionsPlayRate } from 'App/Services/RoleIdentiticationService'
|
2020-10-11 15:57:43 +00:00
|
|
|
import { ChampionDTO, ItemDTO, PerkDTO, PerkStyleDTO, SummonerSpellDTO } from 'App/Services/Jax/src/Endpoints/CDragonEndpoint'
|
2020-10-11 16:13:14 +00:00
|
|
|
import { TeamStats } from 'App/Models/DetailedMatch'
|
2020-12-18 22:02:20 +00:00
|
|
|
import {
|
|
|
|
|
MatchDto,
|
|
|
|
|
ParticipantDto,
|
2021-08-10 22:12:17 +00:00
|
|
|
PerksDto,
|
2020-12-18 22:02:20 +00:00
|
|
|
} from 'App/Services/Jax/src/Endpoints/MatchEndpoint'
|
2020-10-07 20:03:24 +00:00
|
|
|
|
|
|
|
|
export interface PlayerRole {
|
|
|
|
|
champion: number,
|
|
|
|
|
jungle?: boolean,
|
|
|
|
|
support?: boolean,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default abstract class MatchTransformer {
|
2020-10-11 15:57:43 +00:00
|
|
|
protected champions: ChampionDTO[]
|
|
|
|
|
protected items: ItemDTO[]
|
|
|
|
|
protected perks: PerkDTO[]
|
|
|
|
|
protected perkstyles: PerkStyleDTO[]
|
|
|
|
|
protected summonerSpells: SummonerSpellDTO[]
|
2020-10-11 16:13:14 +00:00
|
|
|
protected championRoles: ChampionsPlayRate
|
2020-10-11 15:31:16 +00:00
|
|
|
protected sortTeamByRole: (a: ParticipantBasic | ParticipantDetails, b: ParticipantBasic | ParticipantDetails) => number
|
2020-10-07 20:03:24 +00:00
|
|
|
/**
|
|
|
|
|
* Get global Context with CDragon Data
|
|
|
|
|
*/
|
|
|
|
|
public async getContext () {
|
|
|
|
|
const items = await Jax.CDragon.items()
|
|
|
|
|
const champions = await Jax.CDragon.champions()
|
|
|
|
|
const perks = await Jax.CDragon.perks()
|
|
|
|
|
const perkstyles = await Jax.CDragon.perkstyles()
|
|
|
|
|
const summonerSpells = await Jax.CDragon.summonerSpells()
|
|
|
|
|
const championRoles = await RoleIdentificationService.pullData().catch(() => { })
|
|
|
|
|
|
|
|
|
|
this.champions = champions
|
|
|
|
|
this.items = items
|
|
|
|
|
this.perks = perks
|
|
|
|
|
this.perkstyles = perkstyles.styles
|
|
|
|
|
this.summonerSpells = summonerSpells
|
2020-10-11 16:13:14 +00:00
|
|
|
this.championRoles = championRoles as ChampionsPlayRate
|
2020-10-07 20:03:24 +00:00
|
|
|
this.sortTeamByRole = sortTeamByRole
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get champion specific data
|
|
|
|
|
* @param id of the champion
|
|
|
|
|
*/
|
2020-10-11 15:57:43 +00:00
|
|
|
public getChampion (id: number): Champion {
|
|
|
|
|
const originalChampionData = this.champions.find(c => c.id === id)
|
|
|
|
|
const icon = 'https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/'
|
|
|
|
|
+ originalChampionData!.squarePortraitPath.split('/assets/')[1].toLowerCase()
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
icon,
|
|
|
|
|
id: originalChampionData!.id,
|
|
|
|
|
name: originalChampionData!.name,
|
|
|
|
|
alias: originalChampionData!.alias,
|
|
|
|
|
roles: originalChampionData!.roles,
|
|
|
|
|
}
|
2020-10-07 20:03:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get global data about the match
|
|
|
|
|
*/
|
|
|
|
|
public getGameInfos (match: MatchDto) {
|
|
|
|
|
return {
|
2021-08-07 20:52:54 +00:00
|
|
|
map: match.info.mapId,
|
|
|
|
|
gamemode: match.info.queueId,
|
|
|
|
|
date: match.info.gameCreation,
|
|
|
|
|
region: match.info.platformId.toLowerCase(),
|
|
|
|
|
season: getSeasonNumber(match.info.gameCreation),
|
|
|
|
|
time: match.info.gameDuration,
|
2020-10-07 20:03:24 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get player specific data during the match
|
|
|
|
|
* @param match
|
|
|
|
|
* @param player
|
|
|
|
|
* @param detailed : detailed or not stats
|
|
|
|
|
* @param teamStats : if detailed, the teamStats argument is mandatory
|
|
|
|
|
*/
|
2020-10-11 16:13:14 +00:00
|
|
|
public getPlayerData (match: MatchDto, player: ParticipantDto, detailed: boolean, teamStats?: TeamStats) {
|
2021-08-10 22:12:17 +00:00
|
|
|
const name = player.summonerName
|
2020-10-07 20:03:24 +00:00
|
|
|
const champion = this.getChampion(player.championId)
|
2021-08-10 22:12:17 +00:00
|
|
|
const role = this.getRoleName(player.teamPosition, match.info.queueId)
|
|
|
|
|
const level = player.champLevel
|
2020-10-07 20:03:24 +00:00
|
|
|
|
|
|
|
|
// Regular stats / Full match stats
|
|
|
|
|
const stats: Stats = {
|
2021-08-10 22:12:17 +00:00
|
|
|
kills: player.kills,
|
|
|
|
|
deaths: player.deaths,
|
|
|
|
|
assists: player.assists,
|
|
|
|
|
minions: player.totalMinionsKilled + player.neutralMinionsKilled,
|
|
|
|
|
vision: player.visionScore,
|
|
|
|
|
gold: player.goldEarned,
|
|
|
|
|
dmgChamp: player.totalDamageDealtToChampions,
|
|
|
|
|
dmgObj: player.damageDealtToObjectives,
|
|
|
|
|
dmgTaken: player.totalDamageTaken,
|
2020-10-07 20:03:24 +00:00
|
|
|
kp: 0,
|
|
|
|
|
kda: 0,
|
|
|
|
|
realKda: 0,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (stats.kills + stats.assists !== 0 && stats.deaths === 0) {
|
|
|
|
|
stats.kda = '∞'
|
|
|
|
|
stats.realKda = stats.kills + stats.assists
|
|
|
|
|
} else {
|
|
|
|
|
stats.kda = +(stats.deaths === 0 ? 0 : ((stats.kills + stats.assists) / stats.deaths)).toFixed(2)
|
|
|
|
|
stats.realKda = stats.kda
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Percent stats / Per minute stats : only for detailed match
|
|
|
|
|
let percentStats: PercentStats
|
|
|
|
|
if (detailed) {
|
2020-10-11 16:13:14 +00:00
|
|
|
teamStats = teamStats!
|
2020-10-07 20:03:24 +00:00
|
|
|
percentStats = {
|
2021-08-10 22:12:17 +00:00
|
|
|
minions: +(stats.minions / (match.info.gameDuration / 60)).toFixed(2),
|
|
|
|
|
vision: +(stats.vision / (match.info.gameDuration / 60)).toFixed(2),
|
|
|
|
|
gold: +(player.goldEarned * 100 / teamStats.gold).toFixed(1) + '%',
|
|
|
|
|
dmgChamp: +(player.totalDamageDealtToChampions * 100 / teamStats.dmgChamp).toFixed(1) + '%',
|
|
|
|
|
dmgObj: +(teamStats.dmgObj ? player.damageDealtToObjectives * 100 / teamStats.dmgObj : 0).toFixed(1) + '%',
|
|
|
|
|
dmgTaken: +(player.totalDamageTaken * 100 / teamStats.dmgTaken).toFixed(1) + '%',
|
2020-10-07 20:03:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stats.kp = teamStats.kills === 0 ? '0%' : +((stats.kills + stats.assists) * 100 / teamStats.kills).toFixed(1) + '%'
|
|
|
|
|
} else {
|
2021-08-10 22:12:17 +00:00
|
|
|
const totalKills = match.info.participants.reduce((prev, current) => {
|
2020-10-07 20:03:24 +00:00
|
|
|
if (current.teamId !== player.teamId) {
|
|
|
|
|
return prev
|
|
|
|
|
}
|
2021-08-10 22:12:17 +00:00
|
|
|
return prev + current.kills
|
2020-10-07 20:03:24 +00:00
|
|
|
}, 0)
|
|
|
|
|
|
2021-08-10 22:12:17 +00:00
|
|
|
stats.criticalStrike = player.largestCriticalStrike
|
|
|
|
|
stats.killingSpree = player.largestKillingSpree
|
|
|
|
|
stats.doubleKills = player.doubleKills
|
|
|
|
|
stats.tripleKills = player.tripleKills
|
|
|
|
|
stats.quadraKills = player.quadraKills
|
|
|
|
|
stats.pentaKills = player.pentaKills
|
|
|
|
|
stats.heal = player.totalHeal
|
|
|
|
|
stats.towers = player.turretKills
|
|
|
|
|
stats.longestLiving = player.longestTimeSpentLiving
|
2020-10-07 20:03:24 +00:00
|
|
|
stats.kp = totalKills === 0 ? 0 : +((stats.kills + stats.assists) * 100 / totalKills).toFixed(1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let primaryRune: string | null = null
|
|
|
|
|
let secondaryRune: string | null = null
|
2021-08-10 22:12:17 +00:00
|
|
|
|
|
|
|
|
const primaryStyle = player.perks.styles.find(s => s.description === 'primaryStyle')
|
|
|
|
|
const subStyle = player.perks.styles.find(s => s.description === 'subStyle')
|
|
|
|
|
|
|
|
|
|
if (primaryStyle && subStyle && primaryStyle.selections.length) {
|
|
|
|
|
({ primaryRune, secondaryRune } = this.getPerksImages(primaryStyle.selections[0].perk, subStyle.style))
|
2020-10-07 20:03:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const items: (Item | null)[] = []
|
|
|
|
|
for (let i = 0; i < 6; i++) {
|
2021-08-10 22:12:17 +00:00
|
|
|
const id = player['item' + i]
|
2020-10-07 20:03:24 +00:00
|
|
|
if (id === 0) {
|
|
|
|
|
items.push(null)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-11 15:57:43 +00:00
|
|
|
const item = this.items.find(i => i.id === id)
|
2020-11-13 18:59:17 +00:00
|
|
|
// TODO: get deleted item from old patch CDragon JSON instead of null
|
|
|
|
|
if (!item) {
|
|
|
|
|
items.push(null)
|
|
|
|
|
continue
|
|
|
|
|
}
|
2020-10-07 20:03:24 +00:00
|
|
|
|
2020-11-13 18:59:17 +00:00
|
|
|
const itemUrl = item.iconPath.split('/assets/')[1].toLowerCase()
|
2020-10-07 20:03:24 +00:00
|
|
|
items.push({
|
|
|
|
|
image: `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${itemUrl}`,
|
2020-11-13 18:59:17 +00:00
|
|
|
name: item.name,
|
|
|
|
|
description: item.description,
|
|
|
|
|
price: item.priceTotal,
|
2020-10-07 20:03:24 +00:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-07 20:52:54 +00:00
|
|
|
const firstSum = player.summoner1Id
|
|
|
|
|
const secondSum = player.summoner2Id
|
2020-10-07 20:03:24 +00:00
|
|
|
|
|
|
|
|
const playerData: ParticipantDetails = {
|
|
|
|
|
name,
|
2021-08-10 22:12:17 +00:00
|
|
|
summonerId: player.summonerId,
|
2020-10-07 20:03:24 +00:00
|
|
|
champion,
|
|
|
|
|
role,
|
|
|
|
|
primaryRune,
|
|
|
|
|
secondaryRune,
|
|
|
|
|
level,
|
|
|
|
|
items,
|
|
|
|
|
firstSum,
|
|
|
|
|
secondSum,
|
|
|
|
|
stats,
|
|
|
|
|
}
|
|
|
|
|
if (detailed) {
|
|
|
|
|
playerData.percentStats = percentStats!
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-10 22:12:17 +00:00
|
|
|
playerData.perks = this.getFullPerks(player.perks)
|
2020-12-18 22:02:20 +00:00
|
|
|
|
2020-10-07 20:03:24 +00:00
|
|
|
return playerData
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-10 22:12:17 +00:00
|
|
|
public getFullPerks (perksDto: PerksDto) {
|
2020-12-18 22:02:20 +00:00
|
|
|
const perks: Perks = {
|
2021-08-10 22:12:17 +00:00
|
|
|
primaryStyle: perksDto.styles.find(s => s.description === 'primaryStyle')!.style,
|
|
|
|
|
secondaryStyle: perksDto.styles.find(s => s.description === 'subStyle')!.style,
|
2020-12-18 22:02:20 +00:00
|
|
|
selected: [],
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-10 22:12:17 +00:00
|
|
|
for (const styles of perksDto.styles) {
|
|
|
|
|
for (const perk of styles.selections) {
|
|
|
|
|
perks.selected.push(perk.perk)
|
|
|
|
|
}
|
2020-12-18 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
|
2021-08-10 22:12:17 +00:00
|
|
|
perks.selected.concat(Object.values(perksDto.statPerks))
|
2020-12-18 22:02:20 +00:00
|
|
|
|
|
|
|
|
return perks
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-07 20:03:24 +00:00
|
|
|
/**
|
|
|
|
|
* Return the icons of the primary rune and secondary category
|
|
|
|
|
* @param perk0 primary perks id
|
|
|
|
|
* @param perkSubStyle secondary perks category
|
|
|
|
|
*/
|
|
|
|
|
public getPerksImages (perk0: number, perkSubStyle: number) {
|
2020-10-11 15:57:43 +00:00
|
|
|
const firstRune = this.perks.find(p => p.id === perk0)
|
|
|
|
|
const firstRuneUrl = firstRune!.iconPath.split('/assets/')[1].toLowerCase()
|
2020-10-07 20:03:24 +00:00
|
|
|
const primaryRune = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${firstRuneUrl}`
|
|
|
|
|
|
2020-10-11 15:57:43 +00:00
|
|
|
const secondRuneStyle = this.perkstyles.find(p => p.id === perkSubStyle)
|
2020-10-07 20:03:24 +00:00
|
|
|
|
|
|
|
|
const secondRuneStyleUrl = secondRuneStyle ? secondRuneStyle.iconPath.split('/assets/')[1].toLowerCase() : null
|
|
|
|
|
const secondaryRune = secondRuneStyleUrl ?
|
|
|
|
|
`https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${secondRuneStyleUrl}`
|
|
|
|
|
: ''
|
|
|
|
|
|
|
|
|
|
return { primaryRune, secondaryRune }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return the lane of the summoner according to timeline
|
|
|
|
|
* @param timeline from Riot Api
|
|
|
|
|
* @param gamemode of the match to check if a role is needed
|
|
|
|
|
*/
|
2021-08-10 22:12:17 +00:00
|
|
|
public getRoleName (teamPosition: string, gamemode: number) {
|
|
|
|
|
if (!queuesWithRole.includes(gamemode) || !teamPosition) {
|
2020-10-07 20:03:24 +00:00
|
|
|
return 'NONE'
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-10 22:12:17 +00:00
|
|
|
if (teamPosition === 'UTILITY') {
|
2020-10-07 20:03:24 +00:00
|
|
|
return 'SUPPORT'
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-10 22:12:17 +00:00
|
|
|
return teamPosition
|
2020-10-07 20:03:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return the 5 roles of a team based on champions
|
|
|
|
|
* @param team 5 champions + smite from a team
|
|
|
|
|
*/
|
|
|
|
|
public getTeamRoles (team: PlayerRole[]) {
|
|
|
|
|
const teamJunglers = team.filter(p => p.jungle && !p.support)
|
|
|
|
|
const jungle = teamJunglers.length === 1 ? teamJunglers[0].champion : undefined
|
|
|
|
|
const teamSupports = team.filter(p => p.support && !p.jungle)
|
|
|
|
|
const support = teamSupports.length === 1 ? teamSupports[0].champion : undefined
|
|
|
|
|
|
|
|
|
|
return RoleIdentificationService.getRoles(this.championRoles, team.map(p => p.champion), jungle, support)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update roles for a team if Riot's ones are badly identified
|
|
|
|
|
* @param team 5 players data of the team
|
|
|
|
|
* @param champs 5 champions + smite from the team
|
|
|
|
|
* @param playerData data of the searched player, only for basic matches
|
|
|
|
|
*/
|
2020-10-11 15:31:16 +00:00
|
|
|
public updateTeamRoles (
|
|
|
|
|
team: ParticipantBasic[] | ParticipantDetails[],
|
|
|
|
|
champs: PlayerRole[],
|
|
|
|
|
playerData?: ParticipantDetails
|
|
|
|
|
) {
|
2020-10-07 20:03:24 +00:00
|
|
|
// const actualRoles = [...new Set(team.map(p => p.role))]
|
|
|
|
|
// if (actualRoles.length === 5) {
|
|
|
|
|
// return
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
const identifiedChamps = this.getTeamRoles(champs)
|
|
|
|
|
for (const summoner of team) {
|
|
|
|
|
summoner.role = Object.entries(identifiedChamps).find(([, champion]) => summoner.champion.id === champion)![0]
|
|
|
|
|
|
|
|
|
|
if (playerData && summoner.champion.id === playerData.champion.id) {
|
|
|
|
|
playerData.role = summoner.role
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* @param match from Riot Api
|
|
|
|
|
* @param allyTeam 5 players of the first team
|
|
|
|
|
* @param enemyTeam 5 players of the second team
|
|
|
|
|
* @param allyTeamId team id of the searched player, only for basic matches
|
|
|
|
|
* @param playerData data of the searched player, only for basic matches
|
|
|
|
|
*/
|
|
|
|
|
public getMatchRoles (
|
|
|
|
|
match: MatchDto,
|
2020-10-11 15:31:16 +00:00
|
|
|
allyTeam: ParticipantBasic[] | ParticipantDetails[],
|
|
|
|
|
enemyTeam: ParticipantBasic[] | ParticipantDetails[],
|
2020-10-07 20:03:24 +00:00
|
|
|
allyTeamId = 100,
|
|
|
|
|
playerData?: ParticipantDetails
|
|
|
|
|
) {
|
2021-08-07 20:52:54 +00:00
|
|
|
if (!this.championRoles || !queuesWithRole.includes(match.info.queueId)) {
|
2020-10-07 20:03:24 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let allyChamps: PlayerRole[] = []
|
|
|
|
|
let enemyChamps: PlayerRole[] = []
|
2021-08-10 22:12:17 +00:00
|
|
|
match.info.participants.map(p => {
|
|
|
|
|
const items = [p.item0, p.item1, p.item2, p.item3, p.item4, p.item5]
|
2020-10-07 20:03:24 +00:00
|
|
|
const playerRole = {
|
|
|
|
|
champion: p.championId,
|
2021-08-10 22:12:17 +00:00
|
|
|
jungle: p.summoner1Id === 11 || p.summoner2Id === 11,
|
2020-10-07 20:03:24 +00:00
|
|
|
support: supportItems.some(suppItem => items.includes(suppItem)),
|
|
|
|
|
}
|
|
|
|
|
p.teamId === allyTeamId ? allyChamps.push(playerRole) : enemyChamps.push(playerRole)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
this.updateTeamRoles(allyTeam, allyChamps, playerData)
|
|
|
|
|
this.updateTeamRoles(enemyTeam, enemyChamps)
|
|
|
|
|
|
|
|
|
|
allyTeam.sort(this.sortTeamByRole)
|
|
|
|
|
enemyTeam.sort(this.sortTeamByRole)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get Summoner Spell Data from CDragon
|
|
|
|
|
* @param id of the summonerSpell
|
|
|
|
|
*/
|
2020-10-11 15:31:16 +00:00
|
|
|
public getSummonerSpell (id: number): SummonerSpell | null {
|
2020-10-07 20:03:24 +00:00
|
|
|
if (id === 0) {
|
|
|
|
|
return null
|
|
|
|
|
}
|
2020-10-11 15:57:43 +00:00
|
|
|
const spell = this.summonerSpells.find(s => s.id === id)
|
|
|
|
|
const spellName = spell!.iconPath.split('/assets/')[1].toLowerCase()
|
2020-10-07 20:03:24 +00:00
|
|
|
return {
|
2020-10-11 15:57:43 +00:00
|
|
|
name: spell!.name,
|
|
|
|
|
description: spell!.description,
|
2020-10-07 20:03:24 +00:00
|
|
|
icon: `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${spellName}`,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|