feat: add X match types
parent
8f1d1429bd
commit
057bfa508e
|
|
@ -7,8 +7,9 @@ import {
|
||||||
CoopHistoryGroups,
|
CoopHistoryGroups,
|
||||||
CoopInfo,
|
CoopInfo,
|
||||||
Game,
|
Game,
|
||||||
HistoryGroups,
|
HistoryGroupItem,
|
||||||
VsInfo,
|
VsInfo,
|
||||||
|
VsMode,
|
||||||
} from "./types.ts";
|
} from "./types.ts";
|
||||||
import { Cache, MemoryCache } from "./cache.ts";
|
import { Cache, MemoryCache } from "./cache.ts";
|
||||||
import { gameId } from "./utils.ts";
|
import { gameId } from "./utils.ts";
|
||||||
|
|
@ -25,9 +26,11 @@ export class GameFetcher {
|
||||||
|
|
||||||
private lock: Record<string, Mutex | undefined> = {};
|
private lock: Record<string, Mutex | undefined> = {};
|
||||||
private bankaraLock = new Mutex();
|
private bankaraLock = new Mutex();
|
||||||
private bankaraHistory?: HistoryGroups<BattleListNode>["nodes"];
|
private bankaraHistory?: HistoryGroupItem<BattleListNode>[];
|
||||||
private coopLock = new Mutex();
|
private coopLock = new Mutex();
|
||||||
private coopHistory?: CoopHistoryGroups["nodes"];
|
private coopHistory?: CoopHistoryGroups["nodes"];
|
||||||
|
private xMatchLock = new Mutex();
|
||||||
|
private xMatchHistory?: HistoryGroupItem<BattleListNode>[];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{ cache = new MemoryCache(), splatnet, state }: {
|
{ cache = new MemoryCache(), splatnet, state }: {
|
||||||
|
|
@ -72,7 +75,27 @@ export class GameFetcher {
|
||||||
return this.rankTracker.getRankStateById(id);
|
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() {
|
getBankaraHistory() {
|
||||||
|
if (!this._splatnet) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
return this.bankaraLock.use(async () => {
|
return this.bankaraLock.use(async () => {
|
||||||
if (this.bankaraHistory) {
|
if (this.bankaraHistory) {
|
||||||
return this.bankaraHistory;
|
return this.bankaraHistory;
|
||||||
|
|
@ -87,6 +110,9 @@ export class GameFetcher {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
getCoopHistory() {
|
getCoopHistory() {
|
||||||
|
if (!this._splatnet) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
return this.coopLock.use(async () => {
|
return this.coopLock.use(async () => {
|
||||||
if (this.coopHistory) {
|
if (this.coopHistory) {
|
||||||
return this.coopHistory;
|
return this.coopHistory;
|
||||||
|
|
@ -124,20 +150,33 @@ export class GameFetcher {
|
||||||
groupInfo,
|
groupInfo,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
async getBattleMetaById(id: string): Promise<Omit<VsInfo, "detail">> {
|
async getBattleMetaById(
|
||||||
|
id: string,
|
||||||
|
vsMode: VsMode,
|
||||||
|
): Promise<Omit<VsInfo, "detail">> {
|
||||||
const gid = await gameId(id);
|
const gid = await gameId(id);
|
||||||
const bankaraHistory = this._splatnet ? await this.getBankaraHistory() : [];
|
|
||||||
const gameIdMap = new Map<BattleListNode, string>();
|
const gameIdMap = new Map<BattleListNode, string>();
|
||||||
|
let group: HistoryGroupItem<BattleListNode> | null = null;
|
||||||
|
let listNode: BattleListNode | null = null;
|
||||||
|
|
||||||
for (const i of bankaraHistory) {
|
if (vsMode === "BANKARA" || vsMode === "X_MATCH") {
|
||||||
for (const j of i.historyDetails.nodes) {
|
const bankaraHistory = vsMode === "BANKARA"
|
||||||
gameIdMap.set(j, await gameId(j.id));
|
? 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) =>
|
group = bankaraHistory.find((i) =>
|
||||||
i.historyDetails.nodes.some((i) => gameIdMap.get(i) === gid)
|
i.historyDetails.nodes.some((i) =>
|
||||||
);
|
gameIdMap.get(i) === gid
|
||||||
|
)
|
||||||
|
) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!group) {
|
if (!group) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -147,19 +186,21 @@ export class GameFetcher {
|
||||||
listNode: null,
|
listNode: null,
|
||||||
rankState: null,
|
rankState: null,
|
||||||
rankBeforeState: null,
|
rankBeforeState: null,
|
||||||
|
groupInfo: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { bankaraMatchChallenge } = group;
|
const { bankaraMatchChallenge, xMatchMeasurement } = group;
|
||||||
const listNode =
|
const { historyDetails, ...groupInfo } = group;
|
||||||
group.historyDetails.nodes.find((i) => gameIdMap.get(i) === gid) ??
|
listNode = historyDetails.nodes.find((i) => gameIdMap.get(i) === gid) ??
|
||||||
null;
|
null;
|
||||||
const index = group.historyDetails.nodes.indexOf(listNode!);
|
const index = historyDetails.nodes.indexOf(listNode!);
|
||||||
|
|
||||||
let challengeProgress: null | ChallengeProgress = null;
|
let challengeProgress: null | ChallengeProgress = null;
|
||||||
if (bankaraMatchChallenge) {
|
const challengeOrMeasurement = bankaraMatchChallenge || xMatchMeasurement;
|
||||||
const pastBattles = group.historyDetails.nodes.slice(0, index);
|
if (challengeOrMeasurement) {
|
||||||
const { winCount, loseCount } = bankaraMatchChallenge;
|
const pastBattles = historyDetails.nodes.slice(0, index);
|
||||||
|
const { winCount, loseCount } = challengeOrMeasurement;
|
||||||
challengeProgress = {
|
challengeProgress = {
|
||||||
index,
|
index,
|
||||||
winCount: winCount -
|
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 {
|
return {
|
||||||
type: "VsInfo",
|
type: "VsInfo",
|
||||||
|
|
@ -180,6 +222,7 @@ export class GameFetcher {
|
||||||
challengeProgress,
|
challengeProgress,
|
||||||
rankState: after ?? null,
|
rankState: after ?? null,
|
||||||
rankBeforeState: before ?? null,
|
rankBeforeState: before ?? null,
|
||||||
|
groupInfo,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
private cacheDetail<T>(
|
private cacheDetail<T>(
|
||||||
|
|
@ -216,7 +259,7 @@ export class GameFetcher {
|
||||||
id,
|
id,
|
||||||
() => this.splatnet.getBattleDetail(id).then((r) => r.vsHistoryDetail),
|
() => 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 = {
|
const game: VsInfo = {
|
||||||
...metadata,
|
...metadata,
|
||||||
|
|
|
||||||
|
|
@ -348,6 +348,8 @@ export class StatInkExporter implements GameExporter {
|
||||||
} else if (modeId === 8) {
|
} else if (modeId === 8) {
|
||||||
throw new Error("Tri-color battle is not supported");
|
throw new Error("Tri-color battle is not supported");
|
||||||
}
|
}
|
||||||
|
} else if (vsMode === "X_MATCH") {
|
||||||
|
return "xmatch";
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new TypeError(`Unknown vsMode ${vsMode}`);
|
throw new TypeError(`Unknown vsMode ${vsMode}`);
|
||||||
|
|
|
||||||
|
|
@ -196,6 +196,10 @@ export class Splatnet3 {
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getXBattleHistories() {
|
||||||
|
return await this.request(Queries.XBattleHistoriesQuery);
|
||||||
|
}
|
||||||
|
|
||||||
async getCoopHistories() {
|
async getCoopHistories() {
|
||||||
const resp = await this.request(Queries.CoopHistoryQuery);
|
const resp = await this.request(Queries.CoopHistoryQuery);
|
||||||
|
|
||||||
|
|
|
||||||
51
src/types.ts
51
src/types.ts
|
|
@ -5,6 +5,7 @@ export enum Queries {
|
||||||
LatestBattleHistoriesQuery = "4f5f26e64bca394b45345a65a2f383bd",
|
LatestBattleHistoriesQuery = "4f5f26e64bca394b45345a65a2f383bd",
|
||||||
RegularBattleHistoriesQuery = "d5b795d09e67ce153e622a184b7e7dfa",
|
RegularBattleHistoriesQuery = "d5b795d09e67ce153e622a184b7e7dfa",
|
||||||
BankaraBattleHistoriesQuery = "de4754588109b77dbcb90fbe44b612ee",
|
BankaraBattleHistoriesQuery = "de4754588109b77dbcb90fbe44b612ee",
|
||||||
|
XBattleHistoriesQuery = "45c74fefb45a49073207229ca65f0a62",
|
||||||
PrivateBattleHistoriesQuery = "1d6ed57dc8b801863126ad4f351dfb9a",
|
PrivateBattleHistoriesQuery = "1d6ed57dc8b801863126ad4f351dfb9a",
|
||||||
VsHistoryDetailQuery = "291295ad311b99a6288fc95a5c4cb2d2",
|
VsHistoryDetailQuery = "291295ad311b99a6288fc95a5c4cb2d2",
|
||||||
CoopHistoryQuery = "6ed02537e4a65bbb5e7f4f23092f6154",
|
CoopHistoryQuery = "6ed02537e4a65bbb5e7f4f23092f6154",
|
||||||
|
|
@ -20,6 +21,7 @@ export type VarsMap = {
|
||||||
[Queries.LatestBattleHistoriesQuery]: [];
|
[Queries.LatestBattleHistoriesQuery]: [];
|
||||||
[Queries.RegularBattleHistoriesQuery]: [];
|
[Queries.RegularBattleHistoriesQuery]: [];
|
||||||
[Queries.BankaraBattleHistoriesQuery]: [];
|
[Queries.BankaraBattleHistoriesQuery]: [];
|
||||||
|
[Queries.XBattleHistoriesQuery]: [];
|
||||||
[Queries.PrivateBattleHistoriesQuery]: [];
|
[Queries.PrivateBattleHistoriesQuery]: [];
|
||||||
[Queries.VsHistoryDetailQuery]: [{
|
[Queries.VsHistoryDetailQuery]: [{
|
||||||
vsResultId: string;
|
vsResultId: string;
|
||||||
|
|
@ -50,6 +52,21 @@ export type BankaraMatchChallenge = {
|
||||||
udemaeAfter: string | null;
|
udemaeAfter: string | null;
|
||||||
earnedUdemaePoint: number | 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 = {
|
export type BattleListNode = {
|
||||||
id: string;
|
id: string;
|
||||||
udemae: string;
|
udemae: string;
|
||||||
|
|
@ -61,15 +78,18 @@ export type BattleListNode = {
|
||||||
export type CoopListNode = {
|
export type CoopListNode = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
export type HistoryGroups<T> = {
|
export type HistoryGroupItem<T> = {
|
||||||
nodes: {
|
bankaraMatchChallenge: null | BankaraMatchChallenge;
|
||||||
bankaraMatchChallenge: null | BankaraMatchChallenge;
|
xMatchMeasurement: null | XMatchMeasurement;
|
||||||
|
|
||||||
historyDetails: {
|
historyDetails: {
|
||||||
nodes: T[];
|
nodes: T[];
|
||||||
};
|
};
|
||||||
}[];
|
|
||||||
};
|
};
|
||||||
|
export type Nodes<T> = {
|
||||||
|
nodes: T[];
|
||||||
|
};
|
||||||
|
export type HistoryGroups<T> = Nodes<HistoryGroupItem<T>>;
|
||||||
export type CoopHistoryGroup = {
|
export type CoopHistoryGroup = {
|
||||||
startTime: null | string;
|
startTime: null | string;
|
||||||
endTime: null | string;
|
endTime: null | string;
|
||||||
|
|
@ -159,6 +179,7 @@ export type ChallengeProgress = {
|
||||||
// With challenge info
|
// With challenge info
|
||||||
export type VsInfo = {
|
export type VsInfo = {
|
||||||
type: "VsInfo";
|
type: "VsInfo";
|
||||||
|
groupInfo: null | Omit<HistoryGroupItem<BattleListNode>, "historyDetails">;
|
||||||
listNode: null | BattleListNode;
|
listNode: null | BattleListNode;
|
||||||
bankaraMatchChallenge: null | BankaraMatchChallenge;
|
bankaraMatchChallenge: null | BankaraMatchChallenge;
|
||||||
challengeProgress: null | ChallengeProgress;
|
challengeProgress: null | ChallengeProgress;
|
||||||
|
|
@ -174,6 +195,7 @@ export type CoopInfo = {
|
||||||
detail: CoopHistoryDetail;
|
detail: CoopHistoryDetail;
|
||||||
};
|
};
|
||||||
export type Game = VsInfo | CoopInfo;
|
export type Game = VsInfo | CoopInfo;
|
||||||
|
export type VsMode = "REGULAR" | "BANKARA" | "PRIVATE" | "FEST" | "X_MATCH";
|
||||||
export type VsHistoryDetail = {
|
export type VsHistoryDetail = {
|
||||||
id: string;
|
id: string;
|
||||||
vsRule: {
|
vsRule: {
|
||||||
|
|
@ -183,13 +205,16 @@ export type VsHistoryDetail = {
|
||||||
};
|
};
|
||||||
vsMode: {
|
vsMode: {
|
||||||
id: string;
|
id: string;
|
||||||
mode: "REGULAR" | "BANKARA" | "PRIVATE" | "FEST";
|
mode: VsMode;
|
||||||
};
|
};
|
||||||
vsStage: {
|
vsStage: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
image: Image;
|
image: Image;
|
||||||
};
|
};
|
||||||
|
xMatch: null | {
|
||||||
|
lastXPower: null | number;
|
||||||
|
};
|
||||||
playedTime: string; // 2021-01-01T00:00:00Z
|
playedTime: string; // 2021-01-01T00:00:00Z
|
||||||
|
|
||||||
bankaraMatch: {
|
bankaraMatch: {
|
||||||
|
|
@ -331,6 +356,12 @@ export type BankaraBattleHistories = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type XBattleHistories = {
|
||||||
|
xBattleHistories: {
|
||||||
|
historyGroups: HistoryGroups<BattleListNode>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type RespMap = {
|
export type RespMap = {
|
||||||
[Queries.HomeQuery]: {
|
[Queries.HomeQuery]: {
|
||||||
currentPlayer: {
|
currentPlayer: {
|
||||||
|
|
@ -361,6 +392,7 @@ export type RespMap = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
[Queries.BankaraBattleHistoriesQuery]: BankaraBattleHistories;
|
[Queries.BankaraBattleHistoriesQuery]: BankaraBattleHistories;
|
||||||
|
[Queries.XBattleHistoriesQuery]: XBattleHistories;
|
||||||
[Queries.PrivateBattleHistoriesQuery]: {
|
[Queries.PrivateBattleHistoriesQuery]: {
|
||||||
privateBattleHistories: {
|
privateBattleHistories: {
|
||||||
historyGroups: HistoryGroups<BattleListNode>;
|
historyGroups: HistoryGroups<BattleListNode>;
|
||||||
|
|
@ -698,6 +730,7 @@ export type StatInkPostBody = {
|
||||||
| "regular"
|
| "regular"
|
||||||
| "bankara_challenge"
|
| "bankara_challenge"
|
||||||
| "bankara_open"
|
| "bankara_open"
|
||||||
|
| "xmatch"
|
||||||
| "splatfest_challenge"
|
| "splatfest_challenge"
|
||||||
| "splatfest_open"
|
| "splatfest_open"
|
||||||
| "private";
|
| "private";
|
||||||
|
|
@ -732,6 +765,8 @@ export type StatInkPostBody = {
|
||||||
rank_up_battle?: "yes" | "no"; // Set "yes" if now "Rank-up Battle" mode.
|
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_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;
|
challenge_lose?: number;
|
||||||
|
x_power_before?: number;
|
||||||
|
x_power_after?: number;
|
||||||
fest_power?: number; // Splatfest Power (Pro)
|
fest_power?: number; // Splatfest Power (Pro)
|
||||||
fest_dragon?:
|
fest_dragon?:
|
||||||
| "10x"
|
| "10x"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue