Skip to content
This repository has been archived by the owner on Jun 13, 2024. It is now read-only.

Commit

Permalink
✨ Pre-/In-Game Competitive History Tab-implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
RXJpaw committed Apr 4, 2023
1 parent 6130d62 commit f8d7452
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 34 deletions.
4 changes: 4 additions & 0 deletions interfaces/custom/current_match_subject.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface LoadedCurrentMatchSubject {
LevelBorderURL: string
HasFistBumpBuddy: boolean
HasPresence: boolean
Incognito: boolean

EncounterAmount: number
LastEncounter: number
Expand All @@ -28,6 +29,9 @@ interface LoadedCurrentMatchSubject {
HighestRankName: string
HighestRank: ValorantAPICompetitiveTiers.Tier

CompetitiveUpdates: ValorantCompetitiveUpdates.Match[]
Triangles: { [actUuid: string]: { NameShort: string; BestRank: ValorantAPICompetitiveTiers.Tier } }

Level: number
TagLine: string
GameName: string
Expand Down
106 changes: 78 additions & 28 deletions src/components/Browser/CurrentMatchPlayer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,33 +74,52 @@
<div v-if="isOverCard && subject.EncounterAmount > 0" class="encounters">
{{ subject.EncounterAmount }} {{ subject.EncounterAmount === 1 ? 'Meetup' : 'Meetups' }}
</div>
<div
v-if="(isOverCard && game_state === 'INGAME') || inventory_subject?.Subject === subject?.Subject"
class="inventory"
@click="clickInventoryIcon()"
>
<svg
v-if="inventory_subject?.Subject !== subject?.Subject"
fill="currentColor"
style="width: 15px; height: 15px; margin-top: 1px"
viewBox="0 0 24.95 22.57"
>
<path d="M281.3,418.52v8.14s-2.94-1.4-2.94-4.34A4.14,4.14,0,0,1,281.3,418.52Z" transform="translate(-278.36 -405.8)" />
<polygon points="14.83 0 12.48 0 10.12 0 7.99 2.13 9.74 2.13 10.23 1.63 12.48 1.63 14.72 1.63 15.22 2.13 16.96 2.13 14.83 0" />
<path d="M290.83,409.56h-9.37v4s7.13,1.49,7.13,4.92h4.49c0-3.43,7.13-4.92,7.13-4.92v-4Z" transform="translate(-278.36 -405.8)" />
<path
d="M294.31,420.55h-6.95s-.21-4-4-4v11.86h15V416.51C294.52,416.51,294.31,420.55,294.31,420.55Z"
transform="translate(-278.36 -405.8)"
/>
<path d="M300.37,418.52v8.14s2.94-1.4,2.94-4.34A4.14,4.14,0,0,0,300.37,418.52Z" transform="translate(-278.36 -405.8)" />
</svg>
<svg v-else aria-hidden="false" width="12" height="12" style="width: 13px; height: 13px; margin: 0 1px" viewBox="0 0 12 12">
<polygon
<div v-if="isOverCard || anySideMenuOpen()" class="side-menu">
<div v-if="game_state === 'INGAME'" class="side-button inventory" @click="clickInventoryIcon()">
<svg
v-if="inventory_subject?.Subject !== subject?.Subject"
fill="currentColor"
fill-rule="evenodd"
points="11 1.576 6.583 6 11 10.424 10.424 11 6 6.583 1.576 11 1 10.424 5.417 6 1 1.576 1.576 1 6 5.417 10.424 1"
></polygon>
</svg>
style="width: 15px; height: 15px; margin-top: 1px"
viewBox="0 0 24.95 22.57"
>
<path d="M281.3,418.52v8.14s-2.94-1.4-2.94-4.34A4.14,4.14,0,0,1,281.3,418.52Z" transform="translate(-278.36 -405.8)" />
<polygon points="14.83 0 12.48 0 10.12 0 7.99 2.13 9.74 2.13 10.23 1.63 12.48 1.63 14.72 1.63 15.22 2.13 16.96 2.13 14.83 0" />
<path d="M290.83,409.56h-9.37v4s7.13,1.49,7.13,4.92h4.49c0-3.43,7.13-4.92,7.13-4.92v-4Z" transform="translate(-278.36 -405.8)" />
<path
d="M294.31,420.55h-6.95s-.21-4-4-4v11.86h15V416.51C294.52,416.51,294.31,420.55,294.31,420.55Z"
transform="translate(-278.36 -405.8)"
/>
<path d="M300.37,418.52v8.14s2.94-1.4,2.94-4.34A4.14,4.14,0,0,0,300.37,418.52Z" transform="translate(-278.36 -405.8)" />
</svg>
<svg v-else aria-hidden="false" width="12" height="12" style="width: 13px; height: 13px; margin: 0 1px" viewBox="0 0 12 12">
<polygon
fill="currentColor"
fill-rule="evenodd"
points="11 1.576 6.583 6 11 10.424 10.424 11 6 6.583 1.576 11 1 10.424 5.417 6 1 1.576 1.576 1 6 5.417 10.424 1"
></polygon>
</svg>
</div>
<div class="side-button history" @click="clickHistoryIcon()">
<svg
v-if="history_subject?.Subject !== subject?.Subject"
fill="currentColor"
style="width: 15px; height: 15px; margin-top: 1px"
viewBox="0 -1.5 22.55 32.17"
>
<polygon points="0 0 10.08 0 10.08 12.94 7.33 13.26 0 7.46 0 0" />
<polygon points="22.55 0 12.47 0 12.47 12.94 15.22 13.26 22.55 7.46 22.55 0" />
<path
d="m11.34,15.08c-4.17,0-7.54,3.38-7.54,7.54s3.38,7.54,7.54,7.54,7.54-3.38,7.54-7.54-3.38-7.54-7.54-7.54Zm0,11.18l-3.72-3.72,3.72-3.72,3.72,3.72-3.72,3.72Z"
/>
</svg>
<svg v-else aria-hidden="false" width="12" height="12" style="width: 13px; height: 13px; margin: 0 1px" viewBox="0 0 12 12">
<polygon
fill="currentColor"
fill-rule="evenodd"
points="11 1.576 6.583 6 11 10.424 10.424 11 6 6.583 1.576 11 1 10.424 5.417 6 1 1.576 1.576 1 6 5.417 10.424 1"
></polygon>
</svg>
</div>
</div>
</div>
</div>
Expand All @@ -112,6 +131,9 @@ export default {
name: 'CurrentMatchPlayer',
components: { Icon },
props: {
history_subject: Object as () => LoadedCurrentMatchSubject | null,
history_left: Number as () => number,
history_top: Number as () => number,
inventory_subject: Object as () => LoadedCurrentMatchSubject | null,
inventory_left: Number as () => number,
inventory_top: Number as () => number,
Expand All @@ -126,15 +148,38 @@ export default {
}
},
methods: {
anySideMenuOpen() {
return this.inventory_subject?.Subject === this.subject?.Subject || this.history_subject?.Subject === this.subject?.Subject
},
hoverOverCard(isOver: boolean) {
this.isOverCard = isOver
},
clickHistoryIcon() {
if (this.history_subject?.Subject === this.subject?.Subject) {
this.$emit('update:history_subject', null)
return
}
this.$emit('update:inventory_subject', null)
const Div = this.$refs['this'] as HTMLDivElement
const Rect = Div.getBoundingClientRect()
let left = this.enemy ? Rect.left - 256 + 69 - 35 : Rect.left + 256 - 55
let top = Math.min(490, Rect.top - 22)
this.$emit('update:history_subject', this.subject)
this.$emit('update:history_left', left)
this.$emit('update:history_top', top)
},
clickInventoryIcon() {
if (this.inventory_subject?.Subject === this.subject?.Subject) {
this.$emit('update:inventory_subject', null)
return
}
this.$emit('update:history_subject', null)
const Div = this.$refs['this'] as HTMLDivElement
const Rect = Div.getBoundingClientRect()
Expand Down Expand Up @@ -256,11 +301,16 @@ export default {
border: 0 solid;
border-radius: 6px;
}
.player > .banner-wrapper > .inventory {
.player > .banner-wrapper > .side-menu {
position: absolute;
top: 8px;
right: 8px;
top: 8px;
display: flex;
flex-direction: column;
gap: 8px;
}
.player > .banner-wrapper > .side-menu > .side-button {
height: 17px;
padding: 0 6px;
Expand Down
36 changes: 31 additions & 5 deletions src/components/Content/CurrentMatch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
v-model:inventory_subject="inventory_subject"
v-model:inventory_left="inventory_left"
v-model:inventory_top="inventory_top"
v-model:history_subject="history_subject"
v-model:history_left="history_left"
v-model:history_top="history_top"
:game_state="game_state"
:subject="subject"
/>
Expand All @@ -27,14 +30,18 @@
v-model:inventory_subject="inventory_subject"
v-model:inventory_left="inventory_left"
v-model:inventory_top="inventory_top"
v-model:history_subject="history_subject"
v-model:history_left="history_left"
v-model:history_top="history_top"
:game_state="game_state"
:subject="subject"
:enemy="true"
/>
</div>
<transition>
<transition-group>
<CurrentMatchInventory v-if="inventory_subject" :subject="inventory_subject" :left="inventory_left" :top="inventory_top" />
</transition>
<CurrentMatchHistory v-if="history_subject" :subject="history_subject" :left="history_left" :top="history_top" />
</transition-group>
</div>
<div v-else class="current-match">
<NotReady text="Waiting for VALORANT match data..." />
Expand All @@ -44,6 +51,7 @@
<script lang="ts">
import CurrentMatchInventory from '@/components/Browser/CurrentMatchInventory.vue'
import { capitalizeFirstLetter, EncounterHistory, sleep } from '@/scripts/methods'
import CurrentMatchHistory from '@/components/Browser/CurrentMatchHistory.vue'
import CurrentMatchPlayer from '@/components/Browser/CurrentMatchPlayer.vue'
import { ValorantInstance } from '@/scripts/valorant_instance'
import WEAPONS from '@/assets/valorant_api/weapons.json'
Expand All @@ -58,7 +66,7 @@ const GameStateChangeChannel = new BroadcastChannel('game-state-change')
export default {
name: 'CurrentMatch',
components: { CurrentMatchInventory, CurrentMatchPlayer, Icon, NotReady },
components: { CurrentMatchHistory, CurrentMatchInventory, CurrentMatchPlayer, Icon, NotReady },
props: {
isVisible: Boolean as () => boolean
},
Expand All @@ -73,7 +81,10 @@ export default {
mock_state: null as null | 'INGAME' | 'PREGAME',
inventory_subject: null as LoadedCurrentMatchSubject | null,
inventory_left: 0,
inventory_top: 0
inventory_top: 0,
history_subject: null as LoadedCurrentMatchSubject | null,
history_left: 0,
history_top: 0
}
},
async created() {
Expand Down Expand Up @@ -119,6 +130,7 @@ export default {
if (event.key === 'Escape') {
this.inventory_subject = null
this.history_subject = null
}
},
getSides() {
Expand Down Expand Up @@ -186,10 +198,13 @@ export default {
const index = (EnemyTeamSize ? (AllyTeamID === 'Blue' ? 0 : EnemyTeamSize) : 0) + i
const MMR = await Valorant.parseMMR(player.Subject)
const Triangles = await Valorant.parseTriangles(player.Subject)
const Presence = Presences.find((presence) => presence.Subject === player.Subject)
const Encounters = await EncounterHistory.get(player.Subject)
const LevelBorder = await Valorant.getLevelBorder(player.PlayerIdentity.AccountLevel, player.PlayerIdentity.PreferredLevelBorderID)
const NameService = NameServices[player.Subject]
const CompetitiveUpdates = await Valorant.getCachedCompetitiveUpdates(player.Subject)
const SkinChromas: (string | null)[] = []
const Buddies: (string | null)[] = []
Expand All @@ -202,7 +217,7 @@ export default {
SkinChromas.push(Item?.Sockets[SOCKETS.skin_chroma]?.Item.ID || null)
}
this.subjects[index] = {
this.subjects[index] = <LoadedCurrentMatchSubject>{
AgentIconURL: `https://media.valorant-api.com/agents/${player.CharacterID}/displayicon.png`,
PlayerCardURL: `https://media.valorant-api.com/playercards/${player.PlayerIdentity.PlayerCardID}/wideart.png`,
LevelBorderURL: LevelBorder.levelNumberAppearance,
Expand All @@ -227,6 +242,9 @@ export default {
HighestRankName: capitalizeFirstLetter(MMR.BestRank.tierName),
HighestRank: MMR.BestRank,
CompetitiveUpdates: CompetitiveUpdates.Matches.filter((m) => m.SeasonID),
Triangles: Triangles,
Level: player.PlayerIdentity.AccountLevel,
TagLine: NameService.TagLine,
GameName: NameService.GameName,
Expand All @@ -253,6 +271,7 @@ export default {
if (GameStateChangeObject.from === 'INGAME' && GameStateChangeObject.to === 'MENUS') {
this.inventory_subject = null
this.history_subject = null
}
if (GameState === 'MENUS') {
Expand Down Expand Up @@ -330,6 +349,13 @@ export default {
opacity: 0;
}
.history:is(.v-enter-active, .v-leave-active) {
transition: opacity ease-in-out 0.15s;
}
.history:is(.v-enter-from, .v-leave-to) {
opacity: 0;
}
:root {
--boi: UwU;
}
Expand Down
47 changes: 46 additions & 1 deletion src/scripts/valorant_instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,26 @@ const connection = {
Cache.SelfLoadout = undefined
Cache.AccountXP = undefined

Cache.Seasons = ValorantAPI.getSeasons()
Cache.LevelBorders = ValorantAPI.getLevelBorders()
Cache.CompetitiveTiers = ValorantAPI.getCompetitiveTiers()
Cache.CompetitiveSeasons = ValorantAPI.getCompetitiveSeasons()
}
}

const Cache = {
ContentServiceContent: undefined as Promise<ValorantContentServiceContent> | undefined,
CoreGameMatch: {} as { [id: string]: Promise<ValorantCoreGameMatch> | undefined },
CoreGameLoadouts: {} as { [id: string]: Promise<ValorantCoreGameLoadouts> | undefined },
PreGameMatch: {} as { [id: string]: Promise<ValorantPreGameMatch> | undefined },
PreGameLoadouts: {} as { [id: string]: Promise<ValorantPreGameLoadouts> | undefined },
// NameService: {} as { [id: string]: ValorantNameService },
// MMR: {} as { [id: string]: Promise<ValorantMMR> | undefined },

ContentServiceContent: undefined as Promise<ValorantContentServiceContent> | undefined,
SelfLoadout: undefined as Promise<ValorantPersonalizationPlayerLoadout> | undefined,
AccountXP: undefined as Promise<ValorantAccountXp> | undefined,

Seasons: ValorantAPI.getSeasons(),
LevelBorders: ValorantAPI.getLevelBorders(),
CompetitiveTiers: ValorantAPI.getCompetitiveTiers(),
CompetitiveSeasons: ValorantAPI.getCompetitiveSeasons()
Expand Down Expand Up @@ -372,6 +374,15 @@ export const ValorantInstance = () => {

return await request('get', 'pd', `/mmr/v1/players/${player_uuid}/competitiveupdates?${query}`)
}
const getCachedCompetitiveUpdates = async (player_uuid, force?: boolean): Promise<ValorantCompetitiveUpdates> => {
const query = new URLSearchParams(<never>{ endIndex: 10, queue: 'competitive' })

return await PersistentCache.get(
`competitive-updates_${player_uuid}`,
() => request('get', 'pd', `/mmr/v1/players/${player_uuid}/competitiveupdates?${query}`),
{ delay: 250, force }
)
}

const getContentServiceContent = async (force?: boolean): Promise<ValorantContentServiceContent> => {
if (!force && Cache.ContentServiceContent) return await Cache.ContentServiceContent!
Expand Down Expand Up @@ -526,6 +537,38 @@ export const ValorantInstance = () => {
return { BestRank, WorstRank, CurrentRank, CurrentRR }
}

const parseTriangles = async (subject, force?: boolean) => {
const MMR = await getMMR(subject, force)

const Seasons = await Cache.Seasons
const CompetitiveTiers = await Cache.CompetitiveTiers
const CompetitiveSeasons = await Cache.CompetitiveSeasons

const Acts = Seasons
//Seasons include future Seasons and therefore needs to be filtered first.
.filter((s) => s.type === 'EAresSeasonType::Act' && new Date(s.startTime).getTime() <= Date.now())
.sort((a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime())
const Triangles = {}

for (const Act of Acts) {
const CompetitiveSeason = CompetitiveSeasons.find((season) => season.seasonUuid === Act.uuid)!
const CompetitiveTier = CompetitiveTiers.find((tier) => tier.uuid === CompetitiveSeason.competitiveTiersUuid)!

const SubjectSeason = MMR.QueueSkills?.competitive.SeasonalInfoBySeasonID?.[Act.uuid]
const EpisodeAct = Act.assetPath.match(/ShooterGame\/Content\/Seasons\/Season_Episode(\d+)_Act(\d+)_DataAsset/)
const WinsByTier = SubjectSeason?.WinsByTier ?? { 0: 0 }

const BestRankTier = Number(Object.keys(WinsByTier).sort((a, b) => Number(b) - Number(a))[0])

Triangles[Act.uuid] = {
NameShort: EpisodeAct ? `Ep. ${EpisodeAct[1]}, Act ${EpisodeAct[2]}` : Act.displayName,
BestRank: CompetitiveTier.tiers.find((t) => t.tier === BestRankTier)!
}
}

return Triangles
}

const getCurrentSeason = async () => {
const Content = await getContentServiceContent()

Expand Down Expand Up @@ -611,6 +654,7 @@ export const ValorantInstance = () => {
getMatchHistory,
getMatchDetails,
getCompetitiveUpdates,
getCachedCompetitiveUpdates,
getContentServiceContent,
getAccountXP,
getContracts,
Expand All @@ -624,6 +668,7 @@ export const ValorantInstance = () => {

getMMR,
parseMMR,
parseTriangles,
getLevelBorder,
getNameService,

Expand Down

0 comments on commit f8d7452

Please sign in to comment.