feat: add tri-color support (#57)
* feat: add tri-color support * feat: add signal * feat: add tri-color filter to export script * fix: tri-color mapmain
parent
4386a75c99
commit
770458f3b6
|
|
@ -1,3 +1,7 @@
|
|||
## 0.2.6
|
||||
|
||||
feat: add tri-color support
|
||||
|
||||
## 0.2.5
|
||||
|
||||
feat: add crown
|
||||
|
|
|
|||
|
|
@ -15,14 +15,15 @@ import { Game } from "../src/types.ts";
|
|||
import { parseHistoryDetailId } from "../src/utils.ts";
|
||||
|
||||
async function exportType(
|
||||
{ statInkExporter, fileExporter, type, gameFetcher }: {
|
||||
{ statInkExporter, fileExporter, type, gameFetcher, filter }: {
|
||||
statInkExporter: StatInkExporter;
|
||||
fileExporter: FileExporter;
|
||||
gameFetcher: GameFetcher;
|
||||
type: Game["type"];
|
||||
filter?: (game: Game) => boolean;
|
||||
},
|
||||
) {
|
||||
const gameList = await fileExporter.exportedGames({ uid, type });
|
||||
const gameList = await fileExporter.exportedGames({ uid, type, filter });
|
||||
|
||||
const workQueue = [
|
||||
...await statInkExporter.notExported({
|
||||
|
|
@ -32,7 +33,10 @@ async function exportType(
|
|||
]
|
||||
.reverse().map((id) => gameList.find((i) => i.id === id)!);
|
||||
|
||||
console.log(`Exporting ${workQueue.length} ${type} games`);
|
||||
console.log(
|
||||
`Exporting ${workQueue.length} ${type} games` +
|
||||
(filter ? " (filtered)" : ""),
|
||||
);
|
||||
|
||||
let exported = 0;
|
||||
for (const { getContent } of workQueue) {
|
||||
|
|
@ -79,7 +83,7 @@ if (opts.help) {
|
|||
`Usage: deno run -A ${Deno.mainModule} [options]
|
||||
|
||||
Options:
|
||||
--type Type of game to export. Can be vs, coop, or all. (default: coop)
|
||||
--type Type of game to export. Can be vs, tri-color, coop, or all. (default: coop)
|
||||
--profile-path <path>, -p Path to config file (default: ./profile.json)
|
||||
--help Show this help message and exit`,
|
||||
);
|
||||
|
|
@ -130,7 +134,24 @@ const statInkExporter = new StatInkExporter({
|
|||
uploadMode: "Manual",
|
||||
env,
|
||||
});
|
||||
const type = (opts.type ?? "coop").replace("all", "vs,coop");
|
||||
const type = (opts.type ?? "coop").replace("all", "vs,coop,tri-color");
|
||||
|
||||
if (type.includes("tri-color")) {
|
||||
[
|
||||
await exportType({
|
||||
type: "VsInfo",
|
||||
fileExporter,
|
||||
statInkExporter,
|
||||
gameFetcher,
|
||||
filter: (game) => {
|
||||
if (game.type === "CoopInfo") {
|
||||
return false;
|
||||
}
|
||||
return game.detail.vsRule.rule === "TRI_COLOR";
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
if (type.includes("vs")) {
|
||||
await exportType({
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { StatInkPostBody, VsHistoryDetail } from "./types.ts";
|
||||
|
||||
export const AGENT_NAME = "s3si.ts";
|
||||
export const S3SI_VERSION = "0.2.5";
|
||||
export const S3SI_VERSION = "0.2.6";
|
||||
export const NSOAPP_VERSION = "2.4.0";
|
||||
export const WEB_VIEW_VERSION = "2.0.0-bd36a652";
|
||||
export const S3SI_LINK = "https://github.com/spacemeowx2/s3si.ts";
|
||||
|
|
@ -46,8 +46,7 @@ export const SPLATNET3_STATINK_MAP: {
|
|||
LOFT: "yagura",
|
||||
GOAL: "hoko",
|
||||
CLAM: "asari",
|
||||
// TODO: support tri-color
|
||||
TRI_COLOR: "nawabari",
|
||||
TRI_COLOR: "tricolor",
|
||||
},
|
||||
RESULT: {
|
||||
WIN: "win",
|
||||
|
|
|
|||
|
|
@ -59,7 +59,11 @@ export class FileExporter implements GameExporter {
|
|||
* Get all exported files
|
||||
*/
|
||||
async exportedGames(
|
||||
{ uid, type }: { uid: string; type: Game["type"] },
|
||||
{ uid, type, filter }: {
|
||||
uid: string;
|
||||
type: Game["type"];
|
||||
filter?: (game: Game) => boolean;
|
||||
},
|
||||
): Promise<{ id: string; getContent: () => Promise<Game> }[]> {
|
||||
const out: { id: string; filepath: string; timestamp: string }[] = [];
|
||||
|
||||
|
|
@ -78,12 +82,18 @@ export class FileExporter implements GameExporter {
|
|||
continue;
|
||||
}
|
||||
if (body.type === "VS" && type === "VsInfo") {
|
||||
if (filter && !filter(body.data)) {
|
||||
continue;
|
||||
}
|
||||
out.push({
|
||||
id: body.data.detail.id,
|
||||
filepath,
|
||||
timestamp,
|
||||
});
|
||||
} else if (body.type === "COOP" && type === "CoopInfo") {
|
||||
if (filter && !filter(body.data)) {
|
||||
continue;
|
||||
}
|
||||
out.push({
|
||||
id: body.data.detail.id,
|
||||
filepath,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
USERAGENT,
|
||||
} from "../constant.ts";
|
||||
import {
|
||||
Color,
|
||||
CoopHistoryDetail,
|
||||
CoopHistoryPlayerResult,
|
||||
CoopInfo,
|
||||
|
|
@ -261,14 +262,6 @@ export class StatInkExporter implements GameExporter {
|
|||
return vsMode.mode === "FEST" && b64Number(vsMode.id) === 8;
|
||||
}
|
||||
async exportGame(game: Game): Promise<ExportResult> {
|
||||
if (game.type === "VsInfo" && this.isTriColor(game.detail)) {
|
||||
// TODO: support tri-color fest
|
||||
return {
|
||||
status: "skip",
|
||||
reason: "Tri-color fest is not supported",
|
||||
};
|
||||
}
|
||||
|
||||
if (game.type === "VsInfo") {
|
||||
const body = await this.mapBattle(game);
|
||||
const { url } = await this.api.postBattle(body);
|
||||
|
|
@ -333,7 +326,7 @@ export class StatInkExporter implements GameExporter {
|
|||
} else if (modeId === 7) {
|
||||
return "splatfest_challenge";
|
||||
} else if (modeId === 8) {
|
||||
throw new Error("Tri-color battle is not supported");
|
||||
return "splatfest_open";
|
||||
}
|
||||
} else if (vsMode === "X_MATCH") {
|
||||
return "xmatch";
|
||||
|
|
@ -406,6 +399,7 @@ export class StatInkExporter implements GameExporter {
|
|||
result.assist = player.result.assist;
|
||||
result.kill = result.kill_or_assist - result.assist;
|
||||
result.death = player.result.death;
|
||||
result.signal = player.result.noroshiTry ?? undefined;
|
||||
result.special = player.result.special;
|
||||
}
|
||||
return result;
|
||||
|
|
@ -437,6 +431,10 @@ export class StatInkExporter implements GameExporter {
|
|||
}
|
||||
const startedAt = Math.floor(new Date(playedTime).getTime() / 1000);
|
||||
|
||||
if (otherTeams.length === 0) {
|
||||
throw new Error(`Other teams is empty`);
|
||||
}
|
||||
|
||||
const result: StatInkPostBody = {
|
||||
uuid: await gameId(vsDetail.id),
|
||||
lobby: this.mapLobby(vsDetail),
|
||||
|
|
@ -452,7 +450,7 @@ export class StatInkExporter implements GameExporter {
|
|||
|
||||
our_team_players: await Promise.all(myTeam.players.map(this.mapPlayer)),
|
||||
their_team_players: await Promise.all(
|
||||
otherTeams.flatMap((i) => i.players).map(
|
||||
otherTeams[0].players.map(
|
||||
this.mapPlayer,
|
||||
),
|
||||
),
|
||||
|
|
@ -472,16 +470,23 @@ export class StatInkExporter implements GameExporter {
|
|||
result.assist = self.result.assist;
|
||||
result.kill = result.kill_or_assist - result.assist;
|
||||
result.death = self.result.death;
|
||||
result.signal = self.result.noroshiTry ?? undefined;
|
||||
result.special = self.result.special;
|
||||
}
|
||||
|
||||
result.our_team_color = this.mapColor(myTeam.color);
|
||||
result.their_team_color = this.mapColor(otherTeams[0].color);
|
||||
if (otherTeams.length === 2) {
|
||||
result.third_team_color = this.mapColor(otherTeams[1].color);
|
||||
}
|
||||
|
||||
if (festMatch) {
|
||||
result.fest_dragon =
|
||||
SPLATNET3_STATINK_MAP.DRAGON[festMatch.dragonMatchType];
|
||||
result.clout_change = festMatch.contribution;
|
||||
result.fest_power = festMatch.myFestPower ?? undefined;
|
||||
}
|
||||
if (rule === "TURF_WAR") {
|
||||
if (rule === "TURF_WAR" || rule === "TRI_COLOR") {
|
||||
result.our_team_percent = (myTeam?.result?.paintRatio ?? 0) * 100;
|
||||
result.their_team_percent = (otherTeams?.[0]?.result?.paintRatio ?? 0) *
|
||||
100;
|
||||
|
|
@ -493,6 +498,39 @@ export class StatInkExporter implements GameExporter {
|
|||
(acc, i) => acc + i.paint,
|
||||
0,
|
||||
);
|
||||
|
||||
if (myTeam.tricolorRole && myTeam.festTeamName) {
|
||||
result.our_team_role = myTeam.tricolorRole === "DEFENSE"
|
||||
? "defender"
|
||||
: "attacker";
|
||||
result.our_team_theme = myTeam.festTeamName;
|
||||
}
|
||||
if (otherTeams[0].tricolorRole && otherTeams[0].festTeamName) {
|
||||
result.their_team_role = otherTeams[0].tricolorRole === "DEFENSE"
|
||||
? "defender"
|
||||
: "attacker";
|
||||
result.their_team_theme = otherTeams[0].festTeamName;
|
||||
}
|
||||
|
||||
if (otherTeams.length === 2) {
|
||||
result.third_team_players = await Promise.all(
|
||||
otherTeams[0].players.map(
|
||||
this.mapPlayer,
|
||||
),
|
||||
);
|
||||
result.third_team_percent = (otherTeams[1]?.result?.paintRatio ?? 0) *
|
||||
100;
|
||||
result.third_team_inked = otherTeams[1].players.reduce(
|
||||
(acc, i) => acc + i.paint,
|
||||
0,
|
||||
);
|
||||
if (otherTeams[1].tricolorRole && otherTeams[1].festTeamName) {
|
||||
result.third_team_role = otherTeams[1].tricolorRole === "DEFENSE"
|
||||
? "defender"
|
||||
: "attacker";
|
||||
result.third_team_theme = otherTeams[1].festTeamName;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (knockout) {
|
||||
result.knockout = knockout === "NEITHER" ? "no" : "yes";
|
||||
|
|
@ -569,6 +607,13 @@ export class StatInkExporter implements GameExporter {
|
|||
|
||||
return result;
|
||||
}
|
||||
mapColor(color: Color): string | undefined {
|
||||
const float2hex = (i: number) =>
|
||||
Math.round(i * 255).toString(16).padStart(2, "0");
|
||||
// rgba
|
||||
const nums = [color.r, color.g, color.b, color.a];
|
||||
return nums.map(float2hex).join("");
|
||||
}
|
||||
isRandom(image: Image | null): boolean {
|
||||
// question mark
|
||||
const RANDOM_FILENAME =
|
||||
|
|
|
|||
26
src/types.ts
26
src/types.ts
|
|
@ -154,6 +154,7 @@ export type VsPlayer = {
|
|||
death: number;
|
||||
assist: number;
|
||||
special: number;
|
||||
noroshiTry: null | number;
|
||||
} | null;
|
||||
paint: number;
|
||||
crown: boolean;
|
||||
|
|
@ -162,8 +163,17 @@ export type VsPlayer = {
|
|||
clothingGear: PlayerGear;
|
||||
shoesGear: PlayerGear;
|
||||
};
|
||||
export type Color = {
|
||||
a: number;
|
||||
b: number;
|
||||
g: number;
|
||||
r: number;
|
||||
};
|
||||
export type VsTeam = {
|
||||
players: VsPlayer[];
|
||||
color: Color;
|
||||
tricolorRole: null | "DEFENSE" | "ATTACK1" | "ATTACK2";
|
||||
festTeamName: null | string;
|
||||
result: null | {
|
||||
paintRatio: null | number;
|
||||
score: null | number;
|
||||
|
|
@ -637,6 +647,7 @@ export type StatInkPlayer = {
|
|||
assist?: number;
|
||||
kill_or_assist?: number;
|
||||
death?: number;
|
||||
signal?: number;
|
||||
special?: number;
|
||||
gears?: StatInkGears;
|
||||
crown?: "yes" | "no";
|
||||
|
|
@ -742,7 +753,7 @@ export type StatInkPostBody = {
|
|||
| "splatfest_challenge"
|
||||
| "splatfest_open"
|
||||
| "private";
|
||||
rule: "nawabari" | "area" | "hoko" | "yagura" | "asari";
|
||||
rule: "nawabari" | "area" | "hoko" | "yagura" | "asari" | "tricolor";
|
||||
stage: string;
|
||||
weapon: string;
|
||||
result: "win" | "lose" | "draw" | "exempted_lose";
|
||||
|
|
@ -752,15 +763,27 @@ export type StatInkPostBody = {
|
|||
assist?: number;
|
||||
kill_or_assist?: number; // equals to kill + assist if you know them
|
||||
death?: number;
|
||||
signal?: number;
|
||||
special?: number; // use count
|
||||
inked: number; // not including bonus
|
||||
medals: string[]; // 0-3 elements
|
||||
our_team_inked?: number; // TW, not including bonus
|
||||
their_team_inked?: number; // TW, not including bonus
|
||||
third_team_inked?: number; // Tricolor Turf War
|
||||
our_team_percent?: number; // TW
|
||||
their_team_percent?: number; // TW
|
||||
third_team_percent?: number; // Tricolor Turf War
|
||||
our_team_count?: number; // Anarchy
|
||||
their_team_count?: number; // Anarchy
|
||||
our_team_color?: string;
|
||||
their_team_color?: string;
|
||||
third_team_color?: string;
|
||||
our_team_role?: "attacker" | "defender";
|
||||
their_team_role?: "attacker" | "defender";
|
||||
third_team_role?: "attacker" | "defender";
|
||||
our_team_theme?: string;
|
||||
their_team_theme?: string;
|
||||
third_team_theme?: string;
|
||||
level_before?: number;
|
||||
level_after?: number;
|
||||
rank_before?: string; // one of c- ... s+, lowercase only /^[abcs][+-]?$/ except s-
|
||||
|
|
@ -790,6 +813,7 @@ export type StatInkPostBody = {
|
|||
cash_after?: number;
|
||||
our_team_players: StatInkPlayer[];
|
||||
their_team_players: StatInkPlayer[];
|
||||
third_team_players?: StatInkPlayer[]; // Tricolor Turf War
|
||||
|
||||
agent: string;
|
||||
agent_version: string;
|
||||
|
|
|
|||
Loading…
Reference in New Issue