2019-09-29 16:06:54 +00:00
|
|
|
'use strict'
|
|
|
|
|
|
2020-04-26 18:07:17 +00:00
|
|
|
const Jax = use('App/Services/Jax')
|
2020-06-12 15:45:16 +00:00
|
|
|
const RoleIdentificationService = use('App/Services/RoleIdentificationService')
|
2020-06-25 12:05:44 +00:00
|
|
|
const { getSeasonNumber, queuesWithRole, sortTeamByRole, supportItems } = use('App/helpers')
|
2019-11-16 16:43:55 +00:00
|
|
|
|
2019-09-29 16:06:54 +00:00
|
|
|
/**
|
|
|
|
|
* MatchTransformer class
|
|
|
|
|
*
|
|
|
|
|
* @class MatchTransformer
|
|
|
|
|
*/
|
2019-11-03 17:49:58 +00:00
|
|
|
class MatchTransformer {
|
2019-09-29 16:06:54 +00:00
|
|
|
/**
|
2020-02-13 19:24:17 +00:00
|
|
|
* Get global Context with CDragon Data
|
2019-09-29 16:06:54 +00:00
|
|
|
*/
|
2019-11-16 16:43:55 +00:00
|
|
|
async getContext() {
|
2019-11-23 16:34:34 +00:00
|
|
|
const items = await Jax.CDragon.items()
|
2019-11-25 20:15:52 +00:00
|
|
|
const champions = await Jax.CDragon.champions()
|
2019-11-23 16:34:34 +00:00
|
|
|
const perks = await Jax.CDragon.perks()
|
|
|
|
|
const perkstyles = await Jax.CDragon.perkstyles()
|
2019-12-01 19:44:41 +00:00
|
|
|
const summonerSpells = await Jax.CDragon.summonerSpells()
|
2020-06-21 18:58:04 +00:00
|
|
|
const championRoles = await RoleIdentificationService.pullData().catch(() => { })
|
2019-11-16 16:43:55 +00:00
|
|
|
|
2019-11-25 20:15:52 +00:00
|
|
|
this.champions = champions
|
2019-11-23 16:34:34 +00:00
|
|
|
this.items = items
|
|
|
|
|
this.perks = perks
|
|
|
|
|
this.perkstyles = perkstyles.styles
|
2019-12-01 19:44:41 +00:00
|
|
|
this.summonerSpells = summonerSpells
|
2020-06-12 15:45:16 +00:00
|
|
|
this.championRoles = championRoles
|
2020-06-16 19:36:39 +00:00
|
|
|
this.sortTeamByRole = sortTeamByRole
|
2019-11-16 16:43:55 +00:00
|
|
|
}
|
2019-11-03 19:59:21 +00:00
|
|
|
|
2019-11-25 20:15:52 +00:00
|
|
|
/**
|
|
|
|
|
* Get champion specific data
|
|
|
|
|
* @param id of the champion
|
|
|
|
|
*/
|
|
|
|
|
getChampion(id) {
|
|
|
|
|
const champion = { ...this.champions.find(c => c.id === id) }
|
|
|
|
|
champion.icon = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${champion.squarePortraitPath.split('/assets/')[1].toLowerCase()}`
|
|
|
|
|
delete champion.squarePortraitPath
|
|
|
|
|
return champion
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-16 16:43:55 +00:00
|
|
|
/**
|
|
|
|
|
* Get global data about the match
|
|
|
|
|
*/
|
|
|
|
|
getGameInfos(match) {
|
2019-11-03 19:59:21 +00:00
|
|
|
return {
|
2019-11-16 16:43:55 +00:00
|
|
|
map: match.mapId,
|
|
|
|
|
gamemode: match.queueId,
|
|
|
|
|
date: match.gameCreation,
|
2019-12-01 18:17:30 +00:00
|
|
|
region: match.platformId.toLowerCase(),
|
2020-06-21 19:16:13 +00:00
|
|
|
season: getSeasonNumber(match.gameCreation),
|
2019-11-16 16:43:55 +00:00
|
|
|
time: match.gameDuration
|
2019-11-03 19:59:21 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get player specific data during the match
|
2019-11-16 16:43:55 +00:00
|
|
|
* @param match
|
2019-11-03 19:59:21 +00:00
|
|
|
* @param player
|
|
|
|
|
* @param detailed : detailed or not stats
|
|
|
|
|
* @param teamStats : if detailed, the teamStats argument is mandatory
|
|
|
|
|
*/
|
2019-11-16 16:43:55 +00:00
|
|
|
getPlayerData(match, player, detailed, teamStats = {}) {
|
|
|
|
|
const identity = match.participantIdentities.find(p => p.participantId === player.participantId)
|
2019-11-03 19:59:21 +00:00
|
|
|
const name = identity.player.summonerName
|
2019-11-25 20:15:52 +00:00
|
|
|
const champion = this.getChampion(player.championId)
|
2020-06-16 19:36:39 +00:00
|
|
|
const role = this.getRoleName(player.timeline, match.queueId)
|
2019-11-03 19:59:21 +00:00
|
|
|
const level = player.stats.champLevel
|
|
|
|
|
|
|
|
|
|
// Regular stats / Full match stats
|
|
|
|
|
const stats = {
|
2019-11-06 17:46:45 +00:00
|
|
|
kills: player.stats.kills,
|
|
|
|
|
deaths: player.stats.deaths,
|
|
|
|
|
assists: player.stats.assists,
|
2019-11-03 19:59:21 +00:00
|
|
|
minions: player.stats.totalMinionsKilled + player.stats.neutralMinionsKilled,
|
|
|
|
|
vision: player.stats.visionScore,
|
2019-12-16 20:06:56 +00:00
|
|
|
gold: player.stats.goldEarned,
|
|
|
|
|
dmgChamp: player.stats.totalDamageDealtToChampions,
|
|
|
|
|
dmgObj: player.stats.damageDealtToObjectives,
|
|
|
|
|
dmgTaken: player.stats.totalDamageTaken,
|
2019-11-03 19:59:21 +00:00
|
|
|
}
|
|
|
|
|
|
2019-11-06 17:46:45 +00:00
|
|
|
if (stats.kills + stats.assists !== 0 && stats.deaths === 0) {
|
|
|
|
|
stats.kda = '∞'
|
2020-01-25 11:33:28 +00:00
|
|
|
stats.realKda = stats.kills + stats.assists
|
2019-11-06 17:46:45 +00:00
|
|
|
} else {
|
|
|
|
|
stats.kda = +(stats.deaths === 0 ? 0 : ((stats.kills + stats.assists) / stats.deaths)).toFixed(2)
|
2020-01-25 11:33:28 +00:00
|
|
|
stats.realKda = stats.kda
|
2019-11-06 17:46:45 +00:00
|
|
|
}
|
|
|
|
|
|
2019-11-03 19:59:21 +00:00
|
|
|
// Percent stats / Per minute stats : only for detailed match
|
|
|
|
|
let percentStats
|
|
|
|
|
if (detailed) {
|
|
|
|
|
percentStats = {
|
2019-11-16 16:43:55 +00:00
|
|
|
minions: +(stats.minions / (match.gameDuration / 60)).toFixed(2),
|
|
|
|
|
vision: +(stats.vision / (match.gameDuration / 60)).toFixed(2),
|
2019-11-06 17:46:45 +00:00
|
|
|
gold: +(player.stats.goldEarned * 100 / teamStats.gold).toFixed(1) + '%',
|
2019-11-03 19:59:21 +00:00
|
|
|
dmgChamp: +(player.stats.totalDamageDealtToChampions * 100 / teamStats.dmgChamp).toFixed(1) + '%',
|
2020-09-26 19:38:32 +00:00
|
|
|
dmgObj: +(teamStats.dmgObj ? player.stats.damageDealtToObjectives * 100 / teamStats.dmgObj : 0).toFixed(1) + '%',
|
2019-11-03 19:59:21 +00:00
|
|
|
dmgTaken: +(player.stats.totalDamageTaken * 100 / teamStats.dmgTaken).toFixed(1) + '%',
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-06 17:46:45 +00:00
|
|
|
stats.kp = teamStats.kills === 0 ? '0%' : +((stats.kills + stats.assists) * 100 / teamStats.kills).toFixed(1) + '%'
|
2019-11-03 19:59:21 +00:00
|
|
|
} else {
|
2019-11-16 16:43:55 +00:00
|
|
|
const totalKills = match.participants.reduce((prev, current) => {
|
2019-11-03 19:59:21 +00:00
|
|
|
if (current.teamId !== player.teamId) {
|
|
|
|
|
return prev
|
|
|
|
|
}
|
|
|
|
|
return prev + current.stats.kills
|
|
|
|
|
}, 0)
|
|
|
|
|
|
2020-08-28 10:09:28 +00:00
|
|
|
stats.criticalStrike = player.stats.largestCriticalStrike
|
|
|
|
|
stats.killingSpree = player.stats.largestKillingSpree
|
|
|
|
|
stats.doubleKills = player.stats.doubleKills
|
|
|
|
|
stats.tripleKills = player.stats.tripleKills
|
|
|
|
|
stats.quadraKills = player.stats.quadraKills
|
|
|
|
|
stats.pentaKills = player.stats.pentaKills
|
|
|
|
|
stats.heal = player.stats.totalHeal
|
|
|
|
|
stats.towers = player.stats.turretKills
|
|
|
|
|
stats.longestLiving = player.stats.longestTimeSpentLiving
|
2019-11-08 14:39:56 +00:00
|
|
|
stats.kp = totalKills === 0 ? 0 : +((stats.kills + stats.assists) * 100 / totalKills).toFixed(1)
|
2019-11-03 19:59:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let primaryRune = null
|
|
|
|
|
let secondaryRune = null
|
|
|
|
|
if (player.stats.perkPrimaryStyle) {
|
2020-01-14 21:04:45 +00:00
|
|
|
({ primaryRune, secondaryRune } = this.getPerksImages(player.stats.perk0, player.stats.perkSubStyle))
|
2019-11-03 19:59:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const items = []
|
|
|
|
|
for (let i = 0; i < 6; i++) {
|
2019-11-14 20:22:01 +00:00
|
|
|
const id = player.stats['item' + i]
|
2019-11-16 16:43:55 +00:00
|
|
|
if (id === 0) {
|
2019-11-14 20:22:01 +00:00
|
|
|
items.push(null)
|
|
|
|
|
continue
|
|
|
|
|
}
|
2019-11-23 16:34:34 +00:00
|
|
|
|
|
|
|
|
const item = this.items.find(i => i.id === id)
|
|
|
|
|
const itemUrl = item.iconPath.split('/assets/')[1].toLowerCase()
|
|
|
|
|
|
2019-11-14 20:22:01 +00:00
|
|
|
items.push({
|
2019-11-23 16:34:34 +00:00
|
|
|
image: `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${itemUrl}`,
|
|
|
|
|
name: item.name,
|
|
|
|
|
description: item.description,
|
|
|
|
|
price: item.priceTotal
|
2019-11-14 20:22:01 +00:00
|
|
|
})
|
2019-11-03 19:59:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const firstSum = player.spell1Id
|
|
|
|
|
const secondSum = player.spell2Id
|
|
|
|
|
|
2019-09-29 16:06:54 +00:00
|
|
|
return {
|
2019-11-03 19:59:21 +00:00
|
|
|
name,
|
2020-01-20 20:35:26 +00:00
|
|
|
summonerId: identity.player.summonerId,
|
2019-11-03 19:59:21 +00:00
|
|
|
champion,
|
|
|
|
|
role,
|
|
|
|
|
primaryRune,
|
|
|
|
|
secondaryRune,
|
|
|
|
|
level,
|
|
|
|
|
items,
|
|
|
|
|
firstSum,
|
|
|
|
|
secondSum,
|
|
|
|
|
stats,
|
|
|
|
|
percentStats,
|
2019-09-29 16:06:54 +00:00
|
|
|
}
|
|
|
|
|
}
|
2019-11-16 16:43:55 +00:00
|
|
|
|
2020-01-14 21:04:45 +00:00
|
|
|
/**
|
|
|
|
|
* Return the icons of the primary rune and secondary category
|
|
|
|
|
* @param perk0 primary perks id
|
|
|
|
|
* @param perkSubStyle secondary perks category
|
|
|
|
|
*/
|
|
|
|
|
getPerksImages(perk0, perkSubStyle) {
|
|
|
|
|
const firstRune = this.perks.find(p => p.id === perk0)
|
|
|
|
|
const firstRuneUrl = firstRune.iconPath.split('/assets/')[1].toLowerCase()
|
|
|
|
|
const primaryRune = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${firstRuneUrl}`
|
|
|
|
|
|
|
|
|
|
const secondRuneStyle = this.perkstyles.find(p => p.id === perkSubStyle)
|
2020-02-06 19:05:14 +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}` : ''
|
2020-01-14 21:04:45 +00:00
|
|
|
|
|
|
|
|
return { primaryRune, secondaryRune }
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-16 16:43:55 +00:00
|
|
|
/**
|
|
|
|
|
* Return the lane of the summoner according to timeline
|
|
|
|
|
* @param timeline from Riot Api
|
2020-06-16 19:36:39 +00:00
|
|
|
* @param gamemode of the match to check if a role is needed
|
2019-11-16 16:43:55 +00:00
|
|
|
*/
|
2020-06-16 19:36:39 +00:00
|
|
|
getRoleName(timeline, gamemode) {
|
2020-06-21 18:58:04 +00:00
|
|
|
if (!queuesWithRole.includes(gamemode)) {
|
2020-06-16 19:36:39 +00:00
|
|
|
return 'NONE'
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-16 16:43:55 +00:00
|
|
|
if (timeline.lane === 'BOTTOM' && timeline.role.includes('SUPPORT')) {
|
|
|
|
|
return 'SUPPORT'
|
|
|
|
|
}
|
2020-06-16 19:36:39 +00:00
|
|
|
|
2019-11-16 16:43:55 +00:00
|
|
|
return timeline.lane
|
|
|
|
|
}
|
2019-12-01 19:44:41 +00:00
|
|
|
|
2020-06-21 18:58:04 +00:00
|
|
|
/**
|
|
|
|
|
* Return the 5 roles of a team based on champions
|
|
|
|
|
* @param team 5 champions + smite from a team
|
|
|
|
|
*/
|
|
|
|
|
getTeamRoles(team) {
|
2020-06-25 12:05:44 +00:00
|
|
|
const teamJunglers = team.filter(p => p.jungle && !p.support)
|
2020-06-21 18:58:04 +00:00
|
|
|
const jungle = teamJunglers.length === 1 ? teamJunglers[0].champion : null
|
2020-06-25 12:05:44 +00:00
|
|
|
const teamSupports = team.filter(p => p.support && !p.jungle)
|
|
|
|
|
const support = teamSupports.length === 1 ? teamSupports[0].champion : null
|
2020-06-21 18:58:04 +00:00
|
|
|
|
2020-09-29 16:02:44 +00:00
|
|
|
return RoleIdentificationService.getRoles(this.championRoles, team.map(p => p.champion), jungle, support)
|
2020-06-21 18:58:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update roles for a team if Riot's ones are badly identified
|
|
|
|
|
* @param {Object} team 5 players data of the team
|
|
|
|
|
* @param {Array} champs 5 champions + smite from the team
|
|
|
|
|
* @param {Object} playerData data of the searched player, only for basic matches
|
|
|
|
|
*/
|
|
|
|
|
updateTeamRoles(team, champs, playerData = null) {
|
2020-06-25 12:05:44 +00:00
|
|
|
// const actualRoles = [...new Set(team.map(p => p.role))]
|
|
|
|
|
// if (actualRoles.length === 5) {
|
|
|
|
|
// return
|
|
|
|
|
// }
|
2020-06-21 18:58:04 +00:00
|
|
|
|
|
|
|
|
champs = this.getTeamRoles(champs)
|
|
|
|
|
for (const summoner of team) {
|
|
|
|
|
summoner.role = Object.entries(champs).find(([, champion]) => summoner.champion.id === champion)[0]
|
|
|
|
|
|
|
|
|
|
if (playerData && summoner.champion.id === playerData.champion.id) {
|
|
|
|
|
playerData.role = summoner.role
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} match from Riot Api
|
|
|
|
|
* @param {Array} allyTeam 5 players of the first team
|
|
|
|
|
* @param {Array} enemyTeam 5 players of the second team
|
|
|
|
|
* @param {Number} allyTeamId team id of the searched player, only for basic matches
|
|
|
|
|
* @param {Object} playerData data of the searched player, only for basic matches
|
|
|
|
|
*/
|
|
|
|
|
getMatchRoles(match, allyTeam, enemyTeam, allyTeamId = 100, playerData = null) {
|
|
|
|
|
if (!this.championRoles || !queuesWithRole.includes(match.queueId)) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let allyChamps = []
|
|
|
|
|
let enemyChamps = []
|
|
|
|
|
match.participants.map(p => {
|
2020-06-25 12:05:44 +00:00
|
|
|
const items = [p.stats.item0, p.stats.item1, p.stats.item2, p.stats.item3, p.stats.item4, p.stats.item5]
|
|
|
|
|
const playerRole = {
|
|
|
|
|
champion: p.championId,
|
|
|
|
|
jungle: p.spell1Id === 11 || p.spell2Id === 11,
|
|
|
|
|
support: supportItems.some(suppItem => items.includes(suppItem))
|
|
|
|
|
}
|
2020-06-21 18:58:04 +00:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-01 19:44:41 +00:00
|
|
|
/**
|
|
|
|
|
* Get Summoner Spell Data from CDragon
|
|
|
|
|
* @param id of the summonerSpell
|
|
|
|
|
*/
|
|
|
|
|
getSummonerSpell(id) {
|
|
|
|
|
if (id === 0) return null
|
|
|
|
|
const spell = this.summonerSpells.find(s => s.id === id)
|
|
|
|
|
const spellName = spell.iconPath.split('/assets/')[1].toLowerCase()
|
|
|
|
|
return {
|
|
|
|
|
name: spell.name,
|
|
|
|
|
description: spell.description,
|
|
|
|
|
icon: `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${spellName}`
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-29 16:06:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = MatchTransformer
|