From 057bfa508e5bfa4a8993efdc1ccf812443f82a0d Mon Sep 17 00:00:00 2001 From: spacemeowx2 Date: Thu, 1 Dec 2022 15:52:04 +0800 Subject: [PATCH] feat: add X match types --- src/GameFetcher.ts | 85 +++++++++++++++++++++++++++++---------- src/exporters/stat.ink.ts | 2 + src/splatnet3.ts | 4 ++ src/types.ts | 51 +++++++++++++++++++---- 4 files changed, 113 insertions(+), 29 deletions(-) diff --git a/src/GameFetcher.ts b/src/GameFetcher.ts index 05eaae0..428e541 100644 --- a/src/GameFetcher.ts +++ b/src/GameFetcher.ts @@ -7,8 +7,9 @@ import { CoopHistoryGroups, CoopInfo, Game, - HistoryGroups, + HistoryGroupItem, VsInfo, + VsMode, } from "./types.ts"; import { Cache, MemoryCache } from "./cache.ts"; import { gameId } from "./utils.ts"; @@ -25,9 +26,11 @@ export class GameFetcher { private lock: Record = {}; private bankaraLock = new Mutex(); - private bankaraHistory?: HistoryGroups["nodes"]; + private bankaraHistory?: HistoryGroupItem[]; private coopLock = new Mutex(); private coopHistory?: CoopHistoryGroups["nodes"]; + private xMatchLock = new Mutex(); + private xMatchHistory?: HistoryGroupItem[]; constructor( { cache = new MemoryCache(), splatnet, state }: { @@ -72,7 +75,27 @@ export class GameFetcher { return this.rankTracker.getRankStateById(id); } + getXMatchHistory() { + if (!this._splatnet) { + return []; + } + return this.xMatchLock.use(async () => { + if (this.xMatchHistory) { + return this.xMatchHistory; + } + + const { xBattleHistories: { historyGroups } } = await this.splatnet + .getXBattleHistories(); + + this.xMatchHistory = historyGroups.nodes; + + return this.xMatchHistory; + }); + } getBankaraHistory() { + if (!this._splatnet) { + return []; + } return this.bankaraLock.use(async () => { if (this.bankaraHistory) { return this.bankaraHistory; @@ -87,6 +110,9 @@ export class GameFetcher { }); } getCoopHistory() { + if (!this._splatnet) { + return []; + } return this.coopLock.use(async () => { if (this.coopHistory) { return this.coopHistory; @@ -124,20 +150,33 @@ export class GameFetcher { groupInfo, }; } - async getBattleMetaById(id: string): Promise> { + async getBattleMetaById( + id: string, + vsMode: VsMode, + ): Promise> { const gid = await gameId(id); - const bankaraHistory = this._splatnet ? await this.getBankaraHistory() : []; + const gameIdMap = new Map(); + let group: HistoryGroupItem | null = null; + let listNode: BattleListNode | null = null; - for (const i of bankaraHistory) { - for (const j of i.historyDetails.nodes) { - gameIdMap.set(j, await gameId(j.id)); + if (vsMode === "BANKARA" || vsMode === "X_MATCH") { + const bankaraHistory = vsMode === "BANKARA" + ? await this.getBankaraHistory() + : await this.getXMatchHistory(); + + for (const i of bankaraHistory) { + for (const j of i.historyDetails.nodes) { + gameIdMap.set(j, await gameId(j.id)); + } } - } - const group = bankaraHistory.find((i) => - i.historyDetails.nodes.some((i) => gameIdMap.get(i) === gid) - ); + group = bankaraHistory.find((i) => + i.historyDetails.nodes.some((i) => + gameIdMap.get(i) === gid + ) + ) ?? null; + } if (!group) { return { @@ -147,19 +186,21 @@ export class GameFetcher { listNode: null, rankState: null, rankBeforeState: null, + groupInfo: null, }; } - const { bankaraMatchChallenge } = group; - const listNode = - group.historyDetails.nodes.find((i) => gameIdMap.get(i) === gid) ?? - null; - const index = group.historyDetails.nodes.indexOf(listNode!); + const { bankaraMatchChallenge, xMatchMeasurement } = group; + const { historyDetails, ...groupInfo } = group; + listNode = historyDetails.nodes.find((i) => gameIdMap.get(i) === gid) ?? + null; + const index = historyDetails.nodes.indexOf(listNode!); let challengeProgress: null | ChallengeProgress = null; - if (bankaraMatchChallenge) { - const pastBattles = group.historyDetails.nodes.slice(0, index); - const { winCount, loseCount } = bankaraMatchChallenge; + const challengeOrMeasurement = bankaraMatchChallenge || xMatchMeasurement; + if (challengeOrMeasurement) { + const pastBattles = historyDetails.nodes.slice(0, index); + const { winCount, loseCount } = challengeOrMeasurement; challengeProgress = { index, winCount: winCount - @@ -171,7 +212,8 @@ export class GameFetcher { }; } - const { before, after } = await this.rankTracker.getRankStateById(id) ?? {}; + const { before, after } = await this.rankTracker.getRankStateById(id) ?? + {}; return { type: "VsInfo", @@ -180,6 +222,7 @@ export class GameFetcher { challengeProgress, rankState: after ?? null, rankBeforeState: before ?? null, + groupInfo, }; } private cacheDetail( @@ -216,7 +259,7 @@ export class GameFetcher { id, () => this.splatnet.getBattleDetail(id).then((r) => r.vsHistoryDetail), ); - const metadata = await this.getBattleMetaById(id); + const metadata = await this.getBattleMetaById(id, detail.vsMode.mode); const game: VsInfo = { ...metadata, diff --git a/src/exporters/stat.ink.ts b/src/exporters/stat.ink.ts index 177154d..cad1077 100644 --- a/src/exporters/stat.ink.ts +++ b/src/exporters/stat.ink.ts @@ -348,6 +348,8 @@ export class StatInkExporter implements GameExporter { } else if (modeId === 8) { throw new Error("Tri-color battle is not supported"); } + } else if (vsMode === "X_MATCH") { + return "xmatch"; } throw new TypeError(`Unknown vsMode ${vsMode}`); diff --git a/src/splatnet3.ts b/src/splatnet3.ts index 14be585..2eeaf87 100644 --- a/src/splatnet3.ts +++ b/src/splatnet3.ts @@ -196,6 +196,10 @@ export class Splatnet3 { return resp; } + async getXBattleHistories() { + return await this.request(Queries.XBattleHistoriesQuery); + } + async getCoopHistories() { const resp = await this.request(Queries.CoopHistoryQuery); diff --git a/src/types.ts b/src/types.ts index cfa64e7..e63b161 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,6 +5,7 @@ export enum Queries { LatestBattleHistoriesQuery = "4f5f26e64bca394b45345a65a2f383bd", RegularBattleHistoriesQuery = "d5b795d09e67ce153e622a184b7e7dfa", BankaraBattleHistoriesQuery = "de4754588109b77dbcb90fbe44b612ee", + XBattleHistoriesQuery = "45c74fefb45a49073207229ca65f0a62", PrivateBattleHistoriesQuery = "1d6ed57dc8b801863126ad4f351dfb9a", VsHistoryDetailQuery = "291295ad311b99a6288fc95a5c4cb2d2", CoopHistoryQuery = "6ed02537e4a65bbb5e7f4f23092f6154", @@ -20,6 +21,7 @@ export type VarsMap = { [Queries.LatestBattleHistoriesQuery]: []; [Queries.RegularBattleHistoriesQuery]: []; [Queries.BankaraBattleHistoriesQuery]: []; + [Queries.XBattleHistoriesQuery]: []; [Queries.PrivateBattleHistoriesQuery]: []; [Queries.VsHistoryDetailQuery]: [{ vsResultId: string; @@ -50,6 +52,21 @@ export type BankaraMatchChallenge = { udemaeAfter: string | null; earnedUdemaePoint: number | null; }; +export type XMatchMeasurement = { + state: "COMPLETED" | "INPROGRESS"; + xPowerAfter: null | number; + isInitial: boolean; + winCount: number; + loseCount: number; + maxInitialBattleCount: number; + maxWinCount: number; + maxLoseCount: number; + vsRule: { + name: string; + rule: string; + id: string; + }; +}; export type BattleListNode = { id: string; udemae: string; @@ -61,15 +78,18 @@ export type BattleListNode = { export type CoopListNode = { id: string; }; -export type HistoryGroups = { - nodes: { - bankaraMatchChallenge: null | BankaraMatchChallenge; +export type HistoryGroupItem = { + bankaraMatchChallenge: null | BankaraMatchChallenge; + xMatchMeasurement: null | XMatchMeasurement; - historyDetails: { - nodes: T[]; - }; - }[]; + historyDetails: { + nodes: T[]; + }; }; +export type Nodes = { + nodes: T[]; +}; +export type HistoryGroups = Nodes>; export type CoopHistoryGroup = { startTime: null | string; endTime: null | string; @@ -159,6 +179,7 @@ export type ChallengeProgress = { // With challenge info export type VsInfo = { type: "VsInfo"; + groupInfo: null | Omit, "historyDetails">; listNode: null | BattleListNode; bankaraMatchChallenge: null | BankaraMatchChallenge; challengeProgress: null | ChallengeProgress; @@ -174,6 +195,7 @@ export type CoopInfo = { detail: CoopHistoryDetail; }; export type Game = VsInfo | CoopInfo; +export type VsMode = "REGULAR" | "BANKARA" | "PRIVATE" | "FEST" | "X_MATCH"; export type VsHistoryDetail = { id: string; vsRule: { @@ -183,13 +205,16 @@ export type VsHistoryDetail = { }; vsMode: { id: string; - mode: "REGULAR" | "BANKARA" | "PRIVATE" | "FEST"; + mode: VsMode; }; vsStage: { id: string; name: string; image: Image; }; + xMatch: null | { + lastXPower: null | number; + }; playedTime: string; // 2021-01-01T00:00:00Z bankaraMatch: { @@ -331,6 +356,12 @@ export type BankaraBattleHistories = { }; }; +export type XBattleHistories = { + xBattleHistories: { + historyGroups: HistoryGroups; + }; +}; + export type RespMap = { [Queries.HomeQuery]: { currentPlayer: { @@ -361,6 +392,7 @@ export type RespMap = { }; }; [Queries.BankaraBattleHistoriesQuery]: BankaraBattleHistories; + [Queries.XBattleHistoriesQuery]: XBattleHistories; [Queries.PrivateBattleHistoriesQuery]: { privateBattleHistories: { historyGroups: HistoryGroups; @@ -698,6 +730,7 @@ export type StatInkPostBody = { | "regular" | "bankara_challenge" | "bankara_open" + | "xmatch" | "splatfest_challenge" | "splatfest_open" | "private"; @@ -732,6 +765,8 @@ export type StatInkPostBody = { rank_up_battle?: "yes" | "no"; // Set "yes" if now "Rank-up Battle" mode. challenge_win?: number; // Win count for Anarchy (Series) If rank_up_battle is truthy("yes"), the value range is limited to [0, 3]. challenge_lose?: number; + x_power_before?: number; + x_power_after?: number; fest_power?: number; // Splatfest Power (Pro) fest_dragon?: | "10x"