Properly Type scoreMeta using new IDstrings

This commit is contained in:
zkldi
2021-04-07 12:07:55 +01:00
parent a6d4d7734a
commit b7086edb89
3 changed files with 280 additions and 189 deletions
+3 -3
View File
@@ -46,7 +46,7 @@ declare const _default: {
lampColours: ColourStuff;
gradeColours: ColourStuff;
serviceSupportedGames: Record<string, Game[]>;
defaultPlaytype: Record<Game, "SP" | "DP" | "9B" | "Single" | "7K" | "14K" | "5K" | "Gita" | "Dora">;
defaultPlaytype: Record<Game, "SP" | "DP" | "Single" | "9B" | "7K" | "14K" | "5K" | "Gita" | "Dora">;
gameChartIndicators: {
iidx: string[];
popn: string[];
@@ -120,7 +120,7 @@ declare const _default: {
clearExpMultiplier: number;
};
};
validPlaytypes: Record<Game, ("SP" | "DP" | "9B" | "Single" | "7K" | "14K" | "5K" | "Gita" | "Dora")[]>;
validPlaytypes: Record<Game, ("SP" | "DP" | "Single" | "9B" | "7K" | "14K" | "5K" | "Gita" | "Dora")[]>;
ScoreGradeDelta: typeof ScoreGradeDelta;
judgeColours: {
iidx: {
@@ -339,7 +339,7 @@ declare const _default: {
validHitData: Record<Game, string[]>;
validHitMeta: Record<Game, string[]>;
boundaryHCF: Record<Game, number>;
gameSpecificCalc: Partial<Record<Game, Partial<Record<"SP" | "DP" | "9B" | "Single" | "7K" | "14K" | "5K" | "Gita" | "Dora", string[]>>>>;
gameSpecificCalc: Partial<Record<Game, Partial<Record<"SP" | "DP" | "Single" | "9B" | "7K" | "14K" | "5K" | "Gita" | "Dora", string[]>>>>;
expChartScale: Record<Game, number>;
FormatDifficulty: typeof FormatDifficulty;
DirectScoreGradeDelta: typeof DirectScoreGradeDelta;
+112 -91
View File
@@ -3,6 +3,20 @@ export interface CounterDocument {
counterName: string;
value: integer;
}
/**
* IDStrings are an internal (ish) identifier used to identify
* Game + Playtype combos. These are used because TypeScript cannot accurately
* apply two levels of indexing to types, which makes writing generic interfaces
* like the ScoreDocument (Especially the ScoreDocument) *very* hard!
*/
export declare type IDStrings = "iidx:SP" | "iidx:DP" | "popn:9B" | "sdvx:Single" | "usc:Single" | "ddr:SP" | "maimai:Single" | "jubeat:Single" | "bms:7K" | "bms:14K" | "bms:5K" | "chunithm:Single" | "gitadora:Gita" | "gitadora:Dora";
/**
* A utility type for creating an ID string given a game and playtype.
* It should be noted that typescript refuses to assert that
* IDString<G, P> is a member of IDStrings. You're free to attempt to
* rewrite IDStrings to try and get this to work, I promise you it doesn't.
*/
export declare type IDString<G extends Game, P extends Playtypes[G]> = `${G}:${P}`;
/**
* All MongoDB Documents require this field, or atleast they all have them in ktchi's DB.
*/
@@ -457,6 +471,44 @@ export interface UserMilestoneDocument extends MongoDBDocument {
timeAchieved: integer | null;
progress: integer;
}
declare type RanOptions = "NONRAN" | "RANDOM" | "R-RANDOM" | "S-RANDOM" | "MIRROR";
interface IIDXSPScoreMeta {
optionsRandom: RanOptions;
optionsAssist: "AUTO SCRATCH" | "5KEYS" | "LEGACY NOTE" | "ASCR + 5KEY" | "ASCR + LEGACY" | "5KEYS + LEGACY" | "FULL ASSIST";
optionsRange: "SUDDEN+" | "HIDDEN+" | "SUD+ HID+" | "LIFT" | "LIFT SUD+";
optionsGauge: "ASSISTED EASY" | "EASY" | "HARD" | "EX-HARD";
pacemaker: "NO GRAPH" | "MY BEST" | "RIVAL 1" | "RIVAL 2" | "RIVAL 3" | "RIVAL 4" | "RIVAL 5" | "RIVAL NEXT" | "RIVAL BEST" | "RIVAL AVERAGE" | "NATIONAL BEST" | "NATIONAL AVERAGE" | "PREFECTURE BEST" | "PREFECTURE AVERAGE" | "CLASS BEST" | "CLASS AVERAGE" | "VENUE BEST" | "VENUE NEXT" | "PREVIOUS GHOST" | "PACEMAKER AAA" | "PACEMAKER AA" | "PACEMAKER A" | "PACEMAKER" | /* ??? */ "PACEMAKER NEXT" | "PACEMAKER NEXT+" | "PLAYER 1" | "PLAYER 2";
pacemakerName: string;
pacemakerTarget: integer;
}
interface BMS7KScoreMeta {
optionsRandom: RanOptions;
inputDevice: string;
}
interface ScoreMetaLookup {
"iidx:SP": IIDXSPScoreMeta;
"iidx:DP": IIDXSPScoreMeta & {
optionsRandom: [RanOptions, RanOptions];
};
"popn:9B": Record<string, never>;
"sdvx:Single": Record<string, never>;
"usc:Single": BASE_VALID_HIT_META & {
gauge: number;
};
"ddr:SP": Record<string, never>;
"ddr:DP": Record<string, never>;
"maimai:Single": Record<string, never>;
"jubeat:Single": Record<string, never>;
"museca:Single": Record<string, never>;
"bms:7K": BMS7KScoreMeta;
"bms:14K": BMS7KScoreMeta & {
optionsRandom: [RanOptions, RanOptions];
};
"bms:5K": Record<string, never>;
"chunithm:Single": Record<string, never>;
"gitadora:Gita": Record<string, never>;
"gitadora:Dora": Record<string, never>;
}
interface BASE_VALID_HIT_META {
fast: integer;
slow: integer;
@@ -474,108 +526,77 @@ declare type BMSHitMeta = BASE_VALID_HIT_META & {
} & {
bp: integer;
gauge: number;
random: "NONRAN" | "MIRROR" | "RANDOM" | "R-RANDOM" | "S-RANDOM";
diedAt: integer | null;
inputDevice: string;
};
export interface HitMetaLookup {
iidx: {
SP: IIDXHitMeta;
DP: IIDXHitMeta;
"iidx:SP": IIDXHitMeta;
"iidx:DP": IIDXHitMeta;
"popn:9B": BASE_VALID_HIT_META & {
gauge: number;
};
museca: {
Single: BASE_VALID_HIT_META;
"sdvx:Single": BASE_VALID_HIT_META & {
gauge: number;
};
ddr: {
SP: BASE_VALID_HIT_META;
DP: BASE_VALID_HIT_META;
};
maimai: {
SP: BASE_VALID_HIT_META;
DP: BASE_VALID_HIT_META;
};
jubeat: {
Single: BASE_VALID_HIT_META;
};
popn: {
"9B": BASE_VALID_HIT_META & {
gauge: number;
};
};
sdvx: {
Single: BASE_VALID_HIT_META & {
gauge: number;
};
};
chunithm: {
Single: BASE_VALID_HIT_META;
};
gitadora: {
Gita: BASE_VALID_HIT_META;
Dora: BASE_VALID_HIT_META;
};
usc: {
Single: BASE_VALID_HIT_META & {
gauge: number;
};
};
bms: {
"7K": BMSHitMeta;
"14K": BMSHitMeta;
"5K": BMSHitMeta;
"usc:Single": BASE_VALID_HIT_META & {
gauge: number;
};
"ddr:SP": BASE_VALID_HIT_META;
"ddr:DP": BASE_VALID_HIT_META;
"maimai:Single": BASE_VALID_HIT_META;
"jubeat:Single": BASE_VALID_HIT_META;
"museca:Single": BASE_VALID_HIT_META;
"bms:7K": BMSHitMeta;
"bms:14K": BMSHitMeta;
"bms:5K": BMSHitMeta;
"chunithm:Single": BASE_VALID_HIT_META;
"gitadora:Gita": BASE_VALID_HIT_META;
"gitadora:Dora": BASE_VALID_HIT_META;
}
declare type IIDXJudges = "pgreat" | "great" | "good" | "bad" | "poor";
declare type DDRJudges = "marvelous" | "perfect" | "great" | "good" | "boo" | "miss" | "ok" | "ng";
declare type GitadoraJudges = "perfect" | "great" | "good" | "ok" | "miss";
declare type SDVXJudges = "critical" | "near" | "miss";
export interface JudgementLookup {
iidx: {
SP: IIDXJudges;
DP: IIDXJudges & {
foo: string;
};
};
bms: {
"7K": IIDXJudges;
"14K": IIDXJudges;
"5K": IIDXJudges;
};
museca: {
Single: "critical" | "near" | "miss";
};
ddr: {
SP: DDRJudges;
DP: DDRJudges;
};
sdvx: {
Single: SDVXJudges;
};
maimai: {
Single: "perfect" | "great" | "good" | "miss";
};
jubeat: {
Single: "perfect" | "great" | "good" | "bad" | "miss";
};
popn: {
"9B": "cool" | "great" | "good" | "miss";
};
chunithm: {
Single: "jcrit" | "justice" | "attack" | "miss";
};
gitadora: {
Gita: GitadoraJudges;
Dora: GitadoraJudges;
};
usc: {
Single: SDVXJudges;
};
"iidx:SP": IIDXJudges;
"iidx:DP": IIDXJudges;
"popn:9B": "cool" | "great" | "good" | "miss";
"sdvx:Single": SDVXJudges;
"usc:Single": SDVXJudges;
"ddr:SP": DDRJudges;
"ddr:DP": DDRJudges;
"maimai:Single": "perfect" | "great" | "good" | "miss";
"jubeat:Single": "perfect" | "great" | "good" | "bad" | "miss";
"museca:Single": "critical" | "near" | "miss";
"bms:7K": IIDXJudges;
"bms:14K": IIDXJudges;
"bms:5K": IIDXJudges;
"chunithm:Single": "jcrit" | "justice" | "attack" | "miss";
"gitadora:Gita": GitadoraJudges;
"gitadora:Dora": GitadoraJudges;
}
export interface ScoreDocument<T extends Game, P extends Playtypes[T]> extends MongoDBDocument {
export interface GameSpecificCalcLookup {
"iidx:SP": "BPI" | "K%";
"iidx:DP": "BPI";
"popn:9B": never;
"sdvx:Single": "VF4" | "VF5";
"usc:Single": "VF4" | "VF5";
"ddr:SP": "MFCP";
"ddr:DP": "MFCP";
"maimai:Single": never;
"jubeat:Single": never;
"museca:Single": never;
"bms:7K": never;
"bms:14K": never;
"bms:5K": never;
"chunithm:Single": never;
"gitadora:Gita": never;
"gitadora:Dora": never;
}
export interface ScoreDocument<G extends Game, P extends Playtypes[G], I extends IDStrings = IDString<G, P>> extends MongoDBDocument {
service: string;
game: T;
game: G;
playtype: P;
difficulty: Difficulties[T];
difficulty: Difficulties[G];
userID: integer;
scoreData: {
score: number;
@@ -585,14 +606,14 @@ export interface ScoreDocument<T extends Game, P extends Playtypes[T]> extends M
lampIndex: integer;
gradeIndex: integer;
esd: number | null;
hitData: Record<JudgementLookup[T][P], integer>;
hitMeta: Partial<HitMetaLookup[T][P]>;
hitData: Record<JudgementLookup[I], integer>;
hitMeta: Partial<HitMetaLookup[I]>;
};
scoreMeta: Record<string, unknown>;
scoreMeta: ScoreMetaLookup[I];
calculatedData: {
rating: number;
lampRating: number;
gameSpecific: Record<string, number>;
gameSpecific: Partial<Record<GameSpecificCalcLookup[I], number | null>>;
ranking: integer | null;
outOf: integer | null;
};
+165 -95
View File
@@ -5,6 +5,36 @@ export interface CounterDocument {
value: integer;
}
/**
* IDStrings are an internal (ish) identifier used to identify
* Game + Playtype combos. These are used because TypeScript cannot accurately
* apply two levels of indexing to types, which makes writing generic interfaces
* like the ScoreDocument (Especially the ScoreDocument) *very* hard!
*/
export type IDStrings =
| "iidx:SP"
| "iidx:DP"
| "popn:9B"
| "sdvx:Single"
| "usc:Single"
| "ddr:SP"
| "maimai:Single"
| "jubeat:Single"
| "bms:7K"
| "bms:14K"
| "bms:5K"
| "chunithm:Single"
| "gitadora:Gita"
| "gitadora:Dora";
/**
* A utility type for creating an ID string given a game and playtype.
* It should be noted that typescript refuses to assert that
* IDString<G, P> is a member of IDStrings. You're free to attempt to
* rewrite IDStrings to try and get this to work, I promise you it doesn't.
*/
export type IDString<G extends Game, P extends Playtypes[G]> = `${G}:${P}`;
/**
* All MongoDB Documents require this field, or atleast they all have them in ktchi's DB.
*/
@@ -575,6 +605,77 @@ export interface UserMilestoneDocument extends MongoDBDocument {
progress: integer;
}
type RanOptions = "NONRAN" | "RANDOM" | "R-RANDOM" | "S-RANDOM" | "MIRROR";
interface IIDXSPScoreMeta {
optionsRandom: RanOptions;
optionsAssist:
| "AUTO SCRATCH"
| "5KEYS"
| "LEGACY NOTE"
| "ASCR + 5KEY"
| "ASCR + LEGACY"
| "5KEYS + LEGACY"
| "FULL ASSIST";
optionsRange: "SUDDEN+" | "HIDDEN+" | "SUD+ HID+" | "LIFT" | "LIFT SUD+";
optionsGauge: "ASSISTED EASY" | "EASY" | "HARD" | "EX-HARD";
// oh my
pacemaker:
| "NO GRAPH"
| "MY BEST"
| "RIVAL 1"
| "RIVAL 2"
| "RIVAL 3"
| "RIVAL 4"
| "RIVAL 5"
| "RIVAL NEXT"
| "RIVAL BEST"
| "RIVAL AVERAGE"
| "NATIONAL BEST"
| "NATIONAL AVERAGE"
| "PREFECTURE BEST"
| "PREFECTURE AVERAGE"
| "CLASS BEST"
| "CLASS AVERAGE"
| "VENUE BEST"
| "VENUE NEXT"
| "PREVIOUS GHOST"
| "PACEMAKER AAA"
| "PACEMAKER AA"
| "PACEMAKER A"
| "PACEMAKER"
| /* ??? */ "PACEMAKER NEXT"
| "PACEMAKER NEXT+"
| "PLAYER 1"
| "PLAYER 2";
pacemakerName: string; // contains DJ Name
pacemakerTarget: integer; // the EXScore the pacemaker was aimed at
}
interface BMS7KScoreMeta {
optionsRandom: RanOptions;
inputDevice: string;
}
interface ScoreMetaLookup {
"iidx:SP": IIDXSPScoreMeta;
"iidx:DP": IIDXSPScoreMeta & { optionsRandom: [RanOptions, RanOptions] };
"popn:9B": Record<string, never>;
"sdvx:Single": Record<string, never>;
"usc:Single": BASE_VALID_HIT_META & { gauge: number };
"ddr:SP": Record<string, never>;
"ddr:DP": Record<string, never>;
"maimai:Single": Record<string, never>;
"jubeat:Single": Record<string, never>;
"museca:Single": Record<string, never>;
"bms:7K": BMS7KScoreMeta;
"bms:14K": BMS7KScoreMeta & { optionsRandom: [RanOptions, RanOptions] };
"bms:5K": Record<string, never>;
"chunithm:Single": Record<string, never>;
"gitadora:Gita": Record<string, never>;
"gitadora:Dora": Record<string, never>;
}
interface BASE_VALID_HIT_META {
fast: integer;
slow: integer;
@@ -596,51 +697,26 @@ type BMSHitMeta = BASE_VALID_HIT_META &
} & {
bp: integer;
gauge: number;
random: "NONRAN" | "MIRROR" | "RANDOM" | "R-RANDOM" | "S-RANDOM";
diedAt: integer | null;
inputDevice: string;
};
export interface HitMetaLookup {
iidx: {
SP: IIDXHitMeta;
DP: IIDXHitMeta;
};
museca: {
Single: BASE_VALID_HIT_META;
};
ddr: {
SP: BASE_VALID_HIT_META;
DP: BASE_VALID_HIT_META;
};
maimai: {
SP: BASE_VALID_HIT_META;
DP: BASE_VALID_HIT_META;
};
jubeat: {
Single: BASE_VALID_HIT_META;
};
popn: {
"9B": BASE_VALID_HIT_META & { gauge: number };
};
sdvx: {
Single: BASE_VALID_HIT_META & { gauge: number };
};
chunithm: {
Single: BASE_VALID_HIT_META;
};
gitadora: {
Gita: BASE_VALID_HIT_META;
Dora: BASE_VALID_HIT_META;
};
usc: {
Single: BASE_VALID_HIT_META & { gauge: number };
};
bms: {
"7K": BMSHitMeta;
"14K": BMSHitMeta;
"5K": BMSHitMeta;
};
"iidx:SP": IIDXHitMeta;
"iidx:DP": IIDXHitMeta;
"popn:9B": BASE_VALID_HIT_META & { gauge: number };
"sdvx:Single": BASE_VALID_HIT_META & { gauge: number };
"usc:Single": BASE_VALID_HIT_META & { gauge: number };
"ddr:SP": BASE_VALID_HIT_META;
"ddr:DP": BASE_VALID_HIT_META;
"maimai:Single": BASE_VALID_HIT_META;
"jubeat:Single": BASE_VALID_HIT_META;
"museca:Single": BASE_VALID_HIT_META;
"bms:7K": BMSHitMeta;
"bms:14K": BMSHitMeta;
"bms:5K": BMSHitMeta;
"chunithm:Single": BASE_VALID_HIT_META;
"gitadora:Gita": BASE_VALID_HIT_META;
"gitadora:Dora": BASE_VALID_HIT_META;
}
type IIDXJudges = "pgreat" | "great" | "good" | "bad" | "poor";
@@ -651,55 +727,58 @@ type GitadoraJudges = "perfect" | "great" | "good" | "ok" | "miss";
type SDVXJudges = "critical" | "near" | "miss";
export interface JudgementLookup {
iidx: {
SP: IIDXJudges;
DP: IIDXJudges & { foo: string };
};
bms: {
"7K": IIDXJudges;
"14K": IIDXJudges;
"5K": IIDXJudges;
};
museca: {
Single: "critical" | "near" | "miss";
};
ddr: {
SP: DDRJudges;
DP: DDRJudges;
};
sdvx: {
Single: SDVXJudges;
};
maimai: {
Single: "perfect" | "great" | "good" | "miss";
};
jubeat: {
Single: "perfect" | "great" | "good" | "bad" | "miss";
};
popn: {
"9B": "cool" | "great" | "good" | "miss";
};
chunithm: {
Single: "jcrit" | "justice" | "attack" | "miss";
};
gitadora: {
Gita: GitadoraJudges;
Dora: GitadoraJudges;
};
usc: {
Single: SDVXJudges;
};
"iidx:SP": IIDXJudges;
"iidx:DP": IIDXJudges;
"popn:9B": "cool" | "great" | "good" | "miss";
"sdvx:Single": SDVXJudges;
"usc:Single": SDVXJudges;
"ddr:SP": DDRJudges;
"ddr:DP": DDRJudges;
"maimai:Single": "perfect" | "great" | "good" | "miss";
"jubeat:Single": "perfect" | "great" | "good" | "bad" | "miss";
"museca:Single": "critical" | "near" | "miss";
"bms:7K": IIDXJudges;
"bms:14K": IIDXJudges;
"bms:5K": IIDXJudges;
"chunithm:Single": "jcrit" | "justice" | "attack" | "miss";
"gitadora:Gita": GitadoraJudges;
"gitadora:Dora": GitadoraJudges;
}
export interface ScoreDocument<T extends Game, P extends Playtypes[T]> extends MongoDBDocument {
export interface GameSpecificCalcLookup {
"iidx:SP": "BPI" | "K%";
"iidx:DP": "BPI";
"popn:9B": never;
"sdvx:Single": "VF4" | "VF5";
"usc:Single": "VF4" | "VF5";
"ddr:SP": "MFCP";
"ddr:DP": "MFCP";
"maimai:Single": never;
"jubeat:Single": never;
"museca:Single": never;
"bms:7K": never;
"bms:14K": never;
"bms:5K": never;
"chunithm:Single": never;
"gitadora:Gita": never;
"gitadora:Dora": never;
}
export interface ScoreDocument<
G extends Game,
P extends Playtypes[G],
// @ts-expect-error Typescript thinks this is illegal.
// It *ABSOLUTELY* isn't.
I extends IDStrings = IDString<G, P>
> extends MongoDBDocument {
service: string;
game: T;
game: G;
playtype: P;
// @todo - allow different game+pt combinations to specify different difficulties?
// this happens in kamaitachi with gitadora, so I don't see why it shouldn't
// be constrained here.
// other than the fact that TS can't do it :(
difficulty: Difficulties[T];
difficulty: Difficulties[G];
userID: integer;
scoreData: {
score: number;
@@ -709,23 +788,14 @@ export interface ScoreDocument<T extends Game, P extends Playtypes[T]> extends M
lampIndex: integer;
gradeIndex: integer;
esd: number | null;
// @ts-expect-error Typescript does not currently support
// two levels of indexing (which is what we want). Instead
// of indexing all P where matches T, it indexes all permutations
// of P + T.
//
// If we expectErr this, it kinda works (but loses the ability to
// perform the second lookup). At the time, we don't have any
// scenario where one playtype has different hitMeta/hitData to another.
hitData: Record<JudgementLookup[T][P], integer>;
// @ts-expect-error see above
hitMeta: Partial<HitMetaLookup[T][P]>;
hitData: Record<JudgementLookup[I], integer>;
hitMeta: Partial<HitMetaLookup[I]>;
};
scoreMeta: Record<string, unknown>;
scoreMeta: ScoreMetaLookup[I];
calculatedData: {
rating: number;
lampRating: number;
gameSpecific: Record<string, number>;
gameSpecific: Partial<Record<GameSpecificCalcLookup[I], number | null>>;
ranking: integer | null;
outOf: integer | null;
};