mirror of
https://github.com/vkaelin/LeagueStats.git
synced 2026-03-25 12:57:28 +00:00
feat: add detailed-match feature (without ranks for now)
This commit is contained in:
parent
2f677ab17b
commit
973355c201
14 changed files with 463 additions and 146 deletions
|
|
@ -91,29 +91,29 @@
|
||||||
<div
|
<div
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: `url(${
|
backgroundImage: `url(${
|
||||||
player.firstSum ? player.firstSum.icon : null
|
player.summonerSpell1 ? player.summonerSpell1.icon : null
|
||||||
})`,
|
})`,
|
||||||
}"
|
}"
|
||||||
:class="{ 'cursor-pointer': player.firstSum }"
|
:class="{ 'cursor-pointer': player.summonerSpell1 }"
|
||||||
class="w-4 h-4 bg-center bg-cover rounded-md bg-blue-1000"
|
class="w-4 h-4 bg-center bg-cover rounded-md bg-blue-1000"
|
||||||
></div>
|
></div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="player.firstSum" #default>
|
<template v-if="player.summonerSpell1" #default>
|
||||||
<div
|
<div
|
||||||
class="flex max-w-sm p-2 text-xs text-left text-white select-none"
|
class="flex max-w-sm p-2 text-xs text-left text-white select-none"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: `url('${player.firstSum.icon}')`,
|
backgroundImage: `url('${player.summonerSpell1.icon}')`,
|
||||||
}"
|
}"
|
||||||
class="flex-shrink-0 w-12 h-12 ml-1 bg-center bg-cover rounded-md bg-blue-1000"
|
class="flex-shrink-0 w-12 h-12 ml-1 bg-center bg-cover rounded-md bg-blue-1000"
|
||||||
></div>
|
></div>
|
||||||
<div class="ml-2 leading-tight">
|
<div class="ml-2 leading-tight">
|
||||||
<div class="text-base leading-none">
|
<div class="text-base leading-none">
|
||||||
{{ player.firstSum.name }}
|
{{ player.summonerSpell1.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 font-light text-blue-200">
|
<div class="mt-1 font-light text-blue-200">
|
||||||
{{ player.firstSum.description }}
|
{{ player.summonerSpell1.description }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -124,29 +124,29 @@
|
||||||
<div
|
<div
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: `url(${
|
backgroundImage: `url(${
|
||||||
player.secondSum ? player.secondSum.icon : null
|
player.summonerSpell2 ? player.summonerSpell2.icon : null
|
||||||
})`,
|
})`,
|
||||||
}"
|
}"
|
||||||
:class="{ 'cursor-pointer': player.secondSum }"
|
:class="{ 'cursor-pointer': player.summonerSpell2 }"
|
||||||
class="w-4 h-4 bg-center bg-cover rounded-md bg-blue-1000"
|
class="w-4 h-4 bg-center bg-cover rounded-md bg-blue-1000"
|
||||||
></div>
|
></div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="player.secondSum" #default>
|
<template v-if="player.summonerSpell2" #default>
|
||||||
<div
|
<div
|
||||||
class="flex max-w-sm p-2 text-xs text-left text-white select-none"
|
class="flex max-w-sm p-2 text-xs text-left text-white select-none"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: `url('${player.secondSum.icon}')`,
|
backgroundImage: `url('${player.summonerSpell2.icon}')`,
|
||||||
}"
|
}"
|
||||||
class="flex-shrink-0 w-12 h-12 ml-1 bg-center bg-cover rounded-md bg-blue-1000"
|
class="flex-shrink-0 w-12 h-12 ml-1 bg-center bg-cover rounded-md bg-blue-1000"
|
||||||
></div>
|
></div>
|
||||||
<div class="ml-2 leading-tight">
|
<div class="ml-2 leading-tight">
|
||||||
<div class="text-base leading-none">
|
<div class="text-base leading-none">
|
||||||
{{ player.secondSum.name }}
|
{{ player.summonerSpell2.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 font-light text-blue-200">
|
<div class="mt-1 font-light text-blue-200">
|
||||||
{{ player.secondSum.description }}
|
{{ player.summonerSpell2.description }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -195,7 +195,7 @@
|
||||||
class="flex flex-col items-start justify-center ml-1 leading-none"
|
class="flex flex-col items-start justify-center ml-1 leading-none"
|
||||||
>
|
>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="player.firstSum"
|
v-if="player.summonerSpell1"
|
||||||
:to="{
|
:to="{
|
||||||
name: 'summoner',
|
name: 'summoner',
|
||||||
params: { region: $route.params.region, name: player.name },
|
params: { region: $route.params.region, name: player.name },
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Ripple>
|
</Ripple>
|
||||||
<DetailedMatch :data="getMatchDetails(data.gameId) || {}" :details-open="showDetails" />
|
<DetailedMatch :data="getMatchDetails(data.matchId) || {}" :details-open="showDetails" />
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -223,8 +223,8 @@ export default {
|
||||||
displayDetails() {
|
displayDetails() {
|
||||||
this.showDetails = !this.showDetails
|
this.showDetails = !this.showDetails
|
||||||
|
|
||||||
if (!this.getMatchDetails(this.data.gameId)) {
|
if (!this.getMatchDetails(this.data.matchId)) {
|
||||||
this.matchDetails(this.data.gameId)
|
this.matchDetails(this.data.matchId)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isSummonerProfile(account_id) {
|
isSummonerProfile(account_id) {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,20 @@ import store from '@/store'
|
||||||
|
|
||||||
const leaguesNumbers = { 'I': 1, 'II': 2, 'III': 3, 'IV': 4 }
|
const leaguesNumbers = { 'I': 1, 'II': 2, 'III': 3, 'IV': 4 }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the url of the of the player runes
|
||||||
|
* @param {Object} perks : from the API
|
||||||
|
*/
|
||||||
|
export function getPrimaryAndSecondaryRune(perks) {
|
||||||
|
const primaryRune = perks.selected.length ? store.state.cdragon.runes.perks[perks.selected[0]] : null
|
||||||
|
const secondaryRune = store.state.cdragon.runes.perkstyles[perks.secondaryStyle]
|
||||||
|
|
||||||
|
return {
|
||||||
|
primaryRune: primaryRune ? createCDragonAssetUrl(primaryRune.icon) : null,
|
||||||
|
secondaryRune: secondaryRune ? createCDragonAssetUrl(secondaryRune.icon) : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all the infos about a list of matches built with the Riot API data
|
* Return all the infos about a list of matches built with the Riot API data
|
||||||
* @param {Object} RiotData : all data from the Riot API
|
* @param {Object} RiotData : all data from the Riot API
|
||||||
|
|
@ -12,10 +26,9 @@ const leaguesNumbers = { 'I': 1, 'II': 2, 'III': 3, 'IV': 4 }
|
||||||
export function createMatchData(matches) {
|
export function createMatchData(matches) {
|
||||||
for (const match of matches) {
|
for (const match of matches) {
|
||||||
// Runes
|
// Runes
|
||||||
const primaryRune = match.perks.selected.length ? store.state.cdragon.runes.perks[match.perks.selected[0]] : null
|
const runes = getPrimaryAndSecondaryRune(match.perks)
|
||||||
const secondaryRune = store.state.cdragon.runes.perkstyles[match.perks.secondaryStyle]
|
match.primaryRune = runes.primaryRune
|
||||||
match.primaryRune = primaryRune ? createCDragonAssetUrl(primaryRune.icon) : null
|
match.secondaryRune = runes.secondaryRune
|
||||||
match.secondaryRune = secondaryRune ? createCDragonAssetUrl(secondaryRune.icon) : null
|
|
||||||
|
|
||||||
const date = new Date(match.date)
|
const date = new Date(match.date)
|
||||||
const dateOptions = { day: '2-digit', month: '2-digit', year: 'numeric' }
|
const dateOptions = { day: '2-digit', month: '2-digit', year: 'numeric' }
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ export const state = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
MATCH_LOADING(state, gameId) {
|
MATCH_LOADING(state, matchId) {
|
||||||
const alreadyIn = state.matches.find(m => m.gameId === gameId)
|
const alreadyIn = state.matches.find(m => m.matchId === matchId)
|
||||||
if (!alreadyIn) {
|
if (!alreadyIn) {
|
||||||
state.matches.push({ gameId: gameId, status: 'loading' })
|
state.matches.push({ matchId, status: 'loading' })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MATCH_FOUND(state, matchDetails) {
|
MATCH_FOUND(state, matchDetails) {
|
||||||
|
|
@ -28,27 +28,27 @@ export const mutations = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
async matchDetails({ commit, rootState }, gameId) {
|
async matchDetails({ commit }, matchId) {
|
||||||
commit('MATCH_LOADING', gameId)
|
commit('MATCH_LOADING', matchId)
|
||||||
const region = rootState.regionsList[rootState.settings.region]
|
console.log('MATCH DETAILS STORE', matchId)
|
||||||
console.log('MATCH DETAILS STORE', gameId, region)
|
|
||||||
|
|
||||||
const resp = await axios(({ url: 'match/details', data: { gameId, region }, method: 'POST' })).catch(() => { })
|
const resp = await axios(({ url: 'match/details', data: { matchId }, method: 'POST' })).catch(() => { })
|
||||||
console.log('--- DETAILS INFOS ---')
|
console.log('--- DETAILS INFOS ---')
|
||||||
console.log(resp.data)
|
console.log(resp.data)
|
||||||
commit('MATCH_FOUND', resp.data.matchDetails)
|
commit('MATCH_FOUND', resp.data.matchDetails)
|
||||||
|
|
||||||
// If the ranks of the players are not yet known
|
// TODO: add ranks back when it's done on the API
|
||||||
if (resp.data.matchDetails.blueTeam.players[0].rank === undefined) {
|
// // If the ranks of the players are not yet known
|
||||||
const ranks = await axios(({ url: 'match/details/ranks', data: { gameId, region }, method: 'POST' })).catch(() => { })
|
// if (resp.data.matchDetails.blueTeam.players[0].rank === undefined) {
|
||||||
if (!ranks) return
|
// const ranks = await axios(({ url: 'match/details/ranks', data: { gameId, region }, method: 'POST' })).catch(() => { })
|
||||||
console.log('--- RANK OF MATCH DETAILS ---')
|
// if (!ranks) return
|
||||||
console.log(ranks.data)
|
// console.log('--- RANK OF MATCH DETAILS ---')
|
||||||
commit('MATCH_RANKS_FOUND', { gameId, ...ranks.data })
|
// console.log(ranks.data)
|
||||||
}
|
// commit('MATCH_RANKS_FOUND', { gameId, ...ranks.data })
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
getMatchDetails: state => gameId => state.matches.find(m => m.gameId === gameId),
|
getMatchDetails: state => matchId => state.matches.find(m => m.matchId === matchId),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
import Match from 'App/Models/Match'
|
||||||
|
import DetailedMatchSerializer from 'App/Serializers/DetailedMatchSerializer'
|
||||||
import MatchService from 'App/Services/MatchService'
|
import MatchService from 'App/Services/MatchService'
|
||||||
import StatsService from 'App/Services/StatsService'
|
import StatsService from 'App/Services/StatsService'
|
||||||
|
import DetailedMatchValidator from 'App/Validators/DetailedMatchValidator'
|
||||||
import MatchesIndexValidator from 'App/Validators/MatchesIndexValidator'
|
import MatchesIndexValidator from 'App/Validators/MatchesIndexValidator'
|
||||||
|
|
||||||
export default class MatchesController {
|
export default class MatchesController {
|
||||||
|
|
@ -9,7 +12,6 @@ export default class MatchesController {
|
||||||
* @param ctx
|
* @param ctx
|
||||||
*/
|
*/
|
||||||
public async index({ request, response }: HttpContextContract) {
|
public async index({ request, response }: HttpContextContract) {
|
||||||
console.log('More Matches Request')
|
|
||||||
const { puuid, region, matchIds, season } = await request.validate(MatchesIndexValidator)
|
const { puuid, region, matchIds, season } = await request.validate(MatchesIndexValidator)
|
||||||
const matches = await MatchService.getMatches(region, matchIds, puuid)
|
const matches = await MatchService.getMatches(region, matchIds, puuid)
|
||||||
|
|
||||||
|
|
@ -19,4 +21,27 @@ export default class MatchesController {
|
||||||
stats,
|
stats,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST - Return details data for one specific match
|
||||||
|
* @param ctx
|
||||||
|
*/
|
||||||
|
public async show({ request, response }: HttpContextContract) {
|
||||||
|
console.time('MatchDetails')
|
||||||
|
const { matchId } = await request.validate(DetailedMatchValidator)
|
||||||
|
|
||||||
|
const match = await Match.query()
|
||||||
|
.where('id', matchId)
|
||||||
|
.preload('teams')
|
||||||
|
.preload('players')
|
||||||
|
.firstOrFail()
|
||||||
|
|
||||||
|
const matchDetails = DetailedMatchSerializer.serializeOneMatch(match)
|
||||||
|
|
||||||
|
console.timeEnd('MatchDetails')
|
||||||
|
|
||||||
|
return response.json({
|
||||||
|
matchDetails,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ class MatchParser {
|
||||||
damage_dealt_champions: player.totalDamageDealtToChampions,
|
damage_dealt_champions: player.totalDamageDealtToChampions,
|
||||||
damage_taken: player.totalDamageTaken,
|
damage_taken: player.totalDamageTaken,
|
||||||
heal: player.totalHeal,
|
heal: player.totalHeal,
|
||||||
minions: player.totalMinionsKilled,
|
minions: player.totalMinionsKilled + player.neutralMinionsKilled,
|
||||||
critical_strike: player.largestCriticalStrike,
|
critical_strike: player.largestCriticalStrike,
|
||||||
killing_spree: player.killingSprees,
|
killing_spree: player.killingSprees,
|
||||||
time_spent_living: player.longestTimeSpentLiving,
|
time_spent_living: player.longestTimeSpentLiving,
|
||||||
|
|
|
||||||
|
|
@ -2,38 +2,10 @@ import { getSeasonNumber, sortTeamByRole } from 'App/helpers'
|
||||||
import Match from 'App/Models/Match'
|
import Match from 'App/Models/Match'
|
||||||
import MatchPlayer from 'App/Models/MatchPlayer'
|
import MatchPlayer from 'App/Models/MatchPlayer'
|
||||||
import { TeamPosition } from 'App/Parsers/ParsedType'
|
import { TeamPosition } from 'App/Parsers/ParsedType'
|
||||||
import CDragonService from 'App/Services/CDragonService'
|
|
||||||
import MatchSerializer from './MatchSerializer'
|
import MatchSerializer from './MatchSerializer'
|
||||||
import {
|
import { SerializedMatch, SerializedMatchStats, SerializedMatchTeamPlayer } from './SerializedTypes'
|
||||||
SerializedMatch,
|
|
||||||
SerializedMatchChampion,
|
|
||||||
SerializedMatchItem,
|
|
||||||
SerializedMatchPerks,
|
|
||||||
SerializedMatchStats,
|
|
||||||
SerializedMatchSummonerSpell,
|
|
||||||
SerializedMatchTeamPlayer,
|
|
||||||
} from './SerializedTypes'
|
|
||||||
|
|
||||||
class BasicMatchSerializer extends MatchSerializer {
|
class BasicMatchSerializer extends MatchSerializer {
|
||||||
/**
|
|
||||||
* Get champion specific data
|
|
||||||
* @param id of the champion
|
|
||||||
*/
|
|
||||||
public getChampion(id: number): SerializedMatchChampion {
|
|
||||||
const originalChampionData = CDragonService.champions[id]
|
|
||||||
const icon =
|
|
||||||
CDragonService.BASE_URL +
|
|
||||||
originalChampionData.squarePortraitPath.split('/assets/')[1].toLowerCase()
|
|
||||||
|
|
||||||
return {
|
|
||||||
icon,
|
|
||||||
id: originalChampionData.id,
|
|
||||||
name: originalChampionData.name,
|
|
||||||
alias: originalChampionData.alias,
|
|
||||||
roles: originalChampionData.roles,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getPlayerSummary(player: MatchPlayer): SerializedMatchTeamPlayer {
|
protected getPlayerSummary(player: MatchPlayer): SerializedMatchTeamPlayer {
|
||||||
return {
|
return {
|
||||||
puuid: player.summonerPuuid,
|
puuid: player.summonerPuuid,
|
||||||
|
|
@ -47,57 +19,6 @@ class BasicMatchSerializer extends MatchSerializer {
|
||||||
return players.map((p) => this.getPlayerSummary(p)).sort(sortTeamByRole)
|
return players.map((p) => this.getPlayerSummary(p)).sort(sortTeamByRole)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get Summoner Spell Data from CDragon
|
|
||||||
* @param id of the summonerSpell
|
|
||||||
*/
|
|
||||||
public getSummonerSpell(id: number): SerializedMatchSummonerSpell | null {
|
|
||||||
const spell = CDragonService.summonerSpells[id]
|
|
||||||
if (id === 0 || !spell) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
const spellName = spell.iconPath.split('/assets/')[1].toLowerCase()
|
|
||||||
return {
|
|
||||||
name: spell.name,
|
|
||||||
description: spell.description,
|
|
||||||
icon: `${CDragonService.BASE_URL}${spellName}`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getItems(player: MatchPlayer): Array<SerializedMatchItem | null> {
|
|
||||||
const items: (SerializedMatchItem | null)[] = []
|
|
||||||
for (let i = 0; i < 6; i++) {
|
|
||||||
const id = player['item' + i]
|
|
||||||
if (id === 0) {
|
|
||||||
items.push(null)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const item = CDragonService.items[id]
|
|
||||||
if (!item) {
|
|
||||||
items.push(null)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const itemUrl = item.iconPath.split('/assets/')[1].toLowerCase()
|
|
||||||
items.push({
|
|
||||||
image: `${CDragonService.BASE_URL}${itemUrl}`,
|
|
||||||
name: item.name,
|
|
||||||
description: item.description,
|
|
||||||
price: item.priceTotal,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getPerks(player: MatchPlayer): SerializedMatchPerks {
|
|
||||||
return {
|
|
||||||
primaryStyle: player.perksPrimaryStyle,
|
|
||||||
secondaryStyle: player.perksSecondaryStyle,
|
|
||||||
selected: player.perksSelected,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getStats(player: MatchPlayer): SerializedMatchStats {
|
protected getStats(player: MatchPlayer): SerializedMatchStats {
|
||||||
return {
|
return {
|
||||||
kills: player.kills,
|
kills: player.kills,
|
||||||
|
|
@ -137,27 +58,18 @@ class BasicMatchSerializer extends MatchSerializer {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
allyTeam: this.getTeamSummary(allyPlayers),
|
allyTeam: this.getTeamSummary(allyPlayers),
|
||||||
champion: this.getChampion(identity.championId),
|
|
||||||
date: match.date,
|
date: match.date,
|
||||||
enemyTeam: this.getTeamSummary(enemyPlayers),
|
enemyTeam: this.getTeamSummary(enemyPlayers),
|
||||||
matchId: match.id,
|
matchId: match.id,
|
||||||
gamemode: match.gamemode,
|
gamemode: match.gamemode,
|
||||||
items: this.getItems(identity),
|
|
||||||
level: identity.champLevel,
|
|
||||||
map: match.map,
|
map: match.map,
|
||||||
name: identity.summonerName,
|
|
||||||
newMatch,
|
newMatch,
|
||||||
perks: this.getPerks(identity),
|
|
||||||
region: match.region,
|
region: match.region,
|
||||||
result: allyTeam.result,
|
result: allyTeam.result,
|
||||||
role: TeamPosition[identity.teamPosition],
|
|
||||||
season: getSeasonNumber(match.date),
|
season: getSeasonNumber(match.date),
|
||||||
stats: this.getStats(identity),
|
stats: this.getStats(identity),
|
||||||
summonerId: identity.summonerId,
|
|
||||||
summonerSpell1: this.getSummonerSpell(identity.summoner1Id),
|
|
||||||
summonerSpell2: this.getSummonerSpell(identity.summoner2Id),
|
|
||||||
summonerPuuid: puuid,
|
|
||||||
time: match.gameDuration,
|
time: match.gameDuration,
|
||||||
|
...this.getPlayerBase(identity),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public serialize(matches: Match[], puuid: string, newMatches = false): SerializedMatch[] {
|
public serialize(matches: Match[], puuid: string, newMatches = false): SerializedMatch[] {
|
||||||
|
|
|
||||||
134
server-v2/app/Serializers/DetailedMatchSerializer.ts
Normal file
134
server-v2/app/Serializers/DetailedMatchSerializer.ts
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
import { sortTeamByRole } from 'App/helpers'
|
||||||
|
import Match from 'App/Models/Match'
|
||||||
|
import MatchPlayer from 'App/Models/MatchPlayer'
|
||||||
|
import MatchTeam from 'App/Models/MatchTeam'
|
||||||
|
import MatchSerializer from './MatchSerializer'
|
||||||
|
import {
|
||||||
|
SerializedDetailedMatch,
|
||||||
|
SerializedDetailedMatchBan,
|
||||||
|
SerializedDetailedMatchPlayer,
|
||||||
|
SerializedDetailedMatchStats,
|
||||||
|
SerializedDetailedMatchTeam,
|
||||||
|
SerializedDetailedMatchTeamStats,
|
||||||
|
} from './SerializedTypes'
|
||||||
|
|
||||||
|
class DetailedMatchSerializer extends MatchSerializer {
|
||||||
|
protected getTeamBans(team: MatchTeam): SerializedDetailedMatchBan[] {
|
||||||
|
if (!team.bans || !team.banOrders) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return team.bans.map((banId, index) => {
|
||||||
|
return {
|
||||||
|
champion: this.getChampion(banId),
|
||||||
|
championId: banId,
|
||||||
|
pickTurn: team.banOrders![index],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getTeamStats(players: MatchPlayer[]): SerializedDetailedMatchTeamStats {
|
||||||
|
return players.reduce(
|
||||||
|
(acc, player) => {
|
||||||
|
acc.kills += player.kills
|
||||||
|
acc.deaths += player.deaths
|
||||||
|
acc.assists += player.assists
|
||||||
|
acc.gold += player.gold
|
||||||
|
acc.dmgChamp += player.damageDealtChampions
|
||||||
|
acc.dmgObj += player.damageDealtObjectives
|
||||||
|
acc.dmgTaken += player.damageTaken
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
{ kills: 0, deaths: 0, assists: 0, gold: 0, dmgChamp: 0, dmgObj: 0, dmgTaken: 0 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getPlayersDetailed(
|
||||||
|
players: MatchPlayer[],
|
||||||
|
teamStats: SerializedDetailedMatchTeamStats,
|
||||||
|
gameDuration: number
|
||||||
|
): SerializedDetailedMatchPlayer[] {
|
||||||
|
return players
|
||||||
|
.map((player) => {
|
||||||
|
const stats: SerializedDetailedMatchStats = {
|
||||||
|
kills: player.kills,
|
||||||
|
deaths: player.deaths,
|
||||||
|
assists: player.assists,
|
||||||
|
minions: player.minions,
|
||||||
|
vision: player.visionScore,
|
||||||
|
gold: player.gold,
|
||||||
|
dmgChamp: player.damageDealtChampions,
|
||||||
|
dmgObj: player.damageDealtObjectives,
|
||||||
|
dmgTaken: player.damageTaken,
|
||||||
|
kp: player.kp.toFixed(1) + '%',
|
||||||
|
kda: player.kills + player.assists !== 0 && player.deaths === 0 ? '∞' : player.kda,
|
||||||
|
realKda: player.kda,
|
||||||
|
}
|
||||||
|
const percentStats = {
|
||||||
|
minions: +(player.minions / (gameDuration / 60)).toFixed(2),
|
||||||
|
vision: +(player.visionScore / (gameDuration / 60)).toFixed(2),
|
||||||
|
gold: +((player.gold * 100) / teamStats.gold).toFixed(1) + '%',
|
||||||
|
dmgChamp: +((player.damageDealtChampions * 100) / teamStats.dmgChamp).toFixed(1) + '%',
|
||||||
|
dmgObj:
|
||||||
|
+(
|
||||||
|
teamStats.dmgObj ? (player.damageDealtObjectives * 100) / teamStats.dmgObj : 0
|
||||||
|
).toFixed(1) + '%',
|
||||||
|
dmgTaken: +((player.damageTaken * 100) / teamStats.dmgTaken).toFixed(1) + '%',
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...this.getPlayerBase(player),
|
||||||
|
...this.getRuneIcons(player.perksSelected, player.perksSecondaryStyle),
|
||||||
|
stats,
|
||||||
|
percentStats,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sort(sortTeamByRole)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getTeamDetailed(
|
||||||
|
team: MatchTeam,
|
||||||
|
players: MatchPlayer[],
|
||||||
|
gameDuration: number
|
||||||
|
): SerializedDetailedMatchTeam {
|
||||||
|
const teamStats = this.getTeamStats(players)
|
||||||
|
|
||||||
|
return {
|
||||||
|
bans: this.getTeamBans(team),
|
||||||
|
barons: team.barons,
|
||||||
|
color: team.color === 100 ? 'Blue' : 'Red',
|
||||||
|
dragons: team.dragons,
|
||||||
|
inhibitors: team.inhibitors,
|
||||||
|
players: this.getPlayersDetailed(players, teamStats, gameDuration),
|
||||||
|
result: team.result,
|
||||||
|
riftHeralds: team.riftHeralds,
|
||||||
|
teamStats,
|
||||||
|
towers: team.towers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public serializeOneMatch(match: Match): SerializedDetailedMatch {
|
||||||
|
const blueTeam = match.teams.find((team) => team.color === 100)!
|
||||||
|
const redTeam = match.teams.find((team) => team.color === 200)!
|
||||||
|
|
||||||
|
const bluePlayers: MatchPlayer[] = []
|
||||||
|
const redPlayers: MatchPlayer[] = []
|
||||||
|
|
||||||
|
for (const p of match.players) {
|
||||||
|
p.team === 100 ? bluePlayers.push(p) : redPlayers.push(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
blueTeam: this.getTeamDetailed(blueTeam, bluePlayers, match.gameDuration),
|
||||||
|
date: match.date,
|
||||||
|
matchId: match.id,
|
||||||
|
gamemode: match.gamemode,
|
||||||
|
map: match.map,
|
||||||
|
redTeam: this.getTeamDetailed(redTeam, redPlayers, match.gameDuration),
|
||||||
|
region: match.region,
|
||||||
|
season: match.season,
|
||||||
|
time: match.gameDuration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new DetailedMatchSerializer()
|
||||||
|
|
@ -1 +1,102 @@
|
||||||
export default abstract class MatchSerializer {}
|
import MatchPlayer from 'App/Models/MatchPlayer'
|
||||||
|
import { TeamPosition } from 'App/Parsers/ParsedType'
|
||||||
|
import CDragonService from 'App/Services/CDragonService'
|
||||||
|
import {
|
||||||
|
SerializedBasePlayer,
|
||||||
|
SerializedMatchChampion,
|
||||||
|
SerializedMatchItem,
|
||||||
|
SerializedMatchPerks,
|
||||||
|
SerializedMatchSummonerSpell,
|
||||||
|
} from './SerializedTypes'
|
||||||
|
|
||||||
|
export default abstract class MatchSerializer {
|
||||||
|
/**
|
||||||
|
* Get champion specific data
|
||||||
|
* @param id of the champion
|
||||||
|
*/
|
||||||
|
public getChampion(id: number): SerializedMatchChampion {
|
||||||
|
const originalChampionData = CDragonService.champions[id]
|
||||||
|
const icon = CDragonService.createAssetUrl(originalChampionData.squarePortraitPath)
|
||||||
|
|
||||||
|
return {
|
||||||
|
icon,
|
||||||
|
id: originalChampionData.id,
|
||||||
|
name: originalChampionData.name,
|
||||||
|
alias: originalChampionData.alias,
|
||||||
|
roles: originalChampionData.roles,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get Summoner Spell Data from CDragon
|
||||||
|
* @param id of the summonerSpell
|
||||||
|
*/
|
||||||
|
public getSummonerSpell(id: number): SerializedMatchSummonerSpell | null {
|
||||||
|
const spell = CDragonService.summonerSpells[id]
|
||||||
|
if (id === 0 || !spell) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: spell.name,
|
||||||
|
description: spell.description,
|
||||||
|
icon: CDragonService.createAssetUrl(spell.iconPath),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getItems(player: MatchPlayer): Array<SerializedMatchItem | null> {
|
||||||
|
const items: (SerializedMatchItem | null)[] = []
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
const id = player['item' + i]
|
||||||
|
if (id === 0) {
|
||||||
|
items.push(null)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = CDragonService.items[id]
|
||||||
|
if (!item) {
|
||||||
|
items.push(null)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
image: CDragonService.createAssetUrl(item.iconPath),
|
||||||
|
name: item.name,
|
||||||
|
description: item.description,
|
||||||
|
price: item.priceTotal,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getPerks(player: MatchPlayer): SerializedMatchPerks {
|
||||||
|
return {
|
||||||
|
primaryStyle: player.perksPrimaryStyle,
|
||||||
|
secondaryStyle: player.perksSecondaryStyle,
|
||||||
|
selected: player.perksSelected,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getRuneIcons(perksSelected: number[], perksSecondaryStyle: number) {
|
||||||
|
const primaryRune = perksSelected.length ? CDragonService.perks[perksSelected[0]] : null
|
||||||
|
const secondaryRune = CDragonService.perkstyles[perksSecondaryStyle]
|
||||||
|
|
||||||
|
return {
|
||||||
|
primaryRune: primaryRune ? CDragonService.createAssetUrl(primaryRune.iconPath) : null,
|
||||||
|
secondaryRune: secondaryRune ? CDragonService.createAssetUrl(secondaryRune.iconPath) : null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getPlayerBase(player: MatchPlayer): SerializedBasePlayer {
|
||||||
|
return {
|
||||||
|
champion: this.getChampion(player.championId),
|
||||||
|
items: this.getItems(player),
|
||||||
|
level: player.champLevel,
|
||||||
|
name: player.summonerName,
|
||||||
|
perks: this.getPerks(player),
|
||||||
|
role: TeamPosition[player.teamPosition],
|
||||||
|
summonerId: player.summonerId,
|
||||||
|
summonerPuuid: player.summonerPuuid,
|
||||||
|
summonerSpell1: this.getSummonerSpell(player.summoner1Id),
|
||||||
|
summonerSpell2: this.getSummonerSpell(player.summoner2Id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,28 @@
|
||||||
export interface SerializedMatch {
|
export interface SerializedBasePlayer {
|
||||||
allyTeam: SerializedMatchTeamPlayer[]
|
|
||||||
champion: SerializedMatchChampion
|
champion: SerializedMatchChampion
|
||||||
date: number
|
|
||||||
enemyTeam: SerializedMatchTeamPlayer[]
|
|
||||||
matchId: string
|
|
||||||
gamemode: number
|
|
||||||
items: Array<SerializedMatchItem | null>
|
items: Array<SerializedMatchItem | null>
|
||||||
level: number
|
level: number
|
||||||
map: number
|
|
||||||
name: string
|
name: string
|
||||||
newMatch: boolean
|
|
||||||
perks: SerializedMatchPerks
|
perks: SerializedMatchPerks
|
||||||
region: string
|
|
||||||
result: string
|
|
||||||
role: string
|
role: string
|
||||||
season: number
|
|
||||||
stats: SerializedMatchStats
|
|
||||||
summonerId: string
|
summonerId: string
|
||||||
summonerPuuid: string
|
summonerPuuid: string
|
||||||
summonerSpell1: SerializedMatchSummonerSpell | null
|
summonerSpell1: SerializedMatchSummonerSpell | null
|
||||||
summonerSpell2: SerializedMatchSummonerSpell | null
|
summonerSpell2: SerializedMatchSummonerSpell | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedMatch extends SerializedBasePlayer {
|
||||||
|
allyTeam: SerializedMatchTeamPlayer[]
|
||||||
|
date: number
|
||||||
|
enemyTeam: SerializedMatchTeamPlayer[]
|
||||||
|
matchId: string
|
||||||
|
gamemode: number
|
||||||
|
map: number
|
||||||
|
newMatch: boolean
|
||||||
|
region: string
|
||||||
|
result: string
|
||||||
|
season: number
|
||||||
|
stats: SerializedMatchStats
|
||||||
time: number
|
time: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,3 +83,80 @@ export interface SerializedMatchStats {
|
||||||
tripleKills: number
|
tripleKills: number
|
||||||
vision: number
|
vision: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================
|
||||||
|
|
||||||
|
Detailed Match
|
||||||
|
|
||||||
|
============================ */
|
||||||
|
export interface SerializedDetailedMatch {
|
||||||
|
blueTeam: SerializedDetailedMatchTeam
|
||||||
|
date: number
|
||||||
|
matchId: string
|
||||||
|
gamemode: number
|
||||||
|
map: number
|
||||||
|
redTeam: SerializedDetailedMatchTeam
|
||||||
|
region: string
|
||||||
|
season: number
|
||||||
|
time: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedDetailedMatchTeam {
|
||||||
|
bans: SerializedDetailedMatchBan[]
|
||||||
|
barons: number
|
||||||
|
color: string
|
||||||
|
dragons: number
|
||||||
|
inhibitors: number
|
||||||
|
players: SerializedDetailedMatchPlayer[]
|
||||||
|
result: string
|
||||||
|
riftHeralds: number
|
||||||
|
teamStats: SerializedDetailedMatchTeamStats
|
||||||
|
towers: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedDetailedMatchBan {
|
||||||
|
champion: SerializedMatchChampion
|
||||||
|
championId: number
|
||||||
|
pickTurn: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedDetailedMatchPlayer extends SerializedBasePlayer {
|
||||||
|
stats: SerializedDetailedMatchStats
|
||||||
|
percentStats: SerializedDetailedMatchPercentStats
|
||||||
|
primaryRune: string | null
|
||||||
|
secondaryRune: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedDetailedMatchTeamStats {
|
||||||
|
assists: number
|
||||||
|
deaths: number
|
||||||
|
dmgChamp: number
|
||||||
|
dmgObj: number
|
||||||
|
dmgTaken: number
|
||||||
|
gold: number
|
||||||
|
kills: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedDetailedMatchStats {
|
||||||
|
assists: number
|
||||||
|
deaths: number
|
||||||
|
dmgChamp: number
|
||||||
|
dmgObj: number
|
||||||
|
dmgTaken: number
|
||||||
|
gold: number
|
||||||
|
kda: string | number
|
||||||
|
kills: number
|
||||||
|
kp: string
|
||||||
|
minions: number
|
||||||
|
realKda: number
|
||||||
|
vision: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedDetailedMatchPercentStats {
|
||||||
|
dmgChamp: string
|
||||||
|
dmgObj: string
|
||||||
|
dmgTaken: string
|
||||||
|
gold: string
|
||||||
|
minions: number
|
||||||
|
vision: number
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,14 @@ class CDragonService {
|
||||||
return dto.reduce((obj, item) => ((obj[item.id] = item), obj), {})
|
return dto.reduce((obj, item) => ((obj[item.id] = item), obj), {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give the full CDragon image path from the iconPath field
|
||||||
|
*/
|
||||||
|
public createAssetUrl(iconPath: string) {
|
||||||
|
const name = iconPath.split('/assets/')[1].toLowerCase()
|
||||||
|
return `${this.BASE_URL}${name}`
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get global Context with CDragon Data
|
* Get global Context with CDragon Data
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
42
server-v2/app/Validators/DetailedMatchValidator.ts
Normal file
42
server-v2/app/Validators/DetailedMatchValidator.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { schema } from '@ioc:Adonis/Core/Validator'
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
|
||||||
|
export default class DetailedMatchValidator {
|
||||||
|
constructor(protected ctx: HttpContextContract) {}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
* 1. The username must be of data type string. But then also, it should
|
||||||
|
* not contain special characters or numbers.
|
||||||
|
* ```
|
||||||
|
* schema.string({}, [ rules.alpha() ])
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 2. The email must be of data type string, formatted as a valid
|
||||||
|
* email. But also, not used by any other user.
|
||||||
|
* ```
|
||||||
|
* schema.string({}, [
|
||||||
|
* rules.email(),
|
||||||
|
* rules.unique({ table: 'users', column: 'email' }),
|
||||||
|
* ])
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public schema = schema.create({
|
||||||
|
matchId: schema.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
|
* for targeting nested fields and array expressions `(*)` for targeting all
|
||||||
|
* children of an array. For example:
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* 'profile.username.required': 'Username is required',
|
||||||
|
* 'scores.*.number': 'Define scores as valid numbers'
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public messages = {}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { SerializedMatchTeamPlayer } from './Serializers/SerializedTypes'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All League of Legends regions used in Riot API
|
* All League of Legends regions used in Riot API
|
||||||
*/
|
*/
|
||||||
|
|
@ -99,12 +97,16 @@ export function getCurrentSeason(): number {
|
||||||
return seasons[lastTimestamp]
|
return seasons[lastTimestamp]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SortableByRole {
|
||||||
|
role: string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sort array of Players by roles according to a specific order
|
* Sort array of Players by roles according to a specific order
|
||||||
* @param a first player
|
* @param a first player
|
||||||
* @param b second player
|
* @param b second player
|
||||||
*/
|
*/
|
||||||
export function sortTeamByRole(a: SerializedMatchTeamPlayer, b: SerializedMatchTeamPlayer) {
|
export function sortTeamByRole<T extends SortableByRole>(a: T, b: T) {
|
||||||
const sortingArr = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'UTILITY']
|
const sortingArr = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'UTILITY']
|
||||||
return sortingArr.indexOf(a.role) - sortingArr.indexOf(b.role)
|
return sortingArr.indexOf(a.role) - sortingArr.indexOf(b.role)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ Route.post('/summoner/records', 'SummonersController.records')
|
||||||
// Route.post('/summoner/live', 'SummonersController.liveMatchDetails')
|
// Route.post('/summoner/live', 'SummonersController.liveMatchDetails')
|
||||||
|
|
||||||
Route.post('/match', 'MatchesController.index')
|
Route.post('/match', 'MatchesController.index')
|
||||||
// Route.post('/match/details', 'MatchesController.show')
|
Route.post('/match/details', 'MatchesController.show')
|
||||||
// Route.post('/match/details/ranks', 'MatchesController.showRanks')
|
// Route.post('/match/details/ranks', 'MatchesController.showRanks')
|
||||||
|
|
||||||
Route.get('/cdragon/runes', 'CDragonController.runes')
|
Route.get('/cdragon/runes', 'CDragonController.runes')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue