feat: add live game tab back

This commit is contained in:
Kalane 2021-09-17 22:57:57 +02:00
parent 0b0aa48e69
commit 5a7c38281f
11 changed files with 249 additions and 42 deletions

View file

@ -30,11 +30,11 @@
class="flex flex-col items-center runes"
>
<div
:style="{backgroundImage: `url('${player.runes.primaryRune}')`}"
:style="{backgroundImage: `url('${getPrimarRune(player.perks)}')`}"
class="w-6 h-6 bg-center bg-cover"
></div>
<div
:style="{backgroundImage: `url('${player.runes.secondaryRune}')`}"
:style="{backgroundImage: `url('${getSecondaryRune(player.perks)}')`}"
class="w-3 h-3 mt-1 bg-center bg-cover"
></div>
</div>
@ -72,7 +72,11 @@
:class="[player.summonerId === account.id ? 'text-yellow-500' : 'hover:text-blue-200']"
class="font-semibold"
>{{ player.summonerName }}</router-link>
<div class="text-xs">Level {{ player.level }}</div>
<div
:class="[ally ? 'text-teal-300 ' : 'text-red-400 ']"
class="text-xs"
>{{ player.champion.name }}
</div>
</div>
</div>
</td>
@ -185,7 +189,7 @@
<script>
import { mapActions, mapState } from 'vuex'
import { getSummonerLink } from '@/helpers/summoner.js'
import { getSummonerLink, getPrimarRune, getSecondaryRune } from '@/helpers/summoner.js'
import { ContentLoader } from 'vue-content-loader'
export default {
@ -264,17 +268,13 @@ export default {
}
},
selectRunes(player) {
if(!player.perks) {
if(!player.perks)
return
}
this.displayOrHideRunes({
primaryStyle: player.perks.perkStyle,
secondaryStyle: player.perks.perkSubStyle,
selected: player.perks.perkIds
})
this.displayOrHideRunes(player.perks)
},
getSummonerLink,
getPrimarRune,
getSecondaryRune,
...mapActions('cdragon', ['displayOrHideRunes']),
}
}

View file

@ -6,17 +6,21 @@ import store from '@/store'
const leaguesNumbers = { 'I': 1, 'II': 2, 'III': 3, 'IV': 4 }
/**
* Get the url of the of the player runes
* Get the url of the of the player primary rune
* @param {Object} perks : from the API
*/
export function getPrimaryAndSecondaryRune(perks) {
export function getPrimarRune(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 primaryRune ? createCDragonAssetUrl(primaryRune.icon) : null
}
/**
* Get the url of the of the player secondary rune
* @param {Object} perks : from the API
*/
export function getSecondaryRune(perks) {
const secondaryRune = store.state.cdragon.runes.perkstyles[perks.secondaryStyle]
return secondaryRune ? createCDragonAssetUrl(secondaryRune.icon) : null
}
/**
@ -26,9 +30,8 @@ export function getPrimaryAndSecondaryRune(perks) {
export function createMatchData(matches) {
for (const match of matches) {
// Runes
const runes = getPrimaryAndSecondaryRune(match.perks)
match.primaryRune = runes.primaryRune
match.secondaryRune = runes.secondaryRune
match.primaryRune = getPrimarRune(match.perks)
match.secondaryRune = getSecondaryRune(match.perks)
const date = new Date(match.date)
const dateOptions = { day: '2-digit', month: '2-digit', year: 'numeric' }

View file

@ -59,6 +59,8 @@ export default {
created() {
this.fetchData()
this.getRunes()
},
methods: {
@ -67,6 +69,7 @@ export default {
this.liveMatchRequest()
}
},
...mapActions('cdragon', ['getRunes']),
...mapActions('summoner', ['liveMatchRequest']),
},

View file

@ -3,11 +3,13 @@ import { getCurrentSeason } from 'App/helpers'
import Summoner from 'App/Models/Summoner'
import MatchRepository from 'App/Repositories/MatchRepository'
import BasicMatchSerializer from 'App/Serializers/BasicMatchSerializer'
import LiveMatchSerializer from 'App/Serializers/LiveMatchSerializer'
import Jax from 'App/Services/Jax'
import MatchService from 'App/Services/MatchService'
import StatsService from 'App/Services/StatsService'
import SummonerService from 'App/Services/SummonerService'
import SummonerBasicValidator from 'App/Validators/SummonerBasicValidator'
import SummonerLiveValidator from 'App/Validators/SummonerLiveValidator'
import SummonerOverviewValidator from 'App/Validators/SummonerOverviewValidator'
import SummonerRecordValidator from 'App/Validators/SummonerRecordValidator'
@ -104,4 +106,25 @@ export default class SummonersController {
console.timeEnd('recordsRequest')
return response.json(recordsSerialized)
}
/**
* POST - Return live match detail
* @param ctx
*/
public async liveMatchDetails({ request, response }: HttpContextContract) {
console.time('liveMatchDetails')
const { id, region } = await request.validate(SummonerLiveValidator)
// CURRENT GAME
const currentGame = await Jax.Spectator.summonerID(id, region)
if (!currentGame) {
return response.json(null)
}
const currentGameSerialized = await LiveMatchSerializer.serializeOneMatch(currentGame, region)
console.timeEnd('liveMatchDetails')
return response.json(currentGameSerialized)
}
}

View file

@ -0,0 +1,81 @@
import { PlayerRole, queuesWithRole } from 'App/helpers'
import CDragonService from 'App/Services/CDragonService'
import { CurrentGameInfoDTO } from 'App/Services/Jax/src/Endpoints/SpectatorEndpoint'
import { RoleComposition } from 'App/Services/RoleIdentificationService'
import SummonerService from 'App/Services/SummonerService'
import MatchSerializer from './MatchSerializer'
import { SerializedLiveMatch, SerializedLiveMatchPlayer } from './SerializedTypes'
class LiveMatchSerializer extends MatchSerializer {
public async serializeOneMatch(
liveMatch: CurrentGameInfoDTO,
region: string
): Promise<SerializedLiveMatch> {
// Roles
const blueTeam: PlayerRole[] = [] // 100
const redTeam: PlayerRole[] = [] // 200
let blueRoles: RoleComposition = {}
let redRoles: RoleComposition = {}
const needsRole =
CDragonService.championRoles &&
(queuesWithRole.includes(liveMatch.gameQueueConfigId) ||
(liveMatch.gameType === 'CUSTOM_GAME' && liveMatch.participants.length === 10))
if (needsRole) {
liveMatch.participants.map((p) => {
const playerRole = {
champion: p.championId,
jungle: p.spell1Id === 11 || p.spell2Id === 11,
}
p.teamId === 100 ? blueTeam.push(playerRole) : redTeam.push(playerRole)
})
blueRoles = super.getTeamRoles(blueTeam)
redRoles = super.getTeamRoles(redTeam)
}
// Ranks
const requestsRanks = liveMatch.participants.map((p) =>
SummonerService.getRanked(p.summonerId, region)
)
const ranks = await Promise.all(requestsRanks)
// Players
const players: SerializedLiveMatchPlayer[] = liveMatch.participants.map((player, index) => {
let role: string | undefined
// Roles
if (needsRole) {
const roles = player.teamId === 100 ? blueRoles : redRoles
role = Object.entries(roles).find(([, champion]) => player.championId === champion)![0]
}
return {
...player,
role,
rank: ranks[index],
champion: this.getChampion(player.championId),
perks: {
primaryStyle: player.perks.perkStyle,
secondaryStyle: player.perks.perkSubStyle,
selected: player.perks.perkIds,
},
}
})
return {
gameId: liveMatch.gameId,
gameType: liveMatch.gameType,
gameStartTime: liveMatch.gameStartTime,
mapId: liveMatch.mapId,
gameLength: liveMatch.gameLength,
platformId: liveMatch.platformId,
gameMode: liveMatch.gameMode,
bannedChampions: liveMatch.bannedChampions,
gameQueueConfigId: liveMatch.gameQueueConfigId,
observers: liveMatch.observers,
participants: players,
}
}
}
export default new LiveMatchSerializer()

View file

@ -1,7 +1,9 @@
import { PlayerRole } from 'App/helpers'
import MatchPlayer from 'App/Models/MatchPlayer'
import MatchPlayerRank from 'App/Models/MatchPlayerRank'
import { PlayerRankParsed, TeamPosition } from 'App/Parsers/ParsedType'
import CDragonService from 'App/Services/CDragonService'
import RoleIdentificationService, { RoleComposition } from 'App/Services/RoleIdentificationService'
import SummonerService from 'App/Services/SummonerService'
import {
SerializedBasePlayer,
@ -112,4 +114,22 @@ export default abstract class MatchSerializer {
shortName: SummonerService.getRankedShortName(rank),
}
}
/**
* Return the 5 roles of a team based on champions
* @param team 5 champions + smite from a team
*/
protected getTeamRoles(team: PlayerRole[]): RoleComposition {
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(
CDragonService.championRoles,
team.map((p) => p.champion),
jungle,
support
)
}
}

View file

@ -1,3 +1,9 @@
import {
CurrentGameInfoDTO,
GameCustomizationObjectDTO,
} from 'App/Services/Jax/src/Endpoints/SpectatorEndpoint'
import { LeagueEntriesByQueue } from 'App/Services/SummonerService'
export interface SerializedBasePlayer {
champion: SerializedMatchChampion
items: Array<SerializedMatchItem | null>
@ -178,3 +184,28 @@ export interface SerializedPlayerRank {
losses: number
shortName: number | string
}
/* ============================
Live Match
============================ */
export interface SerializedLiveMatch extends Omit<CurrentGameInfoDTO, 'participants'> {
participants: SerializedLiveMatchPlayer[]
}
export interface SerializedLiveMatchPlayer {
bot: boolean
champion: SerializedMatchChampion
championId: number
gameCustomizationObjects: GameCustomizationObjectDTO[]
perks: SerializedMatchPerks
profileIconId: number
rank: LeagueEntriesByQueue
role?: string
spell1Id: number
spell2Id: number
summonerId: string
summonerName: string
teamId: number
}

View file

@ -1,10 +1,9 @@
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
import RiotRateLimiter from 'riot-ratelimiter'
import { LeagueEntriesByQueue } from 'App/Services/SummonerService'
import { JaxConfig } from '../../JaxConfig'
import JaxRequest from '../JaxRequest'
export interface CurrentGameInfo {
export interface CurrentGameInfoDTO {
gameId: number
gameType: string
gameStartTime: number
@ -12,25 +11,25 @@ export interface CurrentGameInfo {
gameLength: number
platformId: string
gameMode: string
bannedChampions: BannedChampion[]
bannedChampions: BannedChampionDTO[]
gameQueueConfigId: number
observers: Observer
participants: CurrentGameParticipant[]
observers: ObserverDTO
participants: CurrentGameParticipantDTO[]
}
export interface BannedChampion {
export interface BannedChampionDTO {
pickTurn: number
championId: number
teamId: number
}
export interface Observer {
export interface ObserverDTO {
encryptionKey: string
}
export interface CurrentGameParticipant {
export interface CurrentGameParticipantDTO {
championId: number
perks: Perks
perks: PerksDTO
profileIconId: number
bot: boolean
teamId: number
@ -38,21 +37,16 @@ export interface CurrentGameParticipant {
summonerId: string
spell1Id: number
spell2Id: number
gameCustomizationObjects: GameCustomizationObject[]
// Custom types from here
role?: string
runes?: { primaryRune: string; secondaryRune: string } | {}
level?: number
rank?: LeagueEntriesByQueue
gameCustomizationObjects: GameCustomizationObjectDTO[]
}
export interface Perks {
export interface PerksDTO {
perkIds: number[]
perkStyle: number
perkSubStyle: number
}
export interface GameCustomizationObject {
export interface GameCustomizationObjectDTO {
category: string
content: string
}
@ -66,7 +60,7 @@ export default class SpectatorEndpoint {
this.limiter = limiter
}
public summonerID(summonerID: string, region: string): Promise<CurrentGameInfo | undefined> {
public summonerID(summonerID: string, region: string): Promise<CurrentGameInfoDTO | undefined> {
return new JaxRequest(
region,
this.config,

View file

@ -0,0 +1,43 @@
import { schema } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class SummonerLiveValidator {
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({
id: schema.string(),
region: 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

@ -50,6 +50,15 @@ export function getRiotRegion(region: string): RiotRegion {
}
}
/**
* Interface to help define a player's role
*/
export interface PlayerRole {
champion: number
jungle?: boolean
support?: boolean
}
/**
* League of Legends queues with defined role for each summoner
*/

View file

@ -33,7 +33,7 @@ Route.post('/summoner/overview', 'SummonersController.overview')
// Route.post('/summoner/champions', 'SummonersController.champions')
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/details', 'MatchesController.show')