checkpoint: 6

This commit is contained in:
zkldi
2023-01-19 16:39:21 +00:00
parent 971db53130
commit fcbb54313b
20 changed files with 208 additions and 900 deletions
+7 -9
View File
@@ -29,15 +29,13 @@ export function ClassValue(id: string, display: string, hoverText?: string): Cla
return { id, display, hoverText };
}
export function zodTierlistData() {
return z.nullable(
z.object({
value: z.number(),
text: z.string(),
individualDifference: z.boolean(),
})
);
}
export const zodTierlistData = z.nullable(
z.strictObject({
value: z.number(),
text: z.string(),
individualDifference: z.boolean(),
})
);
export const zodInt = z.number().int();
export const zodNonNegativeInt = z.number().int().nonnegative();
+25 -5
View File
@@ -7,12 +7,12 @@ export const BMS_CONF = {
defaultPlaytype: "7K",
name: "BMS",
validPlaytypes: ["7K", "14K"],
songData: {
songData: z.strictObject({
genre: z.nullable(z.string()),
subtitle: z.nullable(z.string()),
subartist: z.nullable(z.string()),
tableString: z.nullable(z.string()),
},
}),
} as const satisfies INTERNAL_GAME_CONFIG;
function FormatSieglindeBMS(sgl: number): string {
@@ -130,6 +130,8 @@ export const BMSScratchDans = [
ClassValue("KAIDEN", "皆伝", "Scratch Kaiden"),
];
const RANDOM_SCHEMA = z.enum(["MIRROR", "NONRAN", "R-RANDOM", "RANDOM", "S-RANDOM"]);
export const BMS_7K_CONF = {
providedMetrics: {
score: { type: "INTEGER" },
@@ -239,14 +241,25 @@ export const BMS_7K_CONF = {
chartSets: [],
chartData: {
chartData: z.strictObject({
notecount: zodNonNegativeInt,
hashMD5: z.string(),
hashSHA256: z.string(),
tableFolders: z.array(z.object({ table: z.string(), level: z.string() })),
tableFolders: z.array(z.strictObject({ table: z.string(), level: z.string() })),
sglEC: z.number().nullable(),
sglHC: z.number().nullable(),
},
}),
preferences: z.strictObject({
displayTables: z.array(z.string()),
}),
scoreMeta: z.strictObject({
random: RANDOM_SCHEMA.optional(),
inputDevice: z.enum(["BM_CONTROLLER", "KEYBOARD"]).optional(),
client: z.enum(["lr2oraja", "LR2"]).optional(),
gauge: z.enum(["EASY", "NORMAL", "HARD", "EX-HARD"]).optional(),
}),
supportedMatchTypes: ["bmsChartHash", "tachiSongID"],
} as const satisfies INTERNAL_GPT_CONFIG;
@@ -257,4 +270,11 @@ export const BMS_14K_CONF = {
genocideDan: { type: "PROVIDED", values: BMSGenocideDans },
stslDan: { type: "PROVIDED", values: BMSDPSlDans },
},
scoreMeta: z.strictObject({
random: z.tuple([RANDOM_SCHEMA, RANDOM_SCHEMA]).optional(),
inputDevice: z.enum(["BM_CONTROLLER", "KEYBOARD"]).optional(),
client: z.enum(["lr2oraja", "LR2"]).optional(),
gauge: z.enum(["EASY", "NORMAL", "HARD", "EX-HARD"]).optional(),
}),
} as const satisfies INTERNAL_GPT_CONFIG;
+8 -4
View File
@@ -7,10 +7,10 @@ export const CHUNITHM_CONF = {
defaultPlaytype: "Single",
name: "CHUNITHM",
validPlaytypes: ["Single"],
songData: {
songData: z.strictObject({
genre: z.string(),
displayVersion: z.string(),
},
}),
} as const satisfies INTERNAL_GAME_CONFIG;
export const CHUNITHMColours = [
@@ -97,9 +97,13 @@ export const CHUNITHM_SINGLE_CONF = {
chartSets: ["Paradise Lost"],
chartData: {
chartData: z.strictObject({
inGameID: zodNonNegativeInt,
},
}),
preferences: z.strictObject({}),
scoreMeta: z.strictObject({}),
supportedMatchTypes: ["inGameID", "songTitle", "tachiSongID"],
} as const satisfies INTERNAL_GPT_CONFIG;
+10 -6
View File
@@ -1,5 +1,5 @@
import { FAST_SLOW_MAXCOMBO } from "./_common";
import { ClassValue } from "../config-utils";
import { ClassValue, zodNonNegativeInt } from "../config-utils";
import { z } from "zod";
import type { INTERNAL_GAME_CONFIG, INTERNAL_GPT_CONFIG } from "../../types/internals";
@@ -7,10 +7,10 @@ export const GITADORA_CONF = {
defaultPlaytype: "Dora",
name: "GITADORA",
validPlaytypes: ["Gita", "Dora"],
songData: {
songData: z.strictObject({
isHot: z.boolean(),
displayVersion: z.string(),
},
}),
} as const satisfies INTERNAL_GAME_CONFIG;
const GitadoraColours = [
@@ -109,9 +109,13 @@ export const GITADORA_GITA_CONF = {
chartSets: ["Konaste"],
chartData: {
inGameID: z.number().int(),
},
chartData: z.strictObject({
inGameID: zodNonNegativeInt,
}),
preferences: z.strictObject({}),
scoreMeta: z.strictObject({}),
supportedMatchTypes: ["inGameID", "songTitle", "tachiSongID"],
} as const satisfies INTERNAL_GPT_CONFIG;
+31 -10
View File
@@ -7,10 +7,10 @@ export const IIDX_CONF = {
defaultPlaytype: "SP",
name: "beatmania IIDX",
validPlaytypes: ["SP", "DP"],
songData: {
songData: z.strictObject({
genre: z.string(),
displayVersion: z.nullable(z.string()),
},
}),
} as const satisfies INTERNAL_GAME_CONFIG;
export const IIDXDans = [
@@ -45,6 +45,8 @@ const BASE_IIDX_CHART_DATA = {
bpiCoefficient: z.number().positive().nullable(),
};
const RANDOM_SCHEMA = z.enum(["NONRAN", "MIRROR", "R-RANDOM", "RANDOM", "S-RANDOM"]);
export const IIDX_SP_CONF = {
providedMetrics: {
score: { type: "INTEGER" },
@@ -206,12 +208,24 @@ export const IIDX_SP_CONF = {
"INFINITAS",
],
chartData: {
chartData: z.strictObject({
...BASE_IIDX_CHART_DATA,
ncTier: zodTierlistData(),
hcTier: zodTierlistData(),
exhcTier: zodTierlistData(),
},
ncTier: zodTierlistData,
hcTier: zodTierlistData,
exhcTier: zodTierlistData,
}),
preferences: z.strictObject({
display2DXTra: z.boolean(),
bpiTarget: z.number(),
}),
scoreMeta: z.strictObject({
random: RANDOM_SCHEMA.optional(),
assist: z.enum(["AUTO SCRATCH", "LEGACY NOTE", "NO ASSIST", "FULL ASSIST"]).optional(),
range: z.enum(["NONE", "HIDDEN+", "SUDDEN+", "LIFT", "LIFT SUD+", "SUD+ HID+"]).optional(),
gauge: z.enum(["ASSISTED EASY", "EASY", "NORMAL", "HARD", "EX-HARD"]).optional(),
}),
supportedMatchTypes: ["inGameID", "tachiSongID", "songTitle"],
} as const satisfies INTERNAL_GPT_CONFIG;
@@ -219,9 +233,16 @@ export const IIDX_SP_CONF = {
export const IIDX_DP_CONF = {
...IIDX_SP_CONF,
chartData: {
chartData: z.strictObject({
...BASE_IIDX_CHART_DATA,
dpTier: zodTierlistData(),
},
dpTier: zodTierlistData,
}),
scoreMeta: z.strictObject({
random: z.tuple([RANDOM_SCHEMA, RANDOM_SCHEMA]).optional(),
assist: z.enum(["AUTO SCRATCH", "LEGACY NOTE", "NO ASSIST", "FULL ASSIST"]).optional(),
range: z.enum(["NONE", "HIDDEN+", "SUDDEN+", "LIFT", "LIFT SUD+", "SUD+ HID+"]).optional(),
gauge: z.enum(["ASSISTED EASY", "EASY", "NORMAL", "HARD", "EX-HARD"]).optional(),
}),
} as const satisfies INTERNAL_GPT_CONFIG;
+17 -6
View File
@@ -6,7 +6,10 @@ export const ITG_CONF = {
defaultPlaytype: "Stamina",
name: "ITG",
validPlaytypes: ["Stamina"],
songData: { subtitle: z.string(), originalPack: z.string() },
songData: z.strictObject({
subtitle: z.string(),
originalPack: z.string(),
}),
} as const satisfies INTERNAL_GAME_CONFIG;
export const ITG_STAMINA_CONF = {
@@ -96,16 +99,20 @@ export const ITG_STAMINA_CONF = {
chartSets: [],
chartData: {
chartData: z.strictObject({
hashGSv3: z.string(),
difficultyTag: z.enum(["Beginner", "Easy", "Medium", "Hard", "Expert", "Edit"] as const),
length: z.number().positive(),
charter: z.string(),
// What BPM tier should this chart appear in?
primaryBPM: z.number(),
// Pick one BPM to represent this chart. If that doesn't make any sense
// (i.e. chart has significant bpm changes)
// this should be null.
// Note that for some charts with BPM changes, Archi typically picks a common
// BPM to place it at. Not sure if that's automatable.
primaryBPM: z.number().nullable(),
breakdown: z.object({
breakdown: z.strictObject({
detailed: z.string(),
partiallySimplified: z.string(),
simplified: z.string(),
@@ -114,7 +121,11 @@ export const ITG_STAMINA_CONF = {
npsPerMeasure: z.array(zodNonNegativeInt),
notesPerMeasure: z.array(zodNonNegativeInt),
},
}),
preferences: z.strictObject({}),
scoreMeta: z.strictObject({}),
supportedMatchTypes: ["itgChartHash", "tachiSongID"],
} as const satisfies INTERNAL_GPT_CONFIG;
+8 -4
View File
@@ -7,9 +7,9 @@ export const JUBEAT_CONF = {
defaultPlaytype: "Single",
name: "jubeat",
validPlaytypes: ["Single"],
songData: {
songData: z.strictObject({
displayVersion: z.string(),
},
}),
} as const satisfies INTERNAL_GAME_CONFIG;
const JubeatColours = [
@@ -102,9 +102,13 @@ export const JUBEAT_SINGLE_CONF = {
"festo",
],
chartData: {
chartData: z.strictObject({
inGameID: z.union([z.array(zodNonNegativeInt), zodNonNegativeInt]),
},
}),
preferences: z.strictObject({ jubilityTarget: z.number() }),
scoreMeta: z.strictObject({}),
supportedMatchTypes: ["inGameID", "tachiSongID"],
} as const satisfies INTERNAL_GPT_CONFIG;
+7 -4
View File
@@ -7,9 +7,9 @@ export const MAIMAI_DX_CONF = {
defaultPlaytype: "Single",
name: "maimai DX",
validPlaytypes: ["Single"],
songData: {
songData: z.strictObject({
displayVersion: z.string(),
},
}),
} as const satisfies INTERNAL_GAME_CONFIG;
const MaimaiDXDans = [
@@ -161,9 +161,12 @@ export const MAIMAI_DX_SINGLE_CONF = {
chartSets: ["UNiVERSE PLUS"],
chartData: {
chartData: z.strictObject({
isLatest: z.boolean(),
},
}),
preferences: z.strictObject({}),
scoreMeta: z.strictObject({}),
supportedMatchTypes: ["songTitle", "tachiSongID"],
} as const satisfies INTERNAL_GPT_CONFIG;
+7 -3
View File
@@ -7,11 +7,11 @@ export const MUSECA_CONF = {
defaultPlaytype: "Single",
name: "MÚSECA",
validPlaytypes: ["Single"],
songData: {
songData: z.strictObject({
titleJP: z.string(),
artistJP: z.string(),
displayVersion: z.string(),
},
}),
} as const satisfies INTERNAL_GAME_CONFIG;
export const MUSECA_SINGLE_CONF = {
@@ -80,7 +80,11 @@ export const MUSECA_SINGLE_CONF = {
chartSets: ["1 + 1/2", "1 + 1/2 Rev. B"],
chartData: { inGameID: zodNonNegativeInt },
chartData: z.strictObject({ inGameID: zodNonNegativeInt }),
preferences: z.strictObject({}),
scoreMeta: z.strictObject({}),
supportedMatchTypes: ["songTitle", "tachiSongID", "inGameID"],
} as const satisfies INTERNAL_GPT_CONFIG;
+12 -5
View File
@@ -7,12 +7,12 @@ export const PMS_CONF = {
defaultPlaytype: "Controller",
name: "PMS",
validPlaytypes: ["Controller", "Keyboard"],
songData: {
songData: z.strictObject({
genre: z.nullable(z.string()),
subtitle: z.nullable(z.string()),
subartist: z.nullable(z.string()),
tableString: z.nullable(z.string()),
},
}),
} as const satisfies INTERNAL_GAME_CONFIG;
const PMSDans = [
@@ -121,14 +121,21 @@ export const PMS_CONTROLLER_CONF = {
chartSets: [],
chartData: {
chartData: z.strictObject({
notecount: zodNonNegativeInt,
hashMD5: z.string(),
hashSHA256: z.string(),
tableFolders: z.array(z.object({ table: z.string(), level: z.string() })),
tableFolders: z.array(z.strictObject({ table: z.string(), level: z.string() })),
sglEC: z.number().nullable(),
sglHC: z.number().nullable(),
},
}),
preferences: z.strictObject({}),
scoreMeta: z.strictObject({
random: z.enum(["MIRROR", "NONRAN", "R-RANDOM", "RANDOM", "S-RANDOM"]).optional(),
client: z.enum(["beatoraja"]).optional(),
gauge: z.enum(["EASY", "NORMAL", "HARD", "EX-HARD"]).optional(),
}),
supportedMatchTypes: ["bmsChartHash", "tachiSongID"],
} as const satisfies INTERNAL_GPT_CONFIG;
+14 -4
View File
@@ -7,11 +7,11 @@ export const POPN_CONF = {
defaultPlaytype: "9B",
name: "pop'n music",
validPlaytypes: ["9B"],
songData: {
songData: z.strictObject({
displayVersion: z.nullable(z.string()),
genre: z.string(),
genreEN: z.nullable(z.string()),
},
}),
} as const satisfies INTERNAL_GAME_CONFIG;
const PopnClasses = [
@@ -118,11 +118,21 @@ export const POPN_9B_CONF = {
chartSets: ["peace", "Kaimei Riddles"],
chartData: {
chartData: z.strictObject({
// Array<string> | string | null
hashSHA256: z.union([z.array(z.string()), z.string(), z.null()]),
inGameID: zodNonNegativeInt,
},
}),
scoreMeta: z.strictObject({
hiSpeed: z.number().optional(),
hidden: z.number().optional(),
sudden: z.number().optional(),
random: z.enum(["MIRROR", "NONRAN", "RANDOM", "S-RANDOM"]).optional(),
gauge: z.enum(["EASY", "NORMAL", "HARD", "DANGER"]).optional(),
}),
preferences: z.strictObject({}),
supportedMatchTypes: ["inGameID", "tachiSongID", "popnChartHash"],
} as const satisfies INTERNAL_GPT_CONFIG;
+8 -5
View File
@@ -6,9 +6,9 @@ export const SDVX_CONF = {
defaultPlaytype: "Single",
name: "SOUND VOLTEX",
validPlaytypes: ["Single"],
songData: {
songData: z.strictObject({
displayVersion: z.string(),
},
}),
} as const satisfies INTERNAL_GAME_CONFIG;
const SDVXDans = [
@@ -159,10 +159,13 @@ export const SDVX_SINGLE_CONF = {
"Konaste",
],
chartData: {
chartData: z.strictObject({
inGameID: zodNonNegativeInt,
clearTier: zodTierlistData(),
},
clearTier: zodTierlistData,
}),
preferences: z.strictObject({ vf6Target: z.number() }),
scoreMeta: z.strictObject({}),
supportedMatchTypes: ["sdvxInGameID", "songTitle", "tachiSongID"],
} as const satisfies INTERNAL_GPT_CONFIG;
+10 -4
View File
@@ -7,7 +7,7 @@ export const USC_CONF = {
defaultPlaytype: "Controller",
name: "USC",
validPlaytypes: ["Controller", "Keyboard"],
songData: {},
songData: z.strictObject({}),
} as const satisfies INTERNAL_GAME_CONFIG;
export const USC_CONTROLLER_CONF = {
@@ -82,17 +82,23 @@ export const USC_CONTROLLER_CONF = {
chartSets: [],
chartData: {
chartData: z.strictObject({
hashSHA1: z.union([z.array(z.string()), z.string()]),
isOfficial: z.boolean(),
effector: z.string(),
tableFolders: z.array(
z.object({
z.strictObject({
table: z.string(),
level: z.string(),
})
),
},
}),
preferences: z.strictObject({ vf6Target: z.number() }),
scoreMeta: z.strictObject({
noteMod: z.enum(["MIR-RAN", "MIRROR", "NORMAL", "RANDOM"]).optional(),
gaugeMod: z.enum(["NORMAL", "HARD", "PERMISSIVE"]).optional(),
}),
supportedMatchTypes: ["uscChartHash", "tachiSongID"],
} as const satisfies INTERNAL_GPT_CONFIG;
+7 -4
View File
@@ -7,12 +7,12 @@ export const WACCA_CONF = {
defaultPlaytype: "Single",
name: "WACCA",
validPlaytypes: ["Single"],
songData: {
songData: z.strictObject({
titleJP: z.string(),
artistJP: z.string(),
genre: z.string(),
displayVersion: z.nullable(z.string()),
},
}),
} as const satisfies INTERNAL_GAME_CONFIG;
export const WaccaStageUps = [
@@ -132,9 +132,12 @@ export const WACCA_SINGLE_CONF = {
chartSets: ["REVERSE"],
chartData: {
chartData: z.strictObject({
isHot: z.boolean(),
},
}),
preferences: z.strictObject({}),
scoreMeta: z.strictObject({ mirror: z.boolean().optional() }),
supportedMatchTypes: ["songTitle", "tachiSongID"],
} as const satisfies INTERNAL_GPT_CONFIG;
@@ -1,755 +0,0 @@
import type { integer } from "../types";
/**
* Utility index type. Given an GPTString [I], return its playtype.
*/
export interface GPTStringToPlaytype {
"iidx:SP": "SP";
"iidx:DP": "DP";
"popn:9B": "9B";
"sdvx:Single": "Single";
"usc:Keyboard": "Keyboard";
"usc:Controller": "Controller";
"maimaidx:Single": "Single";
"jubeat:Single": "Single";
"museca:Single": "Single";
"bms:7K": "7K";
"bms:14K": "14K";
"chunithm:Single": "Single";
"gitadora:Gita": "Gita";
"gitadora:Dora": "Dora";
"wacca:Single": "Single";
"pms:Keyboard": "Keyboard";
"pms:Controller": "Controller";
"itg:Stamina": "Stamina";
}
/**
* Utility index type. Given a GPTString [I], return its game.
*
* @example GPTStringToGame["iidx:SP"] -> "iidx"
*/
export interface GPTStringToGame {
"iidx:SP": "iidx";
"iidx:DP": "iidx";
"popn:9B": "popn";
"sdvx:Single": "sdvx";
"usc:Keyboard": "usc";
"usc:Controller": "usc";
"maimaidx:Single": "maimaidx";
"jubeat:Single": "jubeat";
"museca:Single": "museca";
"bms:7K": "bms";
"bms:14K": "bms";
"chunithm:Single": "chunithm";
"gitadora:Gita": "gitadora";
"gitadora:Dora": "gitadora";
"wacca:Single": "wacca";
"pms:Keyboard": "pms";
"pms:Controller": "pms";
"itg:Stamina": "itg";
}
/**
* Given a game, return all IDStrings this game has.
*/
export interface GameToIDStrings {
iidx: "iidx:DP" | "iidx:SP";
sdvx: "sdvx:Single";
usc: "usc:Controller" | "usc:Keyboard";
maimaidx: "maimaidx:Single";
jubeat: "jubeat:Single";
museca: "museca:Single";
bms: "bms:7K" | "bms:14K";
chunithm: "chunithm:Single";
gitadora: "gitadora:Dora" | "gitadora:Gita";
popn: "popn:9B";
wacca: "wacca:Single";
pms: "pms:Controller" | "pms:Keyboard";
itg: "itg:Stamina";
}
type IIDXGrades = "A" | "AA" | "AAA" | "B" | "C" | "D" | "E" | "F" | "MAX-" | "MAX";
type SDVXGrades = "A" | "A+" | "AA" | "AA+" | "AAA" | "AAA+" | "B" | "C" | "D" | "PUC" | "S";
type GitadoraGrades = "A" | "B" | "C" | "MAX" | "S" | "SS";
/**
* What grades do Tachi's supported GPTs have?
* Grades are an ordered list of discrete values for a given game,
* typically derived from score values.
*
* All GPTs **must** define a series of grades, as they are used frequently in the UI
* for things like folder graphs and raise breakdowns.
*/
export interface Grades {
"iidx:SP": IIDXGrades;
"iidx:DP": IIDXGrades;
"popn:9B": "A" | "AA" | "AAA" | "B" | "C" | "D" | "E" | "S";
"sdvx:Single": SDVXGrades;
"usc:Keyboard": SDVXGrades;
"usc:Controller": SDVXGrades;
"maimaidx:Single":
| "A"
| "AA"
| "AAA"
| "B"
| "BB"
| "BBB"
| "C"
| "D"
| "S"
| "S+"
| "SS"
| "SS+"
| "SSS"
| "SSS+";
"jubeat:Single": "A" | "B" | "C" | "D" | "E" | "EXC" | "S" | "SS" | "SSS";
"museca:Single": "佳" | "傑" | "傑G" | "優" | "凡" | "拙" | "没" | "秀" | "良";
"bms:7K": IIDXGrades;
"bms:14K": IIDXGrades;
"chunithm:Single": "A" | "AA" | "AAA" | "B" | "BB" | "BBB" | "C" | "D" | "S" | "SS" | "SSS";
"gitadora:Gita": GitadoraGrades;
"gitadora:Dora": GitadoraGrades;
"wacca:Single":
| "A"
| "AA"
| "AAA"
| "B"
| "C"
| "D"
| "MASTER"
| "S"
| "S+"
| "SS"
| "SS+"
| "SSS"
| "SSS+";
"pms:Controller": IIDXGrades;
"pms:Keyboard": IIDXGrades;
"itg:Stamina": "★" | "★★" | "★★★" | "★★★★" | "A" | "B" | "C" | "D" | "S-" | "S" | "S+";
}
type IIDXLamps =
| "ASSIST CLEAR"
| "CLEAR"
| "EASY CLEAR"
| "EX HARD CLEAR"
| "FAILED"
| "FULL COMBO"
| "HARD CLEAR"
| "NO PLAY";
type GitadoraLamps = "CLEAR" | "EXCELLENT" | "FAILED" | "FULL COMBO";
type SDVXLamps =
| "CLEAR"
| "EXCESSIVE CLEAR"
| "FAILED"
| "PERFECT ULTIMATE CHAIN"
| "ULTIMATE CHAIN";
/**
* What lamps do Tachi's supported GPTs have? Lamps are an ordered list of discrete
* values, typically representing a "clear type" or something similar.
*
* Common examples of lamps include "FAILED", "CLEAR" and "FULL COMBO".
*
* All GPTs **must** define a series of lamps, as they're used frequently in the UI
* for things like folder graphs and raise breakdowns.
*/
export interface Lamps {
"iidx:SP": IIDXLamps;
"iidx:DP": IIDXLamps;
"popn:9B": "CLEAR" | "EASY CLEAR" | "FAILED" | "FULL COMBO" | "PERFECT";
"sdvx:Single": SDVXLamps;
"usc:Keyboard": SDVXLamps;
"usc:Controller": SDVXLamps;
"maimaidx:Single":
| "ALL PERFECT"
| "ALL PERFECT+"
| "CLEAR"
| "FAILED"
| "FULL COMBO"
| "FULL COMBO+";
"jubeat:Single": "CLEAR" | "EXCELLENT" | "FAILED" | "FULL COMBO";
"museca:Single": "CLEAR" | "CONNECT ALL" | "FAILED" | "PERFECT CONNECT ALL";
"bms:7K": IIDXLamps;
"bms:14K": IIDXLamps;
"chunithm:Single": "ALL JUSTICE CRITICAL" | "ALL JUSTICE" | "CLEAR" | "FAILED" | "FULL COMBO";
"gitadora:Gita": GitadoraLamps;
"gitadora:Dora": GitadoraLamps;
"wacca:Single": "ALL MARVELOUS" | "CLEAR" | "FAILED" | "FULL COMBO" | "MISSLESS";
"pms:Controller": IIDXLamps;
"pms:Keyboard": IIDXLamps;
"itg:Stamina": "CLEAR" | "FAILED" | "FULL COMBO" | "FULL EXCELLENT COMBO" | "QUAD";
}
type IIDX2DXTraSets = "All Scratch" | "Kichiku" | "Kiraku";
type IIDXSPDifficulties = "ANOTHER" | "BEGINNER" | "HYPER" | "LEGGENDARIA" | "NORMAL";
type IIDXDPDifficulties = "ANOTHER" | "HYPER" | "LEGGENDARIA" | "NORMAL";
/**
* What difficulties do Tachi's supported GPTs have?
*
* A "difficulty" is a string attached onto a chart document. In the simplest terms,
* this determines how many charts a given song can have, and how to distinguish
* them.
* A chart **must** be unique on songID + playtype + difficulty.
* An example would be something like:
* FREEDOM DiVE SP ANOTHER
* FREEDOM DiVE DP ANOTHER
* FREEDOM DiVE DP HYPER
*
* For most arcade games this is a fixed set of values, like "Easy", "Normal"
* or "Hard". However, Tachi isn't limited to a fixed set of strings.
* Some games - especially home games - tend to support any amount of difficulties
* per song. For this case, you can set the supported difficulties to just `string`.
* This will allow *any* string in that field, and therefore a song can have any
* amount of charts.
*/
export interface Difficulties {
"iidx:SP": `${"" | `${IIDX2DXTraSets} `}${IIDXSPDifficulties}`;
"iidx:DP": `${"" | `${IIDX2DXTraSets} `}${IIDXDPDifficulties}`;
"popn:9B": "Easy" | "EX" | "Hyper" | "Normal";
"sdvx:Single": "ADV" | "EXH" | "GRV" | "HVN" | "INF" | "MXM" | "NOV" | "VVD" | "XCD";
"usc:Controller": "ADV" | "EXH" | "INF" | "NOV";
"usc:Keyboard": "ADV" | "EXH" | "INF" | "NOV";
"maimaidx:Single":
| "Advanced"
| "Basic"
| "DX Advanced"
| "DX Basic"
| "DX Expert"
| "DX Master"
| "DX Re:Master"
| "Expert"
| "Master"
| "Re:Master";
"jubeat:Single": "ADV" | "BSC" | "EXT" | "HARD ADV" | "HARD BSC" | "HARD EXT";
"museca:Single": "Green" | "Red" | "Yellow";
"bms:7K": "CHART";
"bms:14K": "CHART";
"chunithm:Single": "ADVANCED" | "BASIC" | "EXPERT" | "MASTER";
"gitadora:Gita":
| "ADVANCED"
| "BASIC"
| "BASS ADVANCED"
| "BASS BASIC"
| "BASS EXTREME"
| "BASS MASTER"
| "EXTREME"
| "MASTER";
"gitadora:Dora": "ADVANCED" | "BASIC" | "EXTREME" | "MASTER";
"wacca:Single": "EXPERT" | "HARD" | "INFERNO" | "NORMAL";
"pms:Controller": "CHART";
"pms:Keyboard": "CHART";
"itg:Stamina": string;
}
/**
* What rating algorithms are ran on sessions for this GPT?
*
* Sessions may have data calculated about them and attached on.
* For example, we may want to average the best 10 skill values this user got in
* a session. Things like this allow us to sort a users sessions and display their
* best.
*
* This type declares the names of those algorithms, and typesafety enforces that
* all of them get an implementation.
*/
export interface SessionRatingAlgorithms {
"iidx:SP": "BPI" | "ktLampRating";
"iidx:DP": "BPI" | "ktLampRating";
"popn:9B": "classPoints";
"sdvx:Single": "ProfileVF6" | "VF6";
"usc:Keyboard": "ProfileVF6" | "VF6";
"usc:Controller": "ProfileVF6" | "VF6";
"maimaidx:Single": "rate";
"jubeat:Single": "jubility";
"museca:Single": "curatorSkill";
"bms:7K": "sieglinde";
"bms:14K": "sieglinde";
"chunithm:Single": "naiveRating";
"gitadora:Gita": "skill";
"gitadora:Dora": "skill";
"wacca:Single": "rate";
"pms:Controller": "sieglinde";
"pms:Keyboard": "sieglinde";
"itg:Stamina": "average32Speed" | "blockRating";
}
/**
* What rating algorithms apply to a users profile for this GPT?
*
* This should be self explanatory.
*/
export interface ProfileRatingAlgorithms {
"iidx:SP": "BPI" | "ktLampRating";
"iidx:DP": "BPI" | "ktLampRating";
"popn:9B": "naiveClassPoints";
"sdvx:Single": "VF6";
"usc:Keyboard": "VF6";
"usc:Controller": "VF6";
"maimaidx:Single": "naiveRate" | "rate";
"jubeat:Single": "jubility" | "naiveJubility";
"museca:Single": "curatorSkill";
"bms:7K": "sieglinde";
"bms:14K": "sieglinde";
"chunithm:Single": "naiveRating";
"gitadora:Gita": "naiveSkill" | "skill";
"gitadora:Dora": "naiveSkill" | "skill";
"wacca:Single": "naiveRate" | "rate";
"pms:Controller": "sieglinde";
"pms:Keyboard": "sieglinde";
"itg:Stamina": "highest32" | "highest256" | "highestBlock";
}
/**
* What rating algorithms exist for scores in this GPT?
*
* Scores themselves may have calculated data attached onto them.
* This type declares the names of those algorithms, and their implementation is
* enforced by typesafety.
*/
export interface ScoreRatingAlgorithms {
"iidx:SP": "BPI" | "ktLampRating";
"iidx:DP": "BPI" | "ktLampRating";
"popn:9B": "classPoints";
"sdvx:Single": "VF6";
"usc:Keyboard": "VF6";
"usc:Controller": "VF6";
"maimaidx:Single": "rate";
"jubeat:Single": "jubility";
"museca:Single": "curatorSkill";
"bms:7K": "sieglinde";
"bms:14K": "sieglinde";
"chunithm:Single": "rating";
"gitadora:Gita": "skill";
"gitadora:Dora": "skill";
"wacca:Single": "rate";
"pms:Controller": "sieglinde";
"pms:Keyboard": "sieglinde";
"itg:Stamina": "blockRating" | "highest32" | "highest256";
}
type SupportedIIDXVersions =
| "3-cs"
| "4-cs"
| "5-cs"
| "6-cs"
| "7-cs"
| "8-cs"
| "9-cs"
| "10-cs"
| "11-cs"
| "12-cs"
| "13-cs"
| "14-cs"
| "15-cs"
| "16-cs"
| "20"
| "21"
| "22"
| "23"
| "24"
| "25"
| "26-omni"
| "26"
| "27-2dxtra"
| "27-omni"
| "27"
| "28-2dxtra"
| "28-omni"
| "28"
| "29-omni"
| "29"
| "30"
| "bmus"
| "inf";
interface ChartDataIIDX {
notecount: integer;
inGameID: Array<integer> | integer;
hashSHA256: string | null;
"2dxtraSet": string | null;
kaidenAverage: integer | null;
worldRecord: integer | null;
bpiCoefficient: number | null;
}
interface ChartDataPMS {
notecount: integer;
hashMD5: string;
hashSHA256: string;
tableFolders: Array<{ table: string; level: string }>;
}
type ChartDataBMS = ChartDataPMS & {
aiLevel?: string | null;
};
interface ChartDataUSC {
hashSHA1: Array<string> | string;
isOfficial: boolean;
effector: string;
tableFolders: Array<{ table: string; level: string }>;
}
/**
* What GPT-Specific content is stored on each chart?
*
* All charts in Tachi get a `data` property where they can store GPT-specific content.
* This allows us to store things like `kaidenAverage` for IIDX, but not make that
* property exist on - say - pop'n, where it wouldn't make any sense.
*/
export interface ChartData {
"iidx:SP": ChartDataIIDX;
"iidx:DP": ChartDataIIDX;
"popn:9B": { hashSHA256: Array<string> | string | null; inGameID: integer };
"sdvx:Single": { inGameID: integer };
"usc:Keyboard": ChartDataUSC;
"usc:Controller": ChartDataUSC;
"jubeat:Single": { inGameID: Array<integer> | integer; isHardMode: boolean };
"maimaidx:Single": { isLatest: boolean };
"museca:Single": { inGameID: integer };
"bms:7K": ChartDataBMS;
"bms:14K": ChartDataBMS;
"chunithm:Single": { inGameID: integer };
"gitadora:Gita": { inGameID: integer };
"gitadora:Dora": { inGameID: integer };
"wacca:Single": { isHot: boolean };
"pms:Controller": ChartDataPMS;
"pms:Keyboard": ChartDataPMS;
"itg:Stamina": {
hashGSv3: string;
difficultyTag: "Beginner" | "Challenge" | "Easy" | "Edit" | "Hard" | "Medium";
breakdown: {
detailed: string;
partiallySimplified: string;
simplified: string;
total: string;
npsPerMeasure: Array<number>;
notesPerMeasure: Array<number>;
};
length: number;
charter: string;
displayBPM: number;
};
}
/**
* What Game-Specific content is stored on each song?
*
* All songs in Tachi get a `data` field which allows them to store game-specific
* fields. This allows us to have things like "genre" for some games, but not all.
*/
export interface SongData {
iidx: { genre: string; displayVersion: string | null };
museca: { titleJP: string; artistJP: string; displayVersion: string };
maimaidx: { displayVersion: string };
jubeat: { displayVersion: string };
popn: {
displayVersion: string | null;
genre: string;
genreEN: string | null;
};
sdvx: { displayVersion: string };
usc: Record<string, never>;
bms: {
genre: string | null;
subtitle: string | null;
subartist: string | null;
tableString: string | null;
};
chunithm: { genre: string; displayVersion: string };
gitadora: { isHot: boolean; displayVersion: string };
wacca: {
titleJP: string;
artistJP: string;
genre: string;
displayVersion: string | null;
};
pms: {
genre: string | null;
subtitle: string | null;
subartist: string | null;
tableString: string | null;
};
itg: {
subtitle: string;
banner: string | null;
};
}
/**
* What tierlists does each GPT support?
*
* If the GPT has no tierlists, set this to `never`.
*/
export interface SupportedTierlists {
"iidx:SP": "kt-EXHC" | "kt-HC" | "kt-NC";
"iidx:DP": "dp-tier";
"bms:7K": "sgl-EC" | "sgl-HC";
"bms:14K": "sgl-EC" | "sgl-HC";
"popn:9B": never;
"sdvx:Single": "clear";
"usc:Keyboard": never;
"usc:Controller": never;
"maimaidx:Single": never;
"jubeat:Single": never;
"museca:Single": never;
"chunithm:Single": never;
"gitadora:Gita": never;
"gitadora:Dora": never;
"wacca:Single": never;
"pms:Controller": "sgl-EC" | "sgl-HC";
"pms:Keyboard": "sgl-EC" | "sgl-HC";
"itg:Stamina": never;
}
type RanOptions = "MIRROR" | "NONRAN" | "R-RANDOM" | "RANDOM" | "S-RANDOM";
interface IIDXSPScoreMeta {
random: RanOptions | null;
assist: "AUTO SCRATCH" | "FULL ASSIST" | "LEGACY NOTE" | "NO ASSIST" | null;
range: "HIDDEN+" | "LIFT SUD+" | "LIFT" | "NONE" | "SUD+ HID+" | "SUDDEN+" | null;
gauge: "ASSISTED EASY" | "EASY" | "EX-HARD" | "HARD" | "NORMAL" | null;
}
interface BMS7KScoreMeta {
random: RanOptions | null;
inputDevice: "BM_CONTROLLER" | "KEYBOARD" | null;
client: "LR2" | "lr2oraja" | null;
gauge: "EASY" | "EX-HARD" | "HARD" | "NORMAL" | null;
}
interface PMSScoreMeta {
random: RanOptions | null;
client: "beatoraja" | null;
gauge: "EASY" | "EX-HARD" | "HARD" | "NORMAL" | null;
}
interface USCScoreMeta {
noteMod: "MIR-RAN" | "MIRROR" | "NORMAL" | "RANDOM" | null;
gaugeMod: "HARD" | "NORMAL" | "PERMISSIVE" | null;
}
/**
* What metadata should exist for scores on this GPT?
*
* This is *score* specific metadata and cannot **ever** exist on a PB.
* That is to say, the datapoints here don't make sense to ever be merged.
* You can't merge a NONRAN option with MIRROR!
*
* This is intended for things like modifiers or options.
* (What RANDOM was the user using? etc.)
*/
export interface ScoreMeta {
"iidx:SP": IIDXSPScoreMeta;
"iidx:DP": IIDXSPScoreMeta & {
random: { left: RanOptions; right: RanOptions } | null;
};
"popn:9B": {
hiSpeed: number | null;
hidden: integer | null;
sudden: integer | null;
random: "MIRROR" | "NONRAN" | "RANDOM" | "S-RANDOM" | null;
gauge: "DANGER" | "EASY" | "HARD" | "NORMAL" | null;
};
"sdvx:Single": { inSkillAnalyser: boolean | null };
"usc:Controller": USCScoreMeta;
"usc:Keyboard": USCScoreMeta;
"maimaidx:Single": Record<string, never>;
"jubeat:Single": Record<string, never>;
"museca:Single": Record<string, never>;
"bms:7K": BMS7KScoreMeta;
"bms:14K": BMS7KScoreMeta & {
random: { left: RanOptions; right: RanOptions } | null;
};
"chunithm:Single": Record<string, never>;
"gitadora:Gita": Record<string, never>;
"gitadora:Dora": Record<string, never>;
"wacca:Single": { mirror: boolean | null };
"pms:Controller": PMSScoreMeta;
"pms:Keyboard": PMSScoreMeta;
"itg:Stamina": Record<string, never>;
}
interface BASE_ADDITIONAL_METRICS {
fast: integer | null;
slow: integer | null;
maxCombo: integer | null;
}
type IIDXAdditionalMetrics = BASE_ADDITIONAL_METRICS & {
bp: integer | null;
gauge: number | null;
gaugeHistory: Array<number | null> | null;
comboBreak: integer | null;
gsmEASY: Array<number | null> | null;
gsmNORMAL: Array<number | null> | null;
gsmHARD: Array<number | null> | null;
gsmEX_HARD: Array<number | null> | null;
};
type BMSJudgePermutations = `${"e" | "l"}${"bd" | "gd" | "gr" | "pg" | "pr"}`;
type BMSAdditionalMetrics = BASE_ADDITIONAL_METRICS & {
[K in BMSJudgePermutations]: integer;
} & {
bp: integer | null;
gauge: number | null;
gaugeHistory: Array<number> | null;
};
type USCAdditionalMetrics = BASE_ADDITIONAL_METRICS & {
gauge: number | null;
};
/**
* What GPT-Specific content exists for this score that isn't the actual score metrics?
*
* These are metrics like gauge values at the end of the score, etc. Things like
* fast/slow and maxCombo can also be here.
*
* Everything here is generally optional or nullable, and does not *have* to be provided
* by a score import.
*/
export interface AdditionalMetrics {
"iidx:SP": IIDXAdditionalMetrics;
"iidx:DP": IIDXAdditionalMetrics;
"popn:9B": BASE_ADDITIONAL_METRICS & {
gauge: number | null;
specificClearType:
| "clearCircle"
| "clearDiamond"
| "clearStar"
| "easyClear"
| "failedCircle"
| "failedDiamond"
| "failedStar"
| "fullComboCircle"
| "fullComboDiamond"
| "fullComboStar"
| "perfect"
| null;
};
"sdvx:Single": BASE_ADDITIONAL_METRICS & {
gauge: number | null;
exScore: number | null;
};
"usc:Controller": USCAdditionalMetrics;
"usc:Keyboard": USCAdditionalMetrics;
"maimaidx:Single": BASE_ADDITIONAL_METRICS;
"jubeat:Single": BASE_ADDITIONAL_METRICS;
"museca:Single": BASE_ADDITIONAL_METRICS;
"bms:7K": BMSAdditionalMetrics;
"bms:14K": BMSAdditionalMetrics;
"chunithm:Single": BASE_ADDITIONAL_METRICS;
"gitadora:Gita": BASE_ADDITIONAL_METRICS;
"gitadora:Dora": BASE_ADDITIONAL_METRICS;
"wacca:Single": BASE_ADDITIONAL_METRICS;
"pms:Controller": BMSAdditionalMetrics;
"pms:Keyboard": BMSAdditionalMetrics;
"itg:Stamina": BASE_ADDITIONAL_METRICS & {
gaugeHistory: Array<integer> | null;
diedAt: integer | null;
};
}
type IIDXJudges = "bad" | "good" | "great" | "pgreat" | "poor";
type GitadoraJudges = "good" | "great" | "miss" | "ok" | "perfect";
// judges might need to be changed here...
// @bug Known that sdvx calls misses errors. I, however, don't care.
type SDVXJudges = "critical" | "miss" | "near";
/**
* What judgements exist for this GPT?
*
* Tachi mandates that a game has a concept of judgements (i.e. timing windows).
* This type declares the names of those timing windows.
*/
export interface Judgements {
"iidx:SP": IIDXJudges;
"iidx:DP": IIDXJudges;
"popn:9B": "bad" | "cool" | "good" | "great";
"sdvx:Single": SDVXJudges;
"usc:Controller": SDVXJudges;
"usc:Keyboard": SDVXJudges;
"maimaidx:Single": "good" | "great" | "miss" | "pcrit" | "perfect";
"jubeat:Single": "good" | "great" | "miss" | "perfect" | "poor";
"museca:Single": "critical" | "miss" | "near";
"bms:7K": IIDXJudges;
"bms:14K": IIDXJudges;
"chunithm:Single": "attack" | "jcrit" | "justice" | "miss";
"gitadora:Gita": GitadoraJudges;
"gitadora:Dora": GitadoraJudges;
"wacca:Single": "good" | "great" | "marvelous" | "miss";
"pms:Controller": "bad" | "cool" | "good" | "great" | "poor";
"pms:Keyboard": "bad" | "cool" | "good" | "great" | "poor";
"itg:Stamina": "decent" | "excellent" | "fantastic" | "great" | "miss" | "wayoff";
}
/**
* If any, what preferences (settings) can users set for this GPT?
*
* This allows us to support GPT-Specific preferences, like a "BPI Target", which only
* makes sense for IIDX.
*/
export interface UGPTSpecificPreferences {
"iidx:SP": { display2DXTra: boolean; bpiTarget: number };
"iidx:DP": { display2DXTra: boolean; bpiTarget: number };
"popn:9B": Record<string, never>;
"sdvx:Single": { vf6Target: number };
"usc:Controller": { vf6Target: number };
"usc:Keyboard": { vf6Target: number };
"maimaidx:Single": Record<string, never>;
"jubeat:Single": { jubilityTarget: number };
"museca:Single": Record<string, never>;
"bms:7K": { displayTables: Array<string> };
"bms:14K": { displayTables: Array<string> };
"chunithm:Single": Record<string, never>;
"gitadora:Gita": Record<string, never>;
"gitadora:Dora": Record<string, never>;
"wacca:Single": Record<string, never>;
"pms:Controller": Record<string, never>;
"pms:Keyboard": Record<string, never>;
"itg:Stamina": Record<string, never>;
}
/**
* What classes does this GPT support?
*
* This type declares the names of class sets for a given GPT.
*
* A class is a *discrete* value from an ordered list of values. Common examples
* would be dans, jubility colours, etc.
*
* You can think of these as discrete profile metrics.
*
* Note that the actual values for these classes are declared in each game's
* configuration.
*/
export interface GameClassSets {
"iidx:SP": "dan";
"iidx:DP": "dan";
"popn:9B": "class";
"sdvx:Single": "dan" | "vfClass";
"usc:Keyboard": never;
"usc:Controller": never;
"maimaidx:Single": "colour" | "dan";
"jubeat:Single": "colour";
"museca:Single": never;
"bms:7K": "genocideDan" | "lnDan" | "scratchDan" | "stslDan";
"bms:14K": "genocideDan" | "stslDan";
"chunithm:Single": "colour";
"gitadora:Gita": "colour";
"gitadora:Dora": "colour";
"wacca:Single": "colour" | "stageUp";
"pms:Controller": "dan";
"pms:Keyboard": "dan";
"itg:Stamina": never;
}
-13
View File
@@ -1,16 +1,3 @@
import type { GameClassSets } from "../config/game-classes";
import type {
IDStrings,
Lamps,
JudgementLookup,
integer,
HitMetaLookup,
ScoreMetaLookup,
Difficulties,
IDStringToGame,
IDStringToPlaytype,
GPTSupportedVersions,
} from "../types";
// These MatchTypes don't need `difficulty` set in the batch manual.
type MatchTypesNoDifficulty = "bmsChartHash" | "itgChartHash" | "popnChartHash" | "uscChartHash";
-23
View File
@@ -1,27 +1,4 @@
import type { AllClassSets, GameClasses } from "../config/game-classes";
import type { UserAuthLevels, integer } from "../types";
import type {
Game,
Playtype,
Playtypes,
Difficulties,
Lamps,
Grades,
UGPTSpecificPreferences,
SessionRatingAlgorithms,
GPTStrings,
ProfileRatingAlgorithms,
GPTStringToPlaytype,
GPTStringToGame,
ChartData,
SupportedTierlists,
SupportedVersions,
SongData,
Judgements,
AdditionalMetrics,
ScoreMeta,
ScoreRatingAlgorithms,
} from "./_TOREMOVE-game-implementations";
import type { APIPermissions } from "./api";
import type { ImportTypes } from "./import-types";
import type { BaseNotification, NotificationBody } from "./notifications";
+17 -28
View File
@@ -23,8 +23,9 @@ export interface ClassInfo {
interface BaseClassConfig {
/**
* What are the possible values for this class field? This should be in
* ascending order.
* What are the possible values for this class field?
*
* @note This should be in ascending order.
*/
values: Array<ClassInfo>;
}
@@ -61,30 +62,27 @@ export interface DerivedClassConfig extends BaseClassConfig {
export type ClassConfig = DerivedClassConfig | ProvidedClassConfig;
/**
* This game's difficulty names are arbitrary (unique) strings. This makes sense
* for a lot of home games, where a song may have any number of difficulties
* attached onto it that we want to care for (think osu!).
*/
export interface DynamicDifficulties {
/**
* This game's difficulty names are arbitrary (unique) strings. This makes sense
* for a lot of home games, where a song may have any number of difficulties
* attached onto it that we want to care for (think osu!).
*/
type: "DYNAMIC";
}
/**
* The amount of difficulties that may belong to a song is a fixed possible set.
*
* For example, if the game only ever supports Easy, Normal and Hard difficulties
* this would be static.
*
* If the game was more like osu!, where a song can have arbitrary unique strings
* as difficulty names, you want DynamicDifficulties instead.
*/
export interface FixedDifficulties<Difficulty extends string> {
/**
* The amount of difficulties that may belong to a song is a fixed possible set.
*
* For example, if the game only ever supports Easy, Normal and Hard difficulties
* this would be static.
*
* If the game was more like osu!, where a song can have arbitrary unique strings
* as difficulty names, you want "DYNAMIC" instead.
*/
type: "FIXED";
/**
* What order should these difficulties appear in?
*/
difficultyOrder: ReadonlyArray<Difficulty>;
/**
@@ -95,15 +93,6 @@ export interface FixedDifficulties<Difficulty extends string> {
*/
difficultyShorthand: Partial<Record<Difficulty, string>>;
/**
* When you click on a song with no difficulty, what should it default to?
*
* @note If the song doesn't have this difficulty, it will pick in an
* undefined order.
*
* i.e. the default difficulty is "ANOTHER" and the song has no "ANOTHER" available,
* then it will just pick something at random that still belongs to that chart.
*/
defaultDifficulty: Difficulty;
}
+7 -5
View File
@@ -1,7 +1,7 @@
import type { MatchTypes } from "./batch-manual";
import type { ClassConfig, DifficultyConfig, RatingAlgorithmConfig } from "./game-config";
import type { ScoreMetric } from "./metrics";
import type { ZodRawShape } from "zod";
import type { AnyZodObject, ZodRawShape } from "zod";
/**
* This is **NOT** intended for external use.
@@ -30,12 +30,12 @@ export type INTERNAL_GPT_CONFIG = Readonly<{
sessionRatingAlgs: Record<string, RatingAlgorithmConfig>;
profileRatingAlgs: Record<string, RatingAlgorithmConfig>;
supportedClasses: Record<string, ClassConfig>;
defaultScoreRatingAlg: string;
defaultSessionRatingAlg: string;
defaultProfileRatingAlg: string;
supportedClasses: Record<string, ClassConfig>;
difficultyConfig: DifficultyConfig;
orderedJudgements: ReadonlyArray<string>;
@@ -44,7 +44,9 @@ export type INTERNAL_GPT_CONFIG = Readonly<{
supportedMatchTypes: ReadonlyArray<MatchTypes>;
chartData: ZodRawShape;
preferences: AnyZodObject;
chartData: AnyZodObject;
scoreMeta: AnyZodObject;
}>;
/**
@@ -58,5 +60,5 @@ export type INTERNAL_GAME_CONFIG<PT extends string = string> = Readonly<{
name: string;
validPlaytypes: ReadonlyArray<PT>;
defaultPlaytype: PT;
songData: ZodRawShape;
songData: AnyZodObject;
}>;
+13 -3
View File
@@ -67,9 +67,19 @@ export type ExtractMetricType<M extends ScoreMetric> = M extends DecimalScoreMet
/**
* Extract all the names of enum types from this record of score metrics.
*
* This is used for enforcing the "default enum" for a GPT. All games have to have
* a default, preferred enum, for things like folder raises and graphs.
*
* For most games, this will be "grade" or "lamp". However, we need a typesafe way
* of checking that this metric is an enum.
*
* Please ignore how magical this type is. I'm sorry. You aren't expected to understand
* this.
*
* @example
* ExtractEnumMetricNames<{ score: { type: "INTEGER" } }, grade: { type: "ENUM" ... }>
* would return a type of "grade"
* ExtractEnumMetricNames<{ score: { type: "INTEGER" } }, grade: { type: "ENUM" ... },
* lamp: { type: "ENUM", ... }>
* would return a type of "grade" | "lamp"
*/
export type ExtractEnumMetricNames<R extends Record<string, ScoreMetric>> = {
[K in keyof R]: R[K] extends EnumScoreMetric ? K : never;
@@ -78,7 +88,7 @@ export type ExtractEnumMetricNames<R extends Record<string, ScoreMetric>> = {
type PossibleMetrics = ExtractMetricType<ScoreMetric>;
/**
* Turn a record of ScoreMetrics into their actual representative values.
* Turn a record of ScoreMetrics into their actual literal values.
*
* @example ExtractMetrics<{ score: IntegerScoreMetric; lamp: EnumScoreMetric<"FAILED"|"CLEAR"> }>
* will equal