feat: add detailed-match feature (without ranks for now)

This commit is contained in:
Kalane 2021-09-16 17:10:09 +02:00
parent 2f677ab17b
commit 973355c201
14 changed files with 463 additions and 146 deletions

View file

@ -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 },

View file

@ -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) {

View file

@ -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' }

View file

@ -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),
} }

View file

@ -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,
})
}
} }

View file

@ -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,

View file

@ -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[] {

View 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()

View file

@ -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),
}
}
}

View file

@ -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
}

View file

@ -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
*/ */

View 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 = {}
}

View file

@ -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)
} }

View file

@ -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')